Merge remote-tracking branch 'upstream/master' into SlowBigRestore

This commit is contained in:
Carl Johnsen
2025-01-03 10:11:50 +01:00
1815 changed files with 220690 additions and 159904 deletions
-66
View File
@@ -1,66 +0,0 @@
version: 1.0.{build}
image: Visual Studio 2017
configuration: Debug
platform: Any CPU
environment:
AUTOUPDATER_Duplicati_SKIP_UPDATE: 1
COVERALLS_REPO_TOKEN:
secure: szQsrkP5rvra8L60SomD73/dFvRwot0UuyA566zqzI2qmLOr+MhLN0AyC8BTbhYY
before_build:
- ps: $env:UNITTEST_BASEFOLDER = "$Env:APPVEYOR_BUILD_FOLDER" + "\testdata"
- cmd: nuget restore Duplicati.sln
build:
project: Duplicati.sln
verbosity: minimal
test_script:
- ps: |
nuget install OpenCover -Version 4.7.922 -OutputDirectory packages
nuget install coveralls.net -Version 0.7.0 -OutputDirectory packages
nuget install NUnit.Runners -Version 3.10.0 -OutputDirectory packages
$testDir = ".\Duplicati\UnitTest\bin\Debug"
$tests = @("$testDir\Duplicati.UnitTest.dll")
$testsPassed = $true
foreach ($elem in $tests) {
$testResultsFile = "$elem" + ".xml"
.\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe `
-register:user `
-target:.\packages\NUnit.ConsoleRunner.3.10.0\tools\nunit3-console.exe `
"-targetargs:""$elem"" /result:""$testResultsFile""" `
"-filter:+[Duplicati.Library.Main]* -[UnitTest]*" `
-output:opencover.xml `
-returntargetcode `
# Keep track of test failures so that we can throw an exception after
# uploading coverage and test results.
$testsPassed = $testsPassed -and $?
# Upload test results to AppVeyor.
$wc = New-Object "System.Net.WebClient"
$wc.UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\""$testResultsFile""))
}
if ($Env:COVERALLS_REPO_TOKEN) {
$revision = git rev-parse HEAD
$branch = git rev-parse --abbrev-ref HEAD
$commitAuthor = git show --quiet --format="%aN" $revision
$commitEmail = git show --quiet --format="%aE" $revision
$commitMessage = git show --quiet --format="%s" $revision
.\packages\coveralls.net.0.7.0\tools\csmacnz.Coveralls.exe `
--opencover -i opencover.xml `
--repoToken "$Env:COVERALLS_REPO_TOKEN" `
--commitId "$revision" `
--commitBranch "$branch" `
--commitAuthor "$commitAuthor" `
--commitEmail "$commitEmail" `
--commitMessage "$commitMessage" `
--useRelativePaths `
--basePath .\Duplicati\UnitTest\bin\Debug
}
if (!$testsPassed) {
throw "Tests failed."
}
+5
View File
@@ -0,0 +1,5 @@
*.csproj text
*.sh text eol=lf
#The #! in rules won't work if CRLF
/ReleaseBuilder/Resources/debian/* text eol=lf
/ReleaseBuilder/Resources/fedora/*.sh text eol=lf
@@ -1,5 +1,15 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!-- Thank you for taking the time to submit an issue using this template. By following the instructions and filling out the sections below, you will help the developers get the necessary information to fix your issue. You may remove sections that aren't relevant to your particular case. You can also preview your report before submitting it. -->
<!-- Love Duplicati? Please consider supporting our collective at https://opencollective.com/duplicati/donate. -->
<!-- Please note that the issues are a tool for Duplicati developers. If you post here, you are supposed to want to help the project by providing timely information on your problem so it can be fixed. If this is not the case, please use the forum instead -->
<!-- Please search to see if an issue has already been created for your report. -->
<!-- Replace the empty checkbox [ ] below with a checked one [x] if you already searched for duplicate bugs. -->
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions & support
url: https://forum.duplicati.com
about: Please ask and answer questions here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
+417
View File
@@ -0,0 +1,417 @@
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 }}"
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"
- 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 }}"
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 S3 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:
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
+143
View File
@@ -0,0 +1,143 @@
name: Freebsd
on:
push:
branches: "**freebsd**"
pull_request:
branches: "**freebsd**"
jobs:
zip:
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- name: Download dotnet
# See also https://github.com/sec/dotnet-core-freebsd-source-build
# See also https://github.com/Thefrank/dotnet-freebsd-crossbuild
run: curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v6.0.401-11/dotnet-sdk-6.0.401-freebsd-x64.tar.gz -o dn.tgz
- name: Build
id: test
uses: vmactions/freebsd-vm@v0
with:
usesh: true
# sync: sshfs
prepare: pkg install -y curl libinotify libunwind git cmake ninja bash wget icu
run: |
#!/bin/bash
set -x
set -e
pwd
ls -lah
whoami
env
freebsd-version
df -h
dmesg | grep memory
swapinfo
dd if=/dev/zero of=/root/swap1 bs=1m count=20480
chmod 0600 /root/swap1
mdconfig -a -t vnode -f /root/swap1 -u 0
swapon /dev/md0
df -h
swapinfo
export DOTNET_CLI_TELEMETRY_OPTOUT=1
BUILD_DIR=`pwd`
cd /var/tmp
mkdir dn
cd dn
echo Unpacking .NET
tar -xzf ${BUILD_DIR}/dn.tgz
cd ${BUILD_DIR}
mkdir /var/tmp/nuget
export RVERSION=v6.0.401-11
export VERSION=6.0.9
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.AspNetCore.App.Runtime.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.AspNetCore.App.Runtime.freebsd-x64.${VERSION}.nupkg
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.NETCore.App.Host.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.NETCore.App.Host.freebsd-x64.${VERSION}.nupkg
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.NETCore.App.Runtime.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.NETCore.App.Runtime.freebsd-x64.${VERSION}.nupkg
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.NETCore.App.Crossgen2.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.NETCore.App.Crossgen2.freebsd-x64.${VERSION}.nupkg
/var/tmp/dn/dotnet nuget add source /var/tmp/nuget/
cp -R `pwd` /var/tmp/build
cd /var/tmp/build
echo Restore
/var/tmp/dn/dotnet restore --runtime=freebsd-x64 Duplicati.sln
echo Build
/var/tmp/dn/dotnet build Duplicati.sln
echo Publish
/var/tmp/dn/dotnet publish -c Release --runtime=freebsd-x64 -o publish Duplicati.sln
cp -R ./publish ${BUILD_DIR}/publish
- name: Save Artifacts
uses: actions/upload-artifact@v2
with:
name: duplicati-freebsd-x64
path: publish
tests:
runs-on: macos-12
steps:
- uses: actions/checkout@v2
- name: Download dotnet
# See also https://github.com/sec/dotnet-core-freebsd-source-build
# See also https://github.com/Thefrank/dotnet-freebsd-crossbuild
run: curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v6.0.401-11/dotnet-sdk-6.0.401-freebsd-x64.tar.gz -o dn.tgz
- name: Run test
id: test
uses: vmactions/freebsd-vm@v0
with:
usesh: true
# sync: sshfs
prepare: pkg install -y curl libinotify libunwind git cmake ninja bash wget icu
run: |
#!/bin/bash
set -x
set -e
pwd
ls -lah
whoami
env
freebsd-version
df -h
dmesg | grep memory
swapinfo
dd if=/dev/zero of=/root/swap1 bs=1m count=20480
chmod 0600 /root/swap1
mdconfig -a -t vnode -f /root/swap1 -u 0
swapon /dev/md0
df -h
swapinfo
export DOTNET_CLI_TELEMETRY_OPTOUT=1
BUILD_DIR=`pwd`
cd /var/tmp
mkdir dn
cd dn
echo Unpacking .NET
tar -xzf ${BUILD_DIR}/dn.tgz
cd ${BUILD_DIR}
mkdir /var/tmp/nuget
export RVERSION=v6.0.401-11
export VERSION=6.0.9
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.AspNetCore.App.Runtime.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.AspNetCore.App.Runtime.freebsd-x64.${VERSION}.nupkg
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.NETCore.App.Host.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.NETCore.App.Host.freebsd-x64.${VERSION}.nupkg
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.NETCore.App.Runtime.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.NETCore.App.Runtime.freebsd-x64.${VERSION}.nupkg
curl -L https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/${RVERSION}/Microsoft.NETCore.App.Crossgen2.freebsd-x64.${VERSION}.nupkg -o /var/tmp/nuget/Microsoft.NETCore.App.Crossgen2.freebsd-x64.${VERSION}.nupkg
/var/tmp/dn/dotnet nuget add source /var/tmp/nuget/
cp -R `pwd` /var/tmp/build
cd /var/tmp/build
echo Restore
/var/tmp/dn/dotnet restore --runtime=freebsd-x64 Duplicati.sln
echo Testing
mkdir -p ${BUILD_DIR}/duplicati_testdata/logs
mkdir -p /var/tmp/duplicati_testdata
ln -s ${BUILD_DIR}/duplicati_testdata/logs /var/tmp/duplicati_testdata/logs
export UNITTEST_BASEFOLDER=/var/tmp/duplicati_testdata
/var/tmp/dn/dotnet test --runtime=freebsd-x64 --verbosity minimal Duplicati.sln
- name: Archive Test Logs
if: always()
uses: actions/upload-artifact@v2
with:
name: test-logs
path: duplicati_testdata/logs/*
+26
View File
@@ -0,0 +1,26 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
actions: write
steps:
- uses: actions/stale@v9
with:
days-before-issue-stale: 15
days-before-issue-close: 15
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 15 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 15 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
exempt-all-assignees: true
only-labels: "pending user feedback"
exempt-issue-labels: "bug,enhancement,good first issue,backend enhancement,backend issue,backup corruption,bounty,bugreport attached,core logic,docker,filters,help wanted,linux,localization,MacOS,mono,performance issue,reproduced,server side,ssl/tls issue,Synology,tests,translation,UI,windows"
repo-token: ${{ secrets.GITHUB_TOKEN }}
operations-per-run: 500
+18 -73
View File
@@ -1,9 +1,6 @@
name: Tests
on: [push, pull_request]
env:
build_directory: "Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release"
on: [pull_request, workflow_dispatch]
jobs:
unit_tests:
@@ -12,81 +9,29 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Add msbuild to PATH (Windows)
if: matrix.os == 'windows-latest'
uses: microsoft/setup-msbuild@v1.0.2
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
- name: Install NuGet
uses: nuget/setup-nuget@v1.0.5
- name: Checkout source
uses: actions/checkout@v4
- name: Checkout Source
uses: actions/checkout@v2.3.4
- name: Restore NuGet dependencies
run: dotnet restore Duplicati.sln
- name: Restore NuGet Packages
run: nuget restore Duplicati.sln
- name: Build Duplicati
run: dotnet build --no-restore Duplicati.sln
- name: Install Additional Dependencies
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get install gtk-sharp2
- name: Run unit tests
run: dotnet test --no-build --verbosity minimal Duplicati.sln
- name: Build Duplicati (Linux and macOS)
if: matrix.os != 'windows-latest'
run: msbuild -p:Configuration=Release -p:DefineConstants=ENABLE_GTK Duplicati.sln
- name: Build Duplicati (Windows)
if: matrix.os == 'windows-latest'
run: msbuild -p:Configuration=Release Duplicati.sln
- name: Run Unit Tests (Linux and macOS)
if: matrix.os != 'windows-latest'
run: |
nuget install NUnit.Runners -Version 3.12.0 -OutputDirectory nunit
mono nunit/NUnit.ConsoleRunner.3.12.0/tools/nunit3-console.exe Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll
- name: Run Unit Tests (Windows)
if: matrix.os == 'windows-latest'
run: |
nuget install NUnit.Runners -Version 3.12.0 -OutputDirectory nunit
nunit/NUnit.ConsoleRunner.3.12.0/tools/nunit3-console.exe Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll
ui_tests:
name: UI tests
selenium:
runs-on: ubuntu-latest
# Some of these steps duplicate those in the unit_tests job above. This
# can be cleaned up using a composite run steps action once it supports
# what we need (https://github.com/actions/runner/issues/646).
steps:
- name: Checkout Source
uses: actions/checkout@v2.3.4
- name: Install NuGet
uses: nuget/setup-nuget@v1.0.5
- name: Restore NuGet Packages
run: nuget restore Duplicati.sln
- name: Install Additional Dependencies
run: sudo apt-get install gtk-sharp2
- name: Build Duplicati
run: msbuild -p:Configuration=Release -p:DefineConstants=ENABLE_GTK Duplicati.sln
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.x
- name: Install dependencies
run: |
pip3 install selenium
pip3 install --upgrade urllib3
- name: Run UI tests
run: |
cp -r Duplicati/Server/webroot "${{ env.build_directory }}"/webroot
mono "${{ env.build_directory }}"/Duplicati.Server.exe &
python3 guiTests/guiTest.py
- uses: actions/checkout@v4
- name: Selenium
run: pipeline/selenium/test.sh
+6 -10
View File
@@ -4,24 +4,20 @@ obj/
*.userprefs
*.user
*.suo
Installer/Paraffin.exe
Installer/account_info
Duplicati/Localization/compiled
Duplicati/Localization/*/
Duplicati/Localization/*.diff.csv
Duplicati/Localization/*.new.csv
Duplicati/Localization/*.updated.csv
Duplicati/Localization/report.*.xml
Installer/incLocFiles.*.wxs
Duplicati/Server/webroot/extjs-*
Installer/binfiles.wxs
Installer/generated-keys.xml
Installer/oem
Updates/build
Duplicati/Server/webroot/*/node_modules/
packages
node_modules
changelog-news.txt
binfiles.wxs
.idea/
.vs/
Installer/debian/*.buildinfo
/publish/
ReleaseBuilder/build-temp/
ReleaseBuilder/Resources/Windows/TrayIcon/binfiles.wxs
ReleaseBuilder/Resources/Windows/Agent/binfiles.wxs
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "stylelint-config-standard-less"
}
-83
View File
@@ -1,83 +0,0 @@
language: minimal
services: docker
before_install:
- sudo apt-get install moreutils
- set -o pipefail
addons:
coverity_scan:
project:
name: "duplicati/duplicati"
description: "Duplicati Build submitted via Travis CI"
notification_email: kenneth@duplicati.com
build_command_prepend: "msbuild /t:Clean"
build_command: "msbuild /t:Rebuild"
branch_pattern: coverity_scan
env:
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "ijaQDcVcMo/utuP514FiDZVGR5LJbATWmYDmgRIIaI0YG2Rk6uuJp9XXDXr8sx66JWttPGgs7Htqfy50VKHdr7nsfyCQCRNGmqF+OJeUgAQMj5spxn9A7PZY9+GVhnXoJ06wvYjCv5MPeLBMDK/KpIM5Ny5IG+TKzQy4U7Dyg0riMUT61aqzxSjTGfHZPmaqBICJTzsRmeGHglemFUz/1B4NAN+IeAtXoH9YIfA2LjJfYmMLckPgFQLKN/tQRaGPWXh77y0qVgA++K8UVu5W3yGo3JXBPDdabGgKs9BPlfN5/Ixnkcy6ekAubDA/Aspur9x7/szj4/AnGMY6frNB33nQcnJQZ/51GYKr6CXCmjoJQuWc8ZKdEMqDn3w/kyEzvluSjAsJNRVboeWPE4VfpAeUBsjQ+TuEQi8WXhN8VJgZV7yCkVlZTVbhtOxMPsBiFb74lpWC2iQuWLJv9ED6vyXZVzZhNtfjXNXNZzIwYxbcd7I0ZQ8fmDstFnvBJveJby2ZRpECXTbc03eIUq1SAaVJJY2r47RD1PwVqM3xvUTljIqtKdyIz915mEOjqG1BnCDaiGHYJ4byuDDeIUZO9X70s60Gh4hupZA2Ruup0v7SfYgkto6aNcStd0NYd716Vd7hqXNTp6nu01L5YYJDY4h5sHyBXVXbxCKlUb01kUw="
- USAGEREPORTER_Duplicati_LEVEL=none
- AUTOUPDATER_Duplicati_SKIP_UPDATE=1
- BUILD_DIR=${TRAVIS_BUILD_DIR}/../.build
- TEST_DIR=${TRAVIS_BUILD_DIR}/../.test
- ROOT_DIR="$TRAVIS_BUILD_DIR"
# matrix expansion within jobs would be good:
# https://github.com/travis-ci/travis-ci/issues/8295
# There is one cache per branch, so concurrent builds on different branches possible!
# DO NOT SET ENVIRONMENT VARIABLES WITHIN JOBS, OR CACHES WILL NOT BE SHARED AMONGST JOBS!
jobs:
include:
- stage: build
cache:
directories:
- $BUILD_DIR
script:
- ${ROOT_DIR}/pipeline/jobs/build_job.sh
- stage: tests
cache:
directories:
- $BUILD_DIR
script:
- ${ROOT_DIR}/pipeline/jobs/unittest_job.sh --testcategories BulkNormal
- stage: tests
cache:
directories:
- $BUILD_DIR
script:
- ${ROOT_DIR}/pipeline/jobs/unittest_job.sh --testcategories BulkNoSize
- stage: tests
cache:
directories:
- $BUILD_DIR
script:
- ${ROOT_DIR}/pipeline/jobs/unittest_job.sh --testcategories SVNDataLong,SVNData,RecoveryTool,ProblematicPath,SymLink
- stage: tests
cache:
directories:
- $BUILD_DIR
script:
- travis_wait ${ROOT_DIR}/pipeline/jobs/unittest_job.sh --testcategories Border
- stage: tests
cache:
directories:
- $BUILD_DIR
script:
- ${ROOT_DIR}/pipeline/jobs/unittest_job.sh --testcategories Filter,Targeted,Purge,Serialization,WebApi,Utility,UriUtility,IO,ImportExport,Disruption,RestoreHandler,RepairHandler,DeleteHandler,Controller
- stage: tests
cache:
directories:
- $BUILD_DIR
script:
- ${ROOT_DIR}/pipeline/jobs/selenium_job.sh
addons:
sauce_connect:
username: $SAUCE_USERNAME
access_key: $SAUCE_ACCESS_KEY
+119
View File
@@ -0,0 +1,119 @@
{
// 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"
},
]
}
+24
View File
@@ -0,0 +1,24 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
// Ask dotnet build to generate full paths for file names.
"/property:GenerateFullPaths=true",
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{17E3EB29-87FD-4BE9-A531-8DAD8B295494}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<RootNamespace>AutoUpdateBuilder</RootNamespace>
<AssemblyName>AutoUpdateBuilder</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Program.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\..\Duplicati\Library\Compression\Duplicati.Library.Compression.csproj">
<Project>{19ECCE09-B5EB-406C-8C57-BAC66997D469}</Project>
<Name>Duplicati.Library.Compression</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj">
<Project>{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}</Project>
<Name>Duplicati.Library.Encryption</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Interface\Duplicati.Library.Interface.csproj">
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
<Name>Duplicati.Library.Interface</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Logging\Duplicati.Library.Logging.csproj">
<Project>{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}</Project>
<Name>Duplicati.Library.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj">
<Project>{7E119745-1F62-43F0-936C-F312A1912C0B}</Project>
<Name>Duplicati.Library.AutoUpdater</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\License\Duplicati.License.csproj">
<Project>{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}</Project>
<Name>Duplicati.License</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
</Project>
@@ -1,74 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoUpdateBuilder", "AutoUpdateBuilder.csproj", "{17E3EB29-87FD-4BE9-A531-8DAD8B295494}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Compression", "..\..\Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "..\..\Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Logging", "..\..\Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "..\..\Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.AutoUpdater", "..\..\Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj", "{7E119745-1F62-43F0-936C-F312A1912C0B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.License", "..\..\Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Localization", "..\..\Duplicati\Library\Localization\Duplicati.Library.Localization.csproj", "{B68F2214-951F-4F78-8488-66E1ED3F50BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Common", "..\..\Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17E3EB29-87FD-4BE9-A531-8DAD8B295494}.Release|Any CPU.Build.0 = Release|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.Build.0 = Release|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.Build.0 = Release|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Release|Any CPU.Build.0 = Release|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.Build.0 = Release|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Release|Any CPU.Build.0 = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.Build.0 = Release|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.Build.0 = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = AutoUpdateBuilder.csproj
EndGlobalSection
EndGlobal
-163
View File
@@ -1,163 +0,0 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace AutoUpdateBuilder
{
public class Program
{
private static RSACryptoServiceProvider privkey;
private static string keyfile;
private static string keyfilepassword;
private static void CompareToManifestPublicKey()
{
if (Duplicati.Library.AutoUpdater.AutoUpdateSettings.SignKey == null || privkey.ToXmlString(false) != Duplicati.Library.AutoUpdater.AutoUpdateSettings.SignKey.ToXmlString(false))
{
Console.WriteLine("The public key in the project is not the same as the public key from the file");
Console.WriteLine("Try setting the key to: ");
Console.WriteLine(privkey.ToXmlString(false));
System.Environment.Exit(5);
}
}
private static void LoadKeyFromFile()
{
using (var enc = new Duplicati.Library.Encryption.AESEncryption(keyfilepassword, new Dictionary<string, string>()))
using (var ms = new System.IO.MemoryStream())
using (var fs = System.IO.File.OpenRead(keyfile))
{
enc.Decrypt(fs, ms);
ms.Position = 0;
using (var sr = new System.IO.StreamReader(ms))
privkey.FromXmlString(sr.ReadToEnd());
}
}
public static int Main(string[] _args)
{
var args = new List<string>(_args);
var opts = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
opts.TryGetValue("input", out string inputfolder);
opts.TryGetValue("output", out string outputfolder);
opts.TryGetValue("allow-new-key", out string allowNewKey);
opts.TryGetValue("keyfile", out keyfile);
opts.TryGetValue("manifest", out string manifestfile);
opts.TryGetValue("keyfile-password", out keyfilepassword);
var usedoptions = new [] { "allow-new-key", "input", "output", "keyfile", "manifest", "keyfile-password" };
if (string.IsNullOrWhiteSpace(inputfolder))
{
Console.WriteLine("Missing input folder");
return 4;
}
if (string.IsNullOrWhiteSpace(outputfolder))
{
Console.WriteLine("Missing output folder");
return 4;
}
if (string.IsNullOrWhiteSpace(keyfile))
{
Console.WriteLine("Missing keyfile");
return 4;
}
if (!System.IO.Directory.Exists(inputfolder))
{
Console.WriteLine("Input folder not found");
return 4;
}
if (string.IsNullOrWhiteSpace(keyfilepassword))
{
Console.WriteLine("Enter keyfile passphrase: ");
keyfilepassword = Console.ReadLine().Trim();
}
if (!System.IO.File.Exists(keyfile))
{
Console.WriteLine("Keyfile not found, creating new");
var newkey = RSA.Create().ToXmlString(true);
using (var enc = new Duplicati.Library.Encryption.AESEncryption(keyfilepassword, new Dictionary<string, string>()))
using (var fs = System.IO.File.OpenWrite(keyfile))
using (var ms = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(newkey)))
enc.Encrypt(ms, fs);
}
if (!System.IO.Directory.Exists(outputfolder))
System.IO.Directory.CreateDirectory(outputfolder);
privkey = (RSACryptoServiceProvider) RSA.Create();
LoadKeyFromFile();
if (!Boolean.TryParse(allowNewKey, out Boolean newKeyAllowed) || !newKeyAllowed)
{
CompareToManifestPublicKey();
}
Duplicati.Library.AutoUpdater.UpdateInfo updateInfo;
using (var fs = System.IO.File.OpenRead(manifestfile))
using (var sr = new System.IO.StreamReader(fs))
using (var jr = new Newtonsoft.Json.JsonTextReader(sr))
updateInfo = new Newtonsoft.Json.JsonSerializer().Deserialize<Duplicati.Library.AutoUpdater.UpdateInfo>(jr);
var isopts = new Dictionary<string, string>(opts, StringComparer.InvariantCultureIgnoreCase);
foreach (var usedopt in usedoptions)
{
isopts.Remove(usedopt);
}
foreach (var k in updateInfo.GetType().GetFields())
{
if (!isopts.ContainsKey(k.Name))
{
continue;
}
try
{
//Console.WriteLine("Setting {0} to {1}", k.Name, isopts[k.Name]);
if (k.FieldType == typeof(string[]))
k.SetValue(updateInfo, isopts[k.Name].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries));
else if (k.FieldType == typeof(Version))
k.SetValue(updateInfo, new Version(isopts[k.Name]));
else if (k.FieldType == typeof(int))
k.SetValue(updateInfo, int.Parse(isopts[k.Name]));
else if (k.FieldType == typeof(long))
k.SetValue(updateInfo, long.Parse(isopts[k.Name]));
else
k.SetValue(updateInfo, isopts[k.Name]);
}
catch (Exception ex)
{
Console.WriteLine("Failed setting {0} to {1}: {2}", k.Name, isopts[k.Name], ex.Message);
}
isopts.Remove(k.Name);
}
foreach (var opt in isopts)
{
Console.WriteLine("Warning! unused option: {0} = {1}", opt.Key, opt.Value);
}
using (var tf = new Duplicati.Library.Utility.TempFile())
{
using (var fs = System.IO.File.OpenWrite(tf))
using (var tw = new System.IO.StreamWriter(fs))
new Newtonsoft.Json.JsonSerializer().Serialize(tw, updateInfo);
Duplicati.Library.AutoUpdater.UpdaterManager.CreateUpdatePackage(privkey, inputfolder, outputfolder, tf);
}
return 0;
}
}
}
@@ -1,27 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("AutoUpdateBuilder")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("2.0.0.7")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
-3
View File
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/></startup></configuration>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net471" />
</packages>
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{3E891818-579A-4C06-8066-4C702BFA5921}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<RootNamespace>DependencyFinder</RootNamespace>
<AssemblyName>DependencyFinder</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<ConsolePause>false</ConsolePause>
<Commandlineparameters>../../../../Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Debug</Commandlineparameters>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Mdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Pdb, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll</HintPath>
</Reference>
<Reference Include="Mono.Cecil.Rocks, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll</HintPath>
</Reference>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
</Project>
@@ -1,17 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DependencyFinder", "DependencyFinder.csproj", "{3E891818-579A-4C06-8066-4C702BFA5921}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3E891818-579A-4C06-8066-4C702BFA5921}.Debug|x86.ActiveCfg = Debug|x86
{3E891818-579A-4C06-8066-4C702BFA5921}.Debug|x86.Build.0 = Debug|x86
{3E891818-579A-4C06-8066-4C702BFA5921}.Release|x86.ActiveCfg = Release|x86
{3E891818-579A-4C06-8066-4C702BFA5921}.Release|x86.Build.0 = Release|x86
EndGlobalSection
EndGlobal
-64
View File
@@ -1,64 +0,0 @@
using System;
using System.Collections.Generic;
using Mono.Cecil;
using System.IO;
using System.Linq;
namespace DependencyFinder
{
class MainClass
{
public static void Main(string[] args)
{
var lst =
Directory.EnumerateFiles(args[0], "*.exe", SearchOption.AllDirectories).Union(
Directory.EnumerateFiles(args[0], "*.dll", SearchOption.AllDirectories))
.Select(x => {
try { return AssemblyDefinition.ReadAssembly(x); }
catch { return null; }
}).Where(x => x != null)
.Distinct();
PoC(lst, Console.Out, new [] { args[0] });
}
//Adapted from: https://stackoverflow.com/questions/9262464/tool-to-show-assembly-dependencies
public static void PoC(IEnumerable<AssemblyDefinition> assemblies, TextWriter writer, IEnumerable<string> searchfolders)
{
var resolver = new DefaultAssemblyResolver();
searchfolders.ToList().ForEach(x => resolver.AddSearchDirectory(x));
//writer.WriteLine("digraph Dependencies {");
var loaded = assemblies
.SelectMany(a => a.Modules.Cast<ModuleDefinition>())
.SelectMany(m => m.AssemblyReferences.Cast<AssemblyNameReference>())
.Distinct()
.Select(asm => {
var dllname = asm.Name + ".dll";
//Console.WriteLine("Probing for {0}", dllname);
try { return AssemblyDefinition.ReadAssembly(dllname); }
catch { }
try { return resolver.Resolve(asm.FullName); }
catch { }
return null;
})
.Where(assembly => assembly != null)
.ToList();
//loaded.ForEach(a => a.MainModule.ReadSymbols());
loaded.Select(x => x.FullName).Distinct().OrderBy(x => x).ToList().ForEach(x => writer.WriteLine("{0}", x));
/*loaded.ForEach(a =>
{
foreach (var r in a.MainModule.AssemblyReferences.Cast<AssemblyNameReference>())
writer.WriteLine(@"""{0}"" -> ""{1}"";", r.Name, a.Name.Name);
} );*/
//writer.WriteLine("}");
}
}
}
@@ -1,27 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("DependencyFinder")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("kenneth")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("2.0.0.7")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
-3
View File
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/></startup></configuration>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net471" />
</packages>
@@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{72B910F2-8E80-4955-A0D6-ED4C35CEE665}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>GnupgSigningTool</RootNamespace>
<AssemblyName>GnupgSigningTool</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup>
<StartupObject>
</StartupObject>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj">
<Project>{2cf2d90e-c25b-47ad-91e0-98451bab8058}</Project>
<Name>Duplicati.Library.Encryption</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Interface\Duplicati.Library.Interface.csproj">
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
<Name>Duplicati.Library.Interface</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
@@ -1,42 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29123.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GnupgSigningTool", "GnupgSigningTool.csproj", "{72B910F2-8E80-4955-A0D6-ED4C35CEE665}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "..\..\Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{2CF2D90E-C25B-47AD-91E0-98451BAB8058}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "..\..\Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{72B910F2-8E80-4955-A0D6-ED4C35CEE665}.Debug|x86.ActiveCfg = Debug|x86
{72B910F2-8E80-4955-A0D6-ED4C35CEE665}.Debug|x86.Build.0 = Debug|x86
{72B910F2-8E80-4955-A0D6-ED4C35CEE665}.Release|x86.ActiveCfg = Release|x86
{72B910F2-8E80-4955-A0D6-ED4C35CEE665}.Release|x86.Build.0 = Release|x86
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.ActiveCfg = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|x86.Build.0 = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.ActiveCfg = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|x86.Build.0 = Release|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Debug|x86.ActiveCfg = Debug|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Debug|x86.Build.0 = Debug|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Release|x86.ActiveCfg = Release|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Release|x86.Build.0 = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|x86.ActiveCfg = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|x86.Build.0 = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|x86.ActiveCfg = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {71CC6B1F-7F52-4059-8761-30B3D0501ECC}
EndGlobalSection
EndGlobal
-107
View File
@@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
namespace GnupgSigningTool
{
public static class Program
{
private static string keyfilepassword;
private static string gpgkeypassphrase;
private static string gpgkeyfile;
private static string gpgpath;
private static string gpgkeyid;
private static bool useArmor;
private static string inputFile;
private static string signatureFile;
private static void SpawnGPG()
{
var armorOption = useArmor ? "--armor" : "";
var gpgArgument = string.Format("--pinentry-mode loopback --passphrase-fd 0 --batch --yes {0} -u \"{1}\" --output \"{2}\" --detach-sig \"{3}\"",
armorOption,
gpgkeyid,
signatureFile,
inputFile);
var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = gpgpath,
Arguments = gpgArgument,
RedirectStandardInput = true,
UseShellExecute = false
});
proc.StandardInput.WriteLine(gpgkeypassphrase);
proc.WaitForExit();
}
private static void LoadGPGKeyIdAndPassphrase()
{
using (var enc = new Duplicati.Library.Encryption.AESEncryption(keyfilepassword, new Dictionary<string, string>()))
using (var ms = new System.IO.MemoryStream())
using (var fs = System.IO.File.OpenRead(gpgkeyfile))
{
try
{
enc.Decrypt(fs, ms);
}
catch (System.Security.Cryptography.CryptographicException e)
{
throw new ArgumentException("Failed to decrypt gpg secret credentials file: {0}\n", e.Message);
}
ms.Position = 0;
using (var sr = new System.IO.StreamReader(ms))
{
var lines = sr.ReadToEnd().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
gpgkeyid = lines[0];
gpgkeypassphrase = lines[1];
}
}
}
public static void Main(string[] _args)
{
var args = new List<string>(_args);
var opts = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
opts.TryGetValue("inputfile", out inputFile);
opts.TryGetValue("signaturefile", out signatureFile);
opts.TryGetValue("keyfile-password", out keyfilepassword);
opts.TryGetValue("gpgkeyfile", out gpgkeyfile);
opts.TryGetValue("gpgpath", out gpgpath);
opts.TryGetValue("armor", out string armor);
useArmor = Boolean.TryParse(armor, out useArmor) && useArmor;
if (string.IsNullOrWhiteSpace(gpgkeyfile))
{
throw new ArgumentException("No gpgfile with encrypted credentials specified.");
}
if (!System.IO.File.Exists(gpgkeyfile))
{
throw new ArgumentException("Specified file with encrypted gpg credentials not found.");
}
LoadGPGKeyIdAndPassphrase();
if (gpgkeyid is null || gpgkeypassphrase is null)
{
throw new ArgumentException("Could not fetch gpg key id or gpg passphrase.");
}
gpgpath = gpgpath ?? Duplicati.Library.Encryption.GPGEncryption.GetGpgProgramPath();
SpawnGPG();
}
}
}
-3
View File
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/></startup></configuration>
+84
View File
@@ -0,0 +1,84 @@
// 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.Text.RegularExpressions;
namespace LicenseUpdater;
public static class Fragments
{
public static string NEW_LICENSE = @"Copyright (C) YYYY, The Duplicati Team
https://duplicati.com, hello@duplicati.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the ""Software""),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.".Replace("YYYY", DateTime.UtcNow.Year.ToString());
public static RegexOptions RE_OPTS = RegexOptions.Singleline | RegexOptions.IgnoreCase;
public static Regex CS_REGION_MATCH = new Regex(@"(?<license>#region Disclaimer / License(?<licensebody>.+(?:Free Software Foundation)[^#]+)#endregion)", RE_OPTS);
public static Regex CS_LGPL_MATCH = new Regex(@"(?<license>//\s+Copyright \(C\) \d{4}, The Duplicati Team.+(?:Free Software Foundation).+Boston,\s+MA\s+0211\d-130\d\s+USA)", RE_OPTS);
public static Regex CS_MIT_MATCH = new Regex(@"(?<license>//\s+Copyright \(C\) \d{4}, The Duplicati Team.+(?:copyright notice and this permission notice).+IN THE SOFTWARE\.)", RE_OPTS);
public static string GetLicenseTextWithPrefixedLines(string linePrefix = "// ")
=> string.Join("\n", Fragments.NEW_LICENSE.Split("\n").Select(x => linePrefix + x).Append(string.Empty));
public static bool MatchAndReplace(ref string data)
{
foreach (var m in new[] { CS_REGION_MATCH, CS_MIT_MATCH })
{
var match = m.Match(data);
if (match.Success && match.Groups["license"].Success)
{
var g = match.Groups["license"];
data = string.Join(
string.Empty,
data.Substring(0, g.Index),
GetLicenseTextWithPrefixedLines(),
data.Substring(g.Index + g.Length)
);
return true;
}
}
return false;
}
}
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>license_upgrader</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
</Project>
+25
View File
@@ -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}") = "LicenseUpdater", "LicenseUpdater.csproj", "{37205C47-20D1-4E7B-9AFB-B833C7FC949F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{37205C47-20D1-4E7B-9AFB-B833C7FC949F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37205C47-20D1-4E7B-9AFB-B833C7FC949F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37205C47-20D1-4E7B-9AFB-B833C7FC949F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37205C47-20D1-4E7B-9AFB-B833C7FC949F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F9144130-7643-4C0E-9545-ECEF7EEF0423}
EndGlobalSection
EndGlobal
+79
View File
@@ -0,0 +1,79 @@
// 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 LicenseUpdater;
var cmdargs = Environment.GetCommandLineArgs();
if (cmdargs.Length != 2)
throw new Exception($"Usage: dotnet run <path-to-source-folder>");
var startpath = Path.GetFullPath(Environment.GetCommandLineArgs().Skip(1).First());
if (!Directory.Exists(startpath))
throw new Exception($"Start path not found: {startpath}");
var target_extensions = new[] {
".cs"
// ".html",
// ".js",
// ".css"
}.ToHashSet(StringComparer.OrdinalIgnoreCase);
File.WriteAllText(Path.Combine(startpath, "LICENSE.txt"), Fragments.GetLicenseTextWithPrefixedLines(string.Empty));
var candidates = Directory.EnumerateFiles(startpath, "*", SearchOption.AllDirectories)
.Where(x => target_extensions.Contains(Path.GetExtension(x) ?? string.Empty));
var blacklistSubFolders = new[] { "bin", "obj", "packages" };
foreach (var file in candidates)
{
if (string.Equals("AssemblyInfo.cs", Path.GetFileName(file), StringComparison.OrdinalIgnoreCase))
continue;
if (blacklistSubFolders.Any(x => file.Contains($"{Path.DirectorySeparatorChar}{x}{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase)))
continue;
if (new FileInfo(file).Length > 10 * 1024 * 1024)
{
Console.WriteLine($"{file} skipped due to size");
continue;
}
var data = File.ReadAllText(file);
if (Fragments.MatchAndReplace(ref data))
{
File.WriteAllText(file, data);
// Console.WriteLine($"{file} updated!");
continue;
}
if (string.Equals(Path.GetExtension(file), ".cs", StringComparison.OrdinalIgnoreCase) && !data.StartsWith("// Copyright", StringComparison.OrdinalIgnoreCase))
{
data = Fragments.GetLicenseTextWithPrefixedLines() + data;
File.WriteAllText(file, data);
// Console.WriteLine($"{file} updated!");
continue;
}
Console.WriteLine($"{file} skipped, no match");
}
+9
View File
@@ -0,0 +1,9 @@
# License Updater
This folder contains a small tool that can update the license in all files, using the current year as the copyright year. It is intended to be run once a year, using the root repository folder as input:
```
dotnet run ../../
```
The resulting change can then be reviewed and committed.
-110
View File
@@ -1,110 +0,0 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace UpdateVersionStamp
{
public static class Program
{
private static readonly string DIR_SEP = Path.DirectorySeparatorChar.ToString();
private static readonly Dictionary<string, Regex> FILEMAP;
static Program()
{
var versionre = @"(?<version>\d+\.\d+\.(\*|(\d+(\.(\*|\d+)))?))";
FILEMAP = new Dictionary<string, Regex>(StringComparer.InvariantCultureIgnoreCase);
FILEMAP.Add("AssemblyInfo.cs", new Regex(@"(\[assembly\: AssemblyVersion\(\""" + versionre + @"""\)\])|(\[assembly\: AssemblyFileVersion\(\""" + versionre + @"\""\)\])|(\[assembly\: AssemblyFileVersionAttribute\(\""" + versionre + @"\""\)\])"));
FILEMAP.Add("UpgradeData.wxi", new Regex(@"\<\?define ProductVersion\=\""" + versionre + @"\"" \?\>"));
FILEMAP.Add("AssemblyRedirects.xml", new Regex(@"newVersion\=\""" + versionre + @"\"""));
FILEMAP.Add("index.html", new Regex(@"\?v\=" + versionre));
FILEMAP.Add("login.html", new Regex(@"\?v\=" + versionre));
FILEMAP.Add("app.js", new Regex(@"\?v\=" + versionre));
}
private class Options
{
public string sourcefolder = Path.GetFullPath(Environment.CurrentDirectory);
public string ignorefilter = null;
public string version = null;
public string versiontag = null;
public void Fixup()
{
sourcefolder = Duplicati.Library.Common.IO.Util.AppendDirSeparator(System.IO.Path.GetFullPath(sourcefolder.Replace("/", DIR_SEP)));
if (ignorefilter != null)
ignorefilter =ignorefilter.Replace("/", DIR_SEP);
}
}
public static void Main(string[] _args)
{
List<string> args = new List<string>(_args);
Dictionary<string, string> options = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args);
Options opt = new Options();
foreach (FieldInfo fi in opt.GetType().GetFields())
if (options.ContainsKey(fi.Name))
fi.SetValue(opt, options[fi.Name]);
opt.Fixup();
Duplicati.Library.Utility.IFilter filter = null;
if (!string.IsNullOrEmpty(opt.ignorefilter))
filter = new Duplicati.Library.Utility.FilterExpression(opt.ignorefilter, false);
Func<string, bool> isFile = (string x) => !x.EndsWith(DIR_SEP);
var paths = Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(opt.sourcefolder)
.Where(x => Duplicati.Library.Utility.FilterExpression.Matches(filter, x))
.Where(x => isFile(x) && FILEMAP.ContainsKey(Path.GetFileName(x)))
.Select(x =>
{
var m = FILEMAP[Path.GetFileName(x)].Match(File.ReadAllText(x));
return m.Success ?
new { File = x, Version = new Version(m.Groups["version"].Value.Replace("*", "0")), Display = m.Groups["version"].Value }
: null;
})
.Where(x => x != null)
.ToArray(); //No need to re-eval
if (paths.Count() == 0)
{
Console.WriteLine("No files found to update...");
return;
}
foreach (var p in paths)
Console.WriteLine("{0}\t:{1}", p.Display, p.File);
if (string.IsNullOrWhiteSpace(opt.version))
{
var maxv = paths.Select(x => x.Version).Max();
opt.version = new Version(
maxv.Major,
maxv.Minor,
maxv.Build,
maxv.Revision).ToString();
}
//Sanity check
var nv = new Version(opt.version).ToString(4);
foreach (var p in paths)
{
var re = FILEMAP[Path.GetFileName(p.File)];
var txt = File.ReadAllText(p.File);
//var m = re.Match(txt).Groups["version"];
txt = re.Replace(txt, (m) => {
var t = m.Groups["version"];
return m.Value.Replace(t.Value, nv);
});
File.WriteAllText(p.File, txt);
}
Console.WriteLine("Updated {0} files to version {1}", paths.Count(), opt.version);
}
}
}
@@ -1,22 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("UpdateVersionStamp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("2.0.0.7")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{1920C3B0-98A0-410F-8972-9C19FA616FE5}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<RootNamespace>UpdateVersionStamp</RootNamespace>
<AssemblyName>UpdateVersionStamp</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<Commandlineparameters>--sourcefolder=../.. --version=2.0.0.0 --version-tag=20140405</Commandlineparameters>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Duplicati\Library\Common\Duplicati.Library.Common.csproj">
<Project>{D63E53E4-A458-4C2F-914D-92F715F58ACF}</Project>
<Name>Duplicati.Library.Common</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
</Project>
@@ -1,35 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateVersionStamp", "UpdateVersionStamp.csproj", "{1920C3B0-98A0-410F-8972-9C19FA616FE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Common", "..\..\Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1920C3B0-98A0-410F-8972-9C19FA616FE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1920C3B0-98A0-410F-8972-9C19FA616FE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1920C3B0-98A0-410F-8972-9C19FA616FE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1920C3B0-98A0-410F-8972-9C19FA616FE5}.Release|Any CPU.Build.0 = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = UpdateVersionStamp.csproj
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
-3
View File
@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/></startup></configuration>
-324
View File
@@ -1,324 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Main", "Duplicati\Library\Main\Duplicati.Library.Main.csproj", "{10D2D1B7-C664-41D8-9B3A-00040C3D421B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.File", "Duplicati\Library\Backend\File\Duplicati.Library.Backend.File.csproj", "{FC9B7611-836F-4127-8B44-A7C31F506807}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Compression", "Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Logging", "Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine", "Duplicati\CommandLine\Duplicati.CommandLine.csproj", "{81765A64-3661-4E3E-B850-2F6F87A51F74}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.DynamicLoader", "Duplicati\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj", "{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Snapshots", "Duplicati\Library\Snapshots\Duplicati.Library.Snapshots.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Modules.Builtin", "Duplicati\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj", "{52826615-7964-47FE-B4B3-1B2DBDF605B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.License", "Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SSHv2", "Duplicati\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj", "{FF2BF37C-E502-4C98-BEA0-701671DDFA08}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.WEBDAV", "Duplicati\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj", "{BAE27510-8B5D-44B2-B33E-372A98908041}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.OneDrive", "Duplicati\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj", "{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.TahoeLAFS", "Duplicati\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj", "{C0270709-2A40-43B5-8CF1-69581B9FA2A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.CloudFiles", "Duplicati\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj", "{1BFAE226-8364-4086-825C-BB83F6F3EE4C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.FTP", "Duplicati\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj", "{F61679A9-E5DE-468A-B5A4-05F92D0143D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.S3", "Duplicati\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj", "{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.AzureBlob", "Duplicati\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj", "{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Localization", "Duplicati\Library\Localization\Duplicati.Library.Localization.csproj", "{B68F2214-951F-4F78-8488-66E1ED3F50BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.AutoUpdater", "Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj", "{7E119745-1F62-43F0-936C-F312A1912C0B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SQLiteHelper", "Duplicati\Library\SQLiteHelper\Duplicati.Library.SQLiteHelper.csproj", "{2C838169-B187-4B09-8768-1C24C2521C8D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.UsageReporter", "Duplicati\Library\UsageReporter\Duplicati.Library.UsageReporter.csproj", "{BB014EA5-CE2C-4444-8D30-38983A0E8553}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.OAuthHelper", "Duplicati\Library\Backend\OAuthHelper\Duplicati.Library.OAuthHelper.csproj", "{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTool", "Duplicati\CommandLine\BackendTool\Duplicati.CommandLine.BackendTool.csproj", "{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTester", "Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{E7280DCA-7776-4A73-B9B5-41FD77FC8799}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.GoogleServices", "Duplicati\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj", "{5489181D-950C-44AF-873C-45EB0A3B6BD2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.OpenStack", "Duplicati\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj", "{D9E4E686-423C-48EC-A392-404E7C00860C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.HubiC", "Duplicati\Library\Backend\HubiC\Duplicati.Library.Backend.HubiC.csproj", "{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.RecoveryTool", "Duplicati\CommandLine\RecoveryTool\Duplicati.CommandLine.RecoveryTool.csproj", "{4A010589-76E6-4F05-A5C4-4598D5DF11F8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Box", "Duplicati\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj", "{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.AlternativeFTP", "Duplicati\Library\Backend\AlternativeFTP\Duplicati.Library.Backend.AlternativeFTP.csproj", "{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Backblaze", "Duplicati\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj", "{61C43D61-4368-4942-84A3-1EB623F4EF2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Dropbox", "Duplicati\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj", "{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Mega", "Duplicati\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj", "{6643A5AE-AB38-453F-ADCE-408E35A81A83}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SharePoint", "Duplicati\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj", "{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Tools", "Duplicati\Tools\Duplicati.Tools.csproj", "{0797AA22-C5DD-4950-BB60-34765AB8C6DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Sia", "Duplicati\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj", "{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Common", "Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Jottacloud", "Duplicati\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj", "{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Rclone", "Duplicati\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj", "{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}.Release|Any CPU.Build.0 = Release|Any CPU
{10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10D2D1B7-C664-41D8-9B3A-00040C3D421B}.Release|Any CPU.Build.0 = Release|Any CPU
{FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC9B7611-836F-4127-8B44-A7C31F506807}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC9B7611-836F-4127-8B44-A7C31F506807}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC9B7611-836F-4127-8B44-A7C31F506807}.Release|Any CPU.Build.0 = Release|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19ECCE09-B5EB-406C-8C57-BAC66997D469}.Release|Any CPU.Build.0 = Release|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94484FDB-2EFA-4CF0-9BE6-A561157B4F87}.Release|Any CPU.Build.0 = Release|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}.Release|Any CPU.Build.0 = Release|Any CPU
{81765A64-3661-4E3E-B850-2F6F87A51F74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81765A64-3661-4E3E-B850-2F6F87A51F74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81765A64-3661-4E3E-B850-2F6F87A51F74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81765A64-3661-4E3E-B850-2F6F87A51F74}.Release|Any CPU.Build.0 = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5899F45-B0FF-483C-9D38-24A9FCAAB237}.Release|Any CPU.Build.0 = Release|Any CPU
{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}.Release|Any CPU.Build.0 = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACE}.Release|Any CPU.Build.0 = Release|Any CPU
{52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{52826615-7964-47FE-B4B3-1B2DBDF605B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52826615-7964-47FE-B4B3-1B2DBDF605B9}.Release|Any CPU.Build.0 = Release|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}.Release|Any CPU.Build.0 = Release|Any CPU
{FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF2BF37C-E502-4C98-BEA0-701671DDFA08}.Release|Any CPU.Build.0 = Release|Any CPU
{BAE27510-8B5D-44B2-B33E-372A98908041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAE27510-8B5D-44B2-B33E-372A98908041}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAE27510-8B5D-44B2-B33E-372A98908041}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BAE27510-8B5D-44B2-B33E-372A98908041}.Release|Any CPU.Build.0 = Release|Any CPU
{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}.Release|Any CPU.Build.0 = Release|Any CPU
{C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0270709-2A40-43B5-8CF1-69581B9FA2A1}.Release|Any CPU.Build.0 = Release|Any CPU
{1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1BFAE226-8364-4086-825C-BB83F6F3EE4C}.Release|Any CPU.Build.0 = Release|Any CPU
{F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F61679A9-E5DE-468A-B5A4-05F92D0143D2}.Release|Any CPU.Build.0 = Release|Any CPU
{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}.Release|Any CPU.Build.0 = Release|Any CPU
{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}.Release|Any CPU.Build.0 = Release|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B68F2214-951F-4F78-8488-66E1ED3F50BF}.Release|Any CPU.Build.0 = Release|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E119745-1F62-43F0-936C-F312A1912C0B}.Release|Any CPU.Build.0 = Release|Any CPU
{2C838169-B187-4B09-8768-1C24C2521C8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C838169-B187-4B09-8768-1C24C2521C8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C838169-B187-4B09-8768-1C24C2521C8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C838169-B187-4B09-8768-1C24C2521C8D}.Release|Any CPU.Build.0 = Release|Any CPU
{BB014EA5-CE2C-4444-8D30-38983A0E8553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB014EA5-CE2C-4444-8D30-38983A0E8553}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB014EA5-CE2C-4444-8D30-38983A0E8553}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB014EA5-CE2C-4444-8D30-38983A0E8553}.Release|Any CPU.Build.0 = Release|Any CPU
{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}.Release|Any CPU.Build.0 = Release|Any CPU
{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}.Release|Any CPU.Build.0 = Release|Any CPU
{E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7280DCA-7776-4A73-B9B5-41FD77FC8799}.Release|Any CPU.Build.0 = Release|Any CPU
{5489181D-950C-44AF-873C-45EB0A3B6BD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5489181D-950C-44AF-873C-45EB0A3B6BD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5489181D-950C-44AF-873C-45EB0A3B6BD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5489181D-950C-44AF-873C-45EB0A3B6BD2}.Release|Any CPU.Build.0 = Release|Any CPU
{D9E4E686-423C-48EC-A392-404E7C00860C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9E4E686-423C-48EC-A392-404E7C00860C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9E4E686-423C-48EC-A392-404E7C00860C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9E4E686-423C-48EC-A392-404E7C00860C}.Release|Any CPU.Build.0 = Release|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Release|Any CPU.Build.0 = Release|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Release|Any CPU.Build.0 = Release|Any CPU
{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}.Release|Any CPU.Build.0 = Release|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Release|Any CPU.Build.0 = Release|Any CPU
{61C43D61-4368-4942-84A3-1EB623F4EF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61C43D61-4368-4942-84A3-1EB623F4EF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61C43D61-4368-4942-84A3-1EB623F4EF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61C43D61-4368-4942-84A3-1EB623F4EF2A}.Release|Any CPU.Build.0 = Release|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Release|Any CPU.Build.0 = Release|Any CPU
{6643A5AE-AB38-453F-ADCE-408E35A81A83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6643A5AE-AB38-453F-ADCE-408E35A81A83}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6643A5AE-AB38-453F-ADCE-408E35A81A83}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6643A5AE-AB38-453F-ADCE-408E35A81A83}.Release|Any CPU.Build.0 = Release|Any CPU
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Release|Any CPU.Build.0 = Release|Any CPU
{0797AA22-C5DD-4950-BB60-34765AB8C6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0797AA22-C5DD-4950-BB60-34765AB8C6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0797AA22-C5DD-4950-BB60-34765AB8C6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0797AA22-C5DD-4950-BB60-34765AB8C6DD}.Release|Any CPU.Build.0 = Release|Any CPU
{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Release|Any CPU.Build.0 = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.Build.0 = Release|Any CPU
{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Release|Any CPU.Build.0 = Release|Any CPU
{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3221EB9F-EE08-460E-B236-CD594688FC5F}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = Duplicati\CommandLine\Duplicati.CommandLine.csproj
Policies = $0
$0.StandardHeader = $1
$1.Text = @ Copyright (C) 2013, The Duplicati Team\r\n http://www.duplicati.com, info@duplicati.com\n \n This library is free software; you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as\n published by the Free Software Foundation; either version 2.1 of the\n License, or (at your option) any later version.\n\n This library is distributed in the hope that it will be useful, but\n WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public\n License along with this library; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
$1.IncludeInNewFiles = True
$0.DotNetNamingPolicy = $2
$2.DirectoryNamespaceAssociation = PrefixedHierarchical
$2.ResourceNamePolicy = FileFormatDefault
$0.TextStylePolicy = $5
$3.inheritsSet = VisualStudio
$3.scope = text/plain
$3.FileWidth = 120
$3.EolMarker = Windows
$3.inheritsScope = text/plain
$0.CSharpFormattingPolicy = $4
$4.IndentSwitchBody = True
$4.AnonymousMethodBraceStyle = NextLine
$4.PropertyBraceStyle = NextLine
$4.PropertyGetBraceStyle = NextLine
$4.PropertySetBraceStyle = NextLine
$4.EventBraceStyle = NextLine
$4.EventAddBraceStyle = NextLine
$4.EventRemoveBraceStyle = NextLine
$4.StatementBraceStyle = NextLine
$4.ElseNewLinePlacement = NewLine
$4.ElseIfNewLinePlacement = SameLine
$4.CatchNewLinePlacement = NewLine
$4.FinallyNewLinePlacement = NewLine
$4.BeforeMethodDeclarationParentheses = False
$4.BeforeMethodCallParentheses = False
$4.BeforeConstructorDeclarationParentheses = False
$4.BeforeIndexerDeclarationBracket = False
$4.BeforeDelegateDeclarationParentheses = False
$4.NewParentheses = False
$4.ForParentheses = False
$4.ForeachParentheses = False
$4.LockParentheses = False
$4.UsingParentheses = False
$4.SpacesBeforeBrackets = False
$4.inheritsSet = Mono
$4.inheritsScope = text/x-csharp
$4.scope = text/x-csharp
$5.FileWidth = 120
$5.EolMarker = Windows
$5.inheritsSet = VisualStudio
$5.inheritsScope = text/plain
$5.scope = text/plain
EndGlobalSection
GlobalSection(SubversionScc) = preSolution
Svn-Managed = True
Manager = AnkhSVN - Subversion Support for Visual Studio
EndGlobalSection
EndGlobal
+280 -83
View File
@@ -1,111 +1,168 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29102.190
# Visual Studio Version 17
VisualStudioVersion = 17.1.32210.238
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Utility", "Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Main", "Duplicati\Library\Main\Duplicati.Library.Main.csproj", "{10D2D1B7-C664-41D8-9B3A-00040C3D421B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Main", "Duplicati\Library\Main\Duplicati.Library.Main.csproj", "{10D2D1B7-C664-41D8-9B3A-00040C3D421B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.File", "Duplicati\Library\Backend\File\Duplicati.Library.Backend.File.csproj", "{FC9B7611-836F-4127-8B44-A7C31F506807}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.File", "Duplicati\Library\Backend\File\Duplicati.Library.Backend.File.csproj", "{FC9B7611-836F-4127-8B44-A7C31F506807}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.FTP", "Duplicati\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj", "{F61679A9-E5DE-468A-B5A4-05F92D0143D2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.FTP", "Duplicati\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj", "{F61679A9-E5DE-468A-B5A4-05F92D0143D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Compression", "Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Compression", "Duplicati\Library\Compression\Duplicati.Library.Compression.csproj", "{19ECCE09-B5EB-406C-8C57-BAC66997D469}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Logging", "Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Logging", "Duplicati\Library\Logging\Duplicati.Library.Logging.csproj", "{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.S3", "Duplicati\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj", "{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.S3", "Duplicati\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj", "{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine", "Duplicati\CommandLine\Duplicati.CommandLine.csproj", "{81765A64-3661-4E3E-B850-2F6F87A51F74}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine", "Duplicati\CommandLine\CLI\Duplicati.CommandLine.csproj", "{81765A64-3661-4E3E-B850-2F6F87A51F74}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.WEBDAV", "Duplicati\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj", "{BAE27510-8B5D-44B2-B33E-372A98908041}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.WEBDAV", "Duplicati\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj", "{BAE27510-8B5D-44B2-B33E-372A98908041}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.CloudFiles", "Duplicati\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj", "{1BFAE226-8364-4086-825C-BB83F6F3EE4C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.CloudFiles", "Duplicati\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj", "{1BFAE226-8364-4086-825C-BB83F6F3EE4C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTester", "Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{E7280DCA-7776-4A73-B9B5-41FD77FC8799}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.BackendTester", "Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{E7280DCA-7776-4A73-B9B5-41FD77FC8799}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Interface", "Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Interface", "Duplicati\Library\Interface\Duplicati.Library.Interface.csproj", "{C5899F45-B0FF-483C-9D38-24A9FCAAB237}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.DynamicLoader", "Duplicati\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj", "{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.DynamicLoader", "Duplicati\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj", "{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Snapshots", "Duplicati\Library\Snapshots\Duplicati.Library.Snapshots.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Snapshots", "Duplicati\Library\Snapshots\Duplicati.Library.Snapshots.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.License", "Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.License", "Duplicati\License\Duplicati.License.csproj", "{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Modules.Builtin", "Duplicati\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj", "{52826615-7964-47FE-B4B3-1B2DBDF605B9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Modules.Builtin", "Duplicati\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj", "{52826615-7964-47FE-B4B3-1B2DBDF605B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.TahoeLAFS", "Duplicati\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj", "{C0270709-2A40-43B5-8CF1-69581B9FA2A1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.TahoeLAFS", "Duplicati\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj", "{C0270709-2A40-43B5-8CF1-69581B9FA2A1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.OneDrive", "Duplicati\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj", "{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.OneDrive", "Duplicati\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj", "{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SSHv2", "Duplicati\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj", "{FF2BF37C-E502-4C98-BEA0-701671DDFA08}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.SSHv2", "Duplicati\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj", "{FF2BF37C-E502-4C98-BEA0-701671DDFA08}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Server", "Duplicati\Server\Duplicati.Server.csproj", "{19E661D2-C5DA-4F35-B3EE-7586E5734B5F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Server", "Duplicati\Server\Duplicati.Server.csproj", "{19E661D2-C5DA-4F35-B3EE-7586E5734B5F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.GUI.TrayIcon", "Duplicati\GUI\Duplicati.GUI.TrayIcon\Duplicati.GUI.TrayIcon.csproj", "{17566860-3D98-4604-AA5B-47661F75609F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.GUI.TrayIcon", "Duplicati\GUI\Duplicati.GUI.TrayIcon\Duplicati.GUI.TrayIcon.csproj", "{17566860-3D98-4604-AA5B-47661F75609F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Server.Serialization", "Duplicati\Server\Duplicati.Server.Serialization\Duplicati.Server.Serialization.csproj", "{33FD1D24-C28F-4C71-933F-98F1586EA76C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Server.Serialization", "Duplicati\Server\Duplicati.Server.Serialization\Duplicati.Server.Serialization.csproj", "{33FD1D24-C28F-4C71-933F-98F1586EA76C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTool", "Duplicati\CommandLine\BackendTool\Duplicati.CommandLine.BackendTool.csproj", "{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.BackendTool", "Duplicati\CommandLine\BackendTool\Duplicati.CommandLine.BackendTool.csproj", "{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SQLiteHelper", "Duplicati\Library\SQLiteHelper\Duplicati.Library.SQLiteHelper.csproj", "{2C838169-B187-4B09-8768-1C24C2521C8D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.SQLiteHelper", "Duplicati\Library\SQLiteHelper\Duplicati.Library.SQLiteHelper.csproj", "{2C838169-B187-4B09-8768-1C24C2521C8D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Localization", "Duplicati\Library\Localization\Duplicati.Library.Localization.csproj", "{B68F2214-951F-4F78-8488-66E1ED3F50BF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Localization", "Duplicati\Library\Localization\Duplicati.Library.Localization.csproj", "{B68F2214-951F-4F78-8488-66E1ED3F50BF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.AutoUpdater", "Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj", "{7E119745-1F62-43F0-936C-F312A1912C0B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.AutoUpdater", "Duplicati\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj", "{7E119745-1F62-43F0-936C-F312A1912C0B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Service", "Duplicati\Service\Duplicati.Service.csproj", "{E93F3DE2-FF3A-4709-96A3-8190AA14FA25}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Service", "Duplicati\Service\Duplicati.Service.csproj", "{E93F3DE2-FF3A-4709-96A3-8190AA14FA25}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.AzureBlob", "Duplicati\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj", "{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.AzureBlob", "Duplicati\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj", "{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.UnitTest", "Duplicati\UnitTest\Duplicati.UnitTest.csproj", "{ECB63D1C-1724-442D-9228-DEABF14F2EA3}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.UnitTest", "Duplicati\UnitTest\Duplicati.UnitTest.csproj", "{ECB63D1C-1724-442D-9228-DEABF14F2EA3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.OAuthHelper", "Duplicati\Library\Backend\OAuthHelper\Duplicati.Library.OAuthHelper.csproj", "{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.OAuthHelper", "Duplicati\Library\Backend\OAuthHelper\Duplicati.Library.OAuthHelper.csproj", "{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.GoogleServices", "Duplicati\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj", "{5489181D-950C-44AF-873C-45EB0A3B6BD2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.GoogleServices", "Duplicati\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj", "{5489181D-950C-44AF-873C-45EB0A3B6BD2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.OpenStack", "Duplicati\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj", "{D9E4E686-423C-48EC-A392-404E7C00860C}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.OpenStack", "Duplicati\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj", "{D9E4E686-423C-48EC-A392-404E7C00860C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.HubiC", "Duplicati\Library\Backend\HubiC\Duplicati.Library.Backend.HubiC.csproj", "{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.RecoveryTool", "Duplicati\CommandLine\RecoveryTool\Duplicati.CommandLine.RecoveryTool.csproj", "{4A010589-76E6-4F05-A5C4-4598D5DF11F8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.RecoveryTool", "Duplicati\CommandLine\RecoveryTool\Duplicati.CommandLine.RecoveryTool.csproj", "{4A010589-76E6-4F05-A5C4-4598D5DF11F8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Backblaze", "Duplicati\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj", "{61C43D61-4368-4942-84A3-1EB623F4EF2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Backblaze", "Duplicati\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj", "{61C43D61-4368-4942-84A3-1EB623F4EF2A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Mega", "Duplicati\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj", "{6643A5AE-AB38-453F-ADCE-408E35A81A83}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Mega", "Duplicati\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj", "{6643A5AE-AB38-453F-ADCE-408E35A81A83}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Box", "Duplicati\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj", "{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Box", "Duplicati\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj", "{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.UsageReporter", "Duplicati\Library\UsageReporter\Duplicati.Library.UsageReporter.csproj", "{BB014EA5-CE2C-4444-8D30-38983A0E8553}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.UsageReporter", "Duplicati\Library\UsageReporter\Duplicati.Library.UsageReporter.csproj", "{BB014EA5-CE2C-4444-8D30-38983A0E8553}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.SharePoint", "Duplicati\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj", "{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SharePoint", "Duplicati\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj", "{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Dropbox", "Duplicati\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj", "{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsService", "Duplicati\WindowsService\WindowsService.csproj", "{BEF7AF9A-3978-4F90-8592-198BF6EA6C6B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Tools", "Duplicati\Tools\Duplicati.Tools.csproj", "{0797AA22-C5DD-4950-BB60-34765AB8C6DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.AlternativeFTP", "Duplicati\Library\Backend\AlternativeFTP\Duplicati.Library.Backend.AlternativeFTP.csproj", "{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Jottacloud", "Duplicati\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj", "{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Dropbox", "Duplicati\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj", "{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Sia", "Duplicati\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj", "{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Tools", "Duplicati\Tools\Duplicati.Tools.csproj", "{0797AA22-C5DD-4950-BB60-34765AB8C6DD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Rclone", "Duplicati\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj", "{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Jottacloud", "Duplicati\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj", "{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Common", "Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Sia", "Duplicati\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj", "{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Encryption", "Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{2CF2D90E-C25B-47AD-91E0-98451BAB8058}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Rclone", "Duplicati\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj", "{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Storj", "Duplicati\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj", "{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Common", "Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.TencentCOS", "Duplicati\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj", "{545DD6D4-9476-42D6-B51C-A28E000C489E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.ConfigurationImporter", "Duplicati\CommandLine\ConfigurationImporter\Duplicati.CommandLine.ConfigurationImporter.csproj", "{B93E3BF0-DAA7-49B7-B07D-0559C5816735}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Executables", "Executables", "{FA88A246-EF8E-46E3-90AF-539B8C0A6ADE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Encryption", "Duplicati\Library\Encryption\Duplicati.Library.Encryption.csproj", "{2CF2D90E-C25B-47AD-91E0-98451BAB8058}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8", "net8", "{6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Storj", "Duplicati\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj", "{AE035E01-C917-4F13-A35E-78F21C1A2F17}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.AutoUpdater", "Executables\net8\Duplicati.CommandLine.AutoUpdater\Duplicati.CommandLine.AutoUpdater.csproj", "{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.TencentCOS", "Duplicati\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj", "{545DD6D4-9476-42D6-B51C-A28E000C489E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Service", "Executables\net8\Duplicati.Service\Duplicati.Service.csproj", "{34149709-F3ED-4FB5-A087-43EB195C948B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Tardigrade", "Duplicati\Library\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj", "{9A04CB37-DA72-4008-9703-3AC5191974E9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.BackendTester", "Executables\net8\Duplicati.CommandLine.BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{2F1C0C8D-5C15-4BC0-811F-87F2C98D9790}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.BackendTool", "Executables\net8\Duplicati.CommandLine.BackendTool\Duplicati.CommandLine.BackendTool.csproj", "{31FA5B9B-4CD6-4BF3-B7A9-12C1E30DCAF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine.RecoveryTool", "Executables\net8\Duplicati.CommandLine.RecoveryTool\Duplicati.CommandLine.RecoveryTool.csproj", "{0FFC557E-1B84-46A2-B6E8-06064FA7EA58}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.GUI.TrayIcon", "Executables\net8\Duplicati.GUI.TrayIcon\Duplicati.GUI.TrayIcon.csproj", "{AF32C621-30DC-40F5-8CE6-DD69053068E9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Server", "Executables\net8\Duplicati.Server\Duplicati.Server.csproj", "{55EEEBD2-CE45-45D6-9838-958F1C7354E4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.WindowsService", "Executables\net8\Duplicati.WindowsService\Duplicati.WindowsService.csproj", "{5D20B150-C445-47BE-8CE8-C9F74F19A4F2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.CommandLine", "Executables\net8\Duplicati.CommandLine\Duplicati.CommandLine.csproj", "{0F5A1F4E-25FA-4D02-920D-CA2138498081}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Idrivee2", "Duplicati\Library\Backend\Idrivee2\Duplicati.Library.Backend.Idrivee2.csproj", "{6B594D23-B629-465C-B799-70EE9E56C218}"
ProjectSection(ProjectDependencies) = postProject
{C03F6DFD-805A-4BE0-9338-64870ADDB4A2} = {C03F6DFD-805A-4BE0-9338-64870ADDB4A2}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Backends", "Backends", "{E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Helper Libraries", "Helper Libraries", "{566EBBDA-19A4-4056-A615-D901D57D2439}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Implementation", "Implementation", "{D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backends", "Duplicati\Library\Backends\Duplicati.Library.Backends.csproj", "{5290E237-C2CD-48F2-99D2-817F9C2163C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.WebserverCore", "Duplicati\WebserverCore\Duplicati.WebserverCore.csproj", "{5A702CEE-DB36-4153-BD94-D8CF867E75A9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.RestAPI", "Duplicati\Library\RestAPI\Duplicati.Library.RestAPI.csproj", "{C1D4D665-23A3-4216-9CD1-D67AE9AAAA4C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.AliyunOSS", "Duplicati\Library\Backend\AliyunOSS\Duplicati.Library.Backend.AliyunOSS.csproj", "{4EB3DABC-D412-4C12-8876-41A1427A389E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.SharpAESCrypt", "Executables\net8\Duplicati.CommandLine.SharpAESCrypt\Duplicati.CommandLine.SharpAESCrypt.csproj", "{FE6FD36C-E171-4599-8D55-62DA579C0864}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.Snapshots", "Executables\net8\Duplicati.CommandLine.Snapshots\Duplicati.CommandLine.Snapshots.csproj", "{0364E724-1929-445E-9145-90A70B01DDC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.ServerUtil", "Duplicati\CommandLine\ServerUtil\Duplicati.CommandLine.ServerUtil.csproj", "{5AF834B1-D227-4A98-9377-6BFA6BCF99A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.ServerUtil", "Executables\net8\Duplicati.CommandLine.ServerUtil\Duplicati.CommandLine.ServerUtil.csproj", "{C09A7DE2-F0F3-4FA4-B36C-A0DE7844739B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.WindowsService", "Duplicati\WindowsService\Duplicati.WindowsService.csproj", "{3476A88B-4123-45F4-AC96-700B747367EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Agent", "Duplicati\Agent\Duplicati.Agent.csproj", "{0C1AD03C-89D8-4C47-82FF-25D5470055B4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Agent", "Executables\net8\Duplicati.Agent\Duplicati.Agent.csproj", "{A2957269-C11F-44CE-B355-CC9BA342295D}"
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -241,10 +298,6 @@ Global
{D9E4E686-423C-48EC-A392-404E7C00860C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9E4E686-423C-48EC-A392-404E7C00860C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9E4E686-423C-48EC-A392-404E7C00860C}.Release|Any CPU.Build.0 = Release|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}.Release|Any CPU.Build.0 = Release|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A010589-76E6-4F05-A5C4-4598D5DF11F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -269,14 +322,6 @@ Global
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}.Release|Any CPU.Build.0 = Release|Any CPU
{BEF7AF9A-3978-4F90-8592-198BF6EA6C6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEF7AF9A-3978-4F90-8592-198BF6EA6C6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEF7AF9A-3978-4F90-8592-198BF6EA6C6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEF7AF9A-3978-4F90-8592-198BF6EA6C6B}.Release|Any CPU.Build.0 = Release|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}.Release|Any CPU.Build.0 = Release|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -301,37 +346,193 @@ Global
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D63E53E4-A458-4C2F-914D-92F715F58ACF}.Release|Any CPU.Build.0 = Release|Any CPU
{B93E3BF0-DAA7-49B7-B07D-0559C5816735}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B93E3BF0-DAA7-49B7-B07D-0559C5816735}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B93E3BF0-DAA7-49B7-B07D-0559C5816735}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B93E3BF0-DAA7-49B7-B07D-0559C5816735}.Release|Any CPU.Build.0 = Release|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CF2D90E-C25B-47AD-91E0-98451BAB8058}.Release|Any CPU.Build.0 = Release|Any CPU
{AE035E01-C917-4F13-A35E-78F21C1A2F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE035E01-C917-4F13-A35E-78F21C1A2F17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE035E01-C917-4F13-A35E-78F21C1A2F17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE035E01-C917-4F13-A35E-78F21C1A2F17}.Release|Any CPU.Build.0 = Release|Any CPU
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489}.Release|Any CPU.Build.0 = Release|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{545DD6D4-9476-42D6-B51C-A28E000C489E}.Release|Any CPU.Build.0 = Release|Any CPU
{9A04CB37-DA72-4008-9703-3AC5191974E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A04CB37-DA72-4008-9703-3AC5191974E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A04CB37-DA72-4008-9703-3AC5191974E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A04CB37-DA72-4008-9703-3AC5191974E9}.Release|Any CPU.Build.0 = Release|Any CPU
{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A}.Release|Any CPU.Build.0 = Release|Any CPU
{34149709-F3ED-4FB5-A087-43EB195C948B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34149709-F3ED-4FB5-A087-43EB195C948B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34149709-F3ED-4FB5-A087-43EB195C948B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34149709-F3ED-4FB5-A087-43EB195C948B}.Release|Any CPU.Build.0 = Release|Any CPU
{2F1C0C8D-5C15-4BC0-811F-87F2C98D9790}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F1C0C8D-5C15-4BC0-811F-87F2C98D9790}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F1C0C8D-5C15-4BC0-811F-87F2C98D9790}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F1C0C8D-5C15-4BC0-811F-87F2C98D9790}.Release|Any CPU.Build.0 = Release|Any CPU
{31FA5B9B-4CD6-4BF3-B7A9-12C1E30DCAF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31FA5B9B-4CD6-4BF3-B7A9-12C1E30DCAF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31FA5B9B-4CD6-4BF3-B7A9-12C1E30DCAF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31FA5B9B-4CD6-4BF3-B7A9-12C1E30DCAF6}.Release|Any CPU.Build.0 = Release|Any CPU
{0FFC557E-1B84-46A2-B6E8-06064FA7EA58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FFC557E-1B84-46A2-B6E8-06064FA7EA58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FFC557E-1B84-46A2-B6E8-06064FA7EA58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FFC557E-1B84-46A2-B6E8-06064FA7EA58}.Release|Any CPU.Build.0 = Release|Any CPU
{AF32C621-30DC-40F5-8CE6-DD69053068E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF32C621-30DC-40F5-8CE6-DD69053068E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF32C621-30DC-40F5-8CE6-DD69053068E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF32C621-30DC-40F5-8CE6-DD69053068E9}.Release|Any CPU.Build.0 = Release|Any CPU
{55EEEBD2-CE45-45D6-9838-958F1C7354E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55EEEBD2-CE45-45D6-9838-958F1C7354E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55EEEBD2-CE45-45D6-9838-958F1C7354E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55EEEBD2-CE45-45D6-9838-958F1C7354E4}.Release|Any CPU.Build.0 = Release|Any CPU
{5D20B150-C445-47BE-8CE8-C9F74F19A4F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D20B150-C445-47BE-8CE8-C9F74F19A4F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D20B150-C445-47BE-8CE8-C9F74F19A4F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D20B150-C445-47BE-8CE8-C9F74F19A4F2}.Release|Any CPU.Build.0 = Release|Any CPU
{0F5A1F4E-25FA-4D02-920D-CA2138498081}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F5A1F4E-25FA-4D02-920D-CA2138498081}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F5A1F4E-25FA-4D02-920D-CA2138498081}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F5A1F4E-25FA-4D02-920D-CA2138498081}.Release|Any CPU.Build.0 = Release|Any CPU
{6B594D23-B629-465C-B799-70EE9E56C218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B594D23-B629-465C-B799-70EE9E56C218}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B594D23-B629-465C-B799-70EE9E56C218}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B594D23-B629-465C-B799-70EE9E56C218}.Release|Any CPU.Build.0 = Release|Any CPU
{5290E237-C2CD-48F2-99D2-817F9C2163C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5290E237-C2CD-48F2-99D2-817F9C2163C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5290E237-C2CD-48F2-99D2-817F9C2163C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5290E237-C2CD-48F2-99D2-817F9C2163C8}.Release|Any CPU.Build.0 = Release|Any CPU
{5A702CEE-DB36-4153-BD94-D8CF867E75A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A702CEE-DB36-4153-BD94-D8CF867E75A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A702CEE-DB36-4153-BD94-D8CF867E75A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A702CEE-DB36-4153-BD94-D8CF867E75A9}.Release|Any CPU.Build.0 = Release|Any CPU
{C1D4D665-23A3-4216-9CD1-D67AE9AAAA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
{FE6FD36C-E171-4599-8D55-62DA579C0864}.Release|Any CPU.Build.0 = Release|Any CPU
{0364E724-1929-445E-9145-90A70B01DDC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0364E724-1929-445E-9145-90A70B01DDC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0364E724-1929-445E-9145-90A70B01DDC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0364E724-1929-445E-9145-90A70B01DDC0}.Release|Any CPU.Build.0 = Release|Any CPU
{5AF834B1-D227-4A98-9377-6BFA6BCF99A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5AF834B1-D227-4A98-9377-6BFA6BCF99A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AF834B1-D227-4A98-9377-6BFA6BCF99A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AF834B1-D227-4A98-9377-6BFA6BCF99A7}.Release|Any CPU.Build.0 = Release|Any CPU
{C09A7DE2-F0F3-4FA4-B36C-A0DE7844739B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C09A7DE2-F0F3-4FA4-B36C-A0DE7844739B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C09A7DE2-F0F3-4FA4-B36C-A0DE7844739B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C09A7DE2-F0F3-4FA4-B36C-A0DE7844739B}.Release|Any CPU.Build.0 = Release|Any CPU
{3476A88B-4123-45F4-AC96-700B747367EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3476A88B-4123-45F4-AC96-700B747367EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3476A88B-4123-45F4-AC96-700B747367EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3476A88B-4123-45F4-AC96-700B747367EB}.Release|Any CPU.Build.0 = Release|Any CPU
{0C1AD03C-89D8-4C47-82FF-25D5470055B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C1AD03C-89D8-4C47-82FF-25D5470055B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C1AD03C-89D8-4C47-82FF-25D5470055B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C1AD03C-89D8-4C47-82FF-25D5470055B4}.Release|Any CPU.Build.0 = Release|Any CPU
{A2957269-C11F-44CE-B355-CC9BA342295D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2957269-C11F-44CE-B355-CC9BA342295D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2957269-C11F-44CE-B355-CC9BA342295D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2957269-C11F-44CE-B355-CC9BA342295D}.Release|Any CPU.Build.0 = Release|Any CPU
{BB3A6E17-FC2E-42E3-B697-10D0972AFE81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FC9B7611-836F-4127-8B44-A7C31F506807} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{F61679A9-E5DE-468A-B5A4-05F92D0143D2} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{19ECCE09-B5EB-406C-8C57-BAC66997D469} = {566EBBDA-19A4-4056-A615-D901D57D2439}
{C03F6DFD-805A-4BE0-9338-64870ADDB4A2} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{81765A64-3661-4E3E-B850-2F6F87A51F74} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{BAE27510-8B5D-44B2-B33E-372A98908041} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{1BFAE226-8364-4086-825C-BB83F6F3EE4C} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{E7280DCA-7776-4A73-B9B5-41FD77FC8799} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{0CA86ECF-5BEC-4909-B4F6-110A03B30B92} = {566EBBDA-19A4-4056-A615-D901D57D2439}
{C0270709-2A40-43B5-8CF1-69581B9FA2A1} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{CCD76347-7DC7-4B42-B7E1-E500E624CAC3} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{FF2BF37C-E502-4C98-BEA0-701671DDFA08} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{19E661D2-C5DA-4F35-B3EE-7586E5734B5F} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{17566860-3D98-4604-AA5B-47661F75609F} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{2C838169-B187-4B09-8768-1C24C2521C8D} = {566EBBDA-19A4-4056-A615-D901D57D2439}
{E93F3DE2-FF3A-4709-96A3-8190AA14FA25} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{8E4CECFB-0413-4B00-AB93-78D1C3902BD5} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{5489181D-950C-44AF-873C-45EB0A3B6BD2} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{D9E4E686-423C-48EC-A392-404E7C00860C} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{4A010589-76E6-4F05-A5C4-4598D5DF11F8} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{61C43D61-4368-4942-84A3-1EB623F4EF2A} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{6643A5AE-AB38-453F-ADCE-408E35A81A83} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{59C8BBC5-6E42-46FB-AB3E-6C183A82459A} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{851A1CB8-3CEB-41B4-956F-34D760D2A8E5} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{E9AB8491-BD4C-4E4F-84C3-0BD551CC7489} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{545DD6D4-9476-42D6-B51C-A28E000C489E} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3} = {FA88A246-EF8E-46E3-90AF-539B8C0A6ADE}
{95B7DD83-2C5A-4F1E-8EA7-39654B2B236A} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{34149709-F3ED-4FB5-A087-43EB195C948B} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{2F1C0C8D-5C15-4BC0-811F-87F2C98D9790} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{31FA5B9B-4CD6-4BF3-B7A9-12C1E30DCAF6} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{0FFC557E-1B84-46A2-B6E8-06064FA7EA58} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{AF32C621-30DC-40F5-8CE6-DD69053068E9} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{55EEEBD2-CE45-45D6-9838-958F1C7354E4} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{5D20B150-C445-47BE-8CE8-C9F74F19A4F2} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{0F5A1F4E-25FA-4D02-920D-CA2138498081} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{6B594D23-B629-465C-B799-70EE9E56C218} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4}
{D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B} = {FA88A246-EF8E-46E3-90AF-539B8C0A6ADE}
{FE6FD36C-E171-4599-8D55-62DA579C0864} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{0364E724-1929-445E-9145-90A70B01DDC0} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{5AF834B1-D227-4A98-9377-6BFA6BCF99A7} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
{C09A7DE2-F0F3-4FA4-B36C-A0DE7844739B} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
{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}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8B40BAFE-D862-4397-9495-8F5EAF5CE80C}
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
Policies = $0
$0.StandardHeader = $1
$1.Text = @ Copyright (C) ${Year}, The Duplicati Team\n http://www.duplicati.com, info@duplicati.com\n \n This library is free software; you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as\n published by the Free Software Foundation; either version 2.1 of the\n License, or (at your option) any later version.\n\n This library is distributed in the hope that it will be useful, but\n WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public\n License along with this library; if not, write to the Free Software\n Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
$1.Text = @ Copyright (C) ${Year}, The Duplicati Team\nhttps://duplicati.com, hello@duplicati.com\n\nPermission is hereby granted, free of charge, to any person obtaining a \ncopy of this software and associated documentation files (the "Software"), \nto deal in the Software without restriction, including without limitation \nthe rights to use, copy, modify, merge, publish, distribute, sublicense, \nand/or sell copies of the Software, and to permit persons to whom the \nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in \nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS \nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \nDEALINGS IN THE SOFTWARE.\n
$0.DotNetNamingPolicy = $2
$2.DirectoryNamespaceAssociation = PrefixedHierarchical
$0.TextStylePolicy = $5
@@ -340,8 +541,4 @@ Global
$5.EolMarker = Windows
$5.scope = text/plain
EndGlobalSection
GlobalSection(SubversionScc) = preSolution
Svn-Managed = True
Manager = AnkhSVN - Subversion Support for Visual Studio
EndGlobalSection
EndGlobal
+30
View File
@@ -0,0 +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.Logging;
namespace Duplicati.Agent;
public class ConsoleLogDestination : ILogDestination
{
public void WriteMessage(LogEntry entry)
=> Console.WriteLine(entry.AsString(true));
}
+30
View File
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Duplicati.Agent.Implementation</AssemblyName>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
<RootNamespace>Duplicati.Agent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Server\Duplicati.Server.csproj" />
<ProjectReference Include="..\Library\RemoteControl\Duplicati.Library.RemoteControl.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
+502
View File
@@ -0,0 +1,502 @@
// 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.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.NamingConventionBinder;
using System.CommandLine.Parsing;
using System.Reactive.Linq;
using System.Security.Cryptography;
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 Microsoft.Extensions.DependencyInjection;
namespace Duplicati.Agent;
public static class Program
{
/// <summary>
/// The log tag for messages from this class
/// </summary>
private static readonly string LogTag = Log.LogTagFromType(typeof(Program));
/// <summary>
/// Create a random pre-shared key for the agent
/// </summary>
private static string? PreSharedKey = null;
/// <summary>
/// Commandline arguments for the agent
/// </summary>
/// <param name="AgentRegistrationUrl">The server URL to connect to</param>
/// <param name="AgentSettingsFile">The file to use for the agent settings</param>
/// <param name="AgentSettingsFilePassphrase">The passphrase for the agent settings file</param>
/// <param name="WebserviceListenInterface">The interface to listen on for the webserver</param>
/// <param name="WebservicePort">The port to listen on for the webserver</param>
/// <param name="WebservicePassword">The password for the webserver, random if none supplied</param>
/// <param name="SettingsEncryptionKey">The encryption key for the database settings</param>
/// <param name="WindowsEventLog">The Windows event log to write to</param>
/// <param name="DisableDbEncryption">Disable database encryption</param>
/// <param name="WebserviceResetJwtConfig">Reset the JWT configuration</param>
/// <param name="WebserviceAllowedHostnames">The allowed hostnames for the webserver</param>
/// <param name="WebserviceApiOnly">Only allow API access to the webserver</param>
/// <param name="WebserviceDisableSigninTokens">Disable signin tokens for the webserver</param>
/// <param name="PingPongKeepalive">Enable ping-pong keepalive</param>
/// <param name="DisablePreSharedKey">Disable the pre-shared key</param>
/// <param name="KeepWebservicePassword">Keep the webserver password</param>
/// <param name="SecretProvider">The secret provider to use</param>
/// <param name="SecretProviderCache">The secret provider cache level</param>
/// <param name="SecretProviderPattern">The secret provider pattern</param>
private sealed record CommandLineArguments(
string AgentRegistrationUrl,
FileInfo AgentSettingsFile,
string? AgentSettingsFilePassphrase,
bool AgentRegisterOnly,
string WebserviceListenInterface,
string WebservicePort,
string WebservicePassword,
string? SettingsEncryptionKey,
string WindowsEventLog,
bool DisableDbEncryption,
bool WebserviceResetJwtConfig,
string WebserviceAllowedHostnames,
bool WebserviceApiOnly,
bool WebserviceDisableSigninTokens,
bool PingPongKeepalive,
bool DisablePreSharedKey,
bool KeepWebservicePassword,
string? SecretProvider,
SecretProviderHelper.CachingLevel SecretProviderCache,
string SecretProviderPattern
);
[STAThread]
public static Task<int> Main(string[] args)
{
Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.Agent);
var runcmd = new Command("run", "Runs the agent")
{
new Option<string>("--agent-registration-url", description: "The server URL to connect to", getDefaultValue: () => RegisterForRemote.DefaultRegisterationUrl),
new Option<FileInfo>("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)),
new Option<string?>("--agent-settings-file-passphrase", description: "The passphrase for the agent settings file", getDefaultValue: () => null),
new Option<bool>("--agent-register-only", description: "Only register the agent, then exit", getDefaultValue: () => false),
new Option<string>("--webservice-listen-interface", description: "The interface to listen on for the webserver", getDefaultValue: () => "loopback"),
new Option<string>("--webservice-port", description: "The port to listen on for the webserver", getDefaultValue: () => "8210"),
new Option<string?>("--webservice-password", description: "The password for the webserver, not set, or set to \"random\", a random value is used.", getDefaultValue: () => null),
new Option<string?>("--settings-encryption-key", description: "The encryption key for the database settings", getDefaultValue: () => null),
new Option<string>("--windows-eventlog", description: "The Windows event log to use as source.", getDefaultValue: () => ""),
new Option<bool>("--disable-db-encryption", description: "Disable database encryption", getDefaultValue: () => false),
new Option<bool>("--webservice-reset-jwt-config", description: "Reset the JWT configuration", getDefaultValue: () => true),
new Option<string>("--webservice-allowed-hostnames", description: "The allowed hostnames for the webserver", getDefaultValue: () => "127.0.0.1"),
new Option<bool>("--webservice-api-only", description: "Only allow API access to the webserver", getDefaultValue: () => true),
new Option<bool>("--webservice-disable-signin-tokens", description: "Disable signin tokens for the webserver", getDefaultValue: () => true),
new Option<bool>("--ping-pong-keepalive", description: "Enable ping-pong keepalive", getDefaultValue: () => false),
new Option<bool>("--disable-pre-shared-key", description: "Disable the pre-shared key that prevents outside access to the webserver", getDefaultValue: () => false),
new Option<bool>("--keep-webservice-password", description: "Disables the random password assigned to the webserver on startup if no password is provided", getDefaultValue: () => false),
new Option<string?>("--secret-provider", description: "The secret provider to use", getDefaultValue: () => null),
new Option<SecretProviderHelper.CachingLevel>("--secret-provider-cache", description: "The secret provider cache level", getDefaultValue: () => SecretProviderHelper.CachingLevel.None),
new Option<string>("--secret-provider-pattern", description: "The secret provider pattern", getDefaultValue: () => SecretProviderHelper.DEFAULT_PATTERN),
};
runcmd.Handler = CommandHandler.Create<CommandLineArguments>(RunAgent);
var clearCommand = new Command("clear", "Clears the agent settings")
{
new Option<FileInfo>("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)),
};
clearCommand.Handler = CommandHandler.Create<FileInfo>(ClearAgentSettings);
var registerCommand = new Command("register", "Registers the agent and exits")
{
new Argument<string>("agent-registration-url", description: "The server URL to connect to", getDefaultValue: () => RegisterForRemote.DefaultRegisterationUrl) { Arity = ArgumentArity.ZeroOrOne },
new Option<FileInfo>("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)),
new Option<string?>("--agent-settings-file-passphrase", description: "The passphrase for the agent settings file", getDefaultValue: () => null),
new Option<string?>("--secret-provider", description: "The secret provider to use", getDefaultValue: () => null),
new Option<string>("--secret-provider-pattern", description: "The secret provider pattern", getDefaultValue: () => SecretProviderHelper.DEFAULT_PATTERN),
};
registerCommand.Handler = CommandHandler.Create<string, FileInfo, string?, string?, string>(RegisterAgent);
var rootcmd = new RootCommand("Duplicati Agent")
{
runcmd,
clearCommand,
registerCommand
};
// rootcmd.Handler = CommandHandler.Create<CommandLineArguments>(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;
}
})
.Build()
.InvokeAsync(args);
}
/// <summary>
/// Clears any local agent settings
/// </summary>
/// <param name="agentSettingsFile">The file to clear</param>
/// <returns>The exit code</returns>
private static Task<int> ClearAgentSettings(FileInfo agentSettingsFile)
{
if (File.Exists(agentSettingsFile.FullName))
File.Delete(agentSettingsFile.FullName);
return Task.FromResult(0);
}
/// <summary>
/// Registers the agent with the remote server
/// </summary>
/// <param name="agentRegistrationUrl">The URL to register the agent with</param>
/// <param name="agentSettingsFile">The file to save the settings to</param>
/// <param name="agentSettingsFilePassphrase">The passphrase for the settings file</param>
/// <param name="secretProvider">The secret provider to use</param>
/// <param name="secretProviderPattern">The secret provider pattern</param>
/// <returns></returns>
private static Task<int> 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
));
/// <summary>
/// Runs the agent
/// </summary>
/// <param name="agentConfig">The configuration from the commandline</param>
/// <returns>The exit code</returns>
private static async Task<int> RunAgent(CommandLineArguments agentConfig)
{
// 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);
using var cts = new CancellationTokenSource();
var target = new ControllerMultiLogTarget(new ConsoleLogDestination(), LogMessageType.Information, null);
using (Log.StartScope(target))
{
if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(agentConfig.WindowsEventLog))
{
if (!WindowsEventLogSource.SourceExists(agentConfig.WindowsEventLog))
{
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 {agentConfig.WindowsEventLog}: {ex.Message}"); }
}
if (WindowsEventLogSource.SourceExists(agentConfig.WindowsEventLog))
target.AddTarget(new WindowsEventLogSource(agentConfig.WindowsEventLog), LogMessageType.Information, null);
}
Log.WriteMessage(LogMessageType.Information, LogTag, "AgentStarting", "Starting agent");
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(
Task.Run(() => StartLocalServer(agentConfig, settings, cts.Token)),
KeepRemoteConnection.Start(
settings.ServerUrl,
settings.JWT,
settings.CertificateUrl,
settings.ServerCertificates,
cts.Token,
c => ReKey(agentConfig, c),
(m) => OnMessage(m, agentConfig)
)
);
await t;
return 0;
}
}
private static async Task OnMessage(KeepRemoteConnection.CommandMessage message, CommandLineArguments agentConfig)
{
Log.WriteMessage(LogMessageType.Verbose, LogTag, "OnMessage", "Received message: {0}", message);
var provider = FIXMEGlobal.Provider.GetRequiredService<IJWTTokenProvider>();
var token = provider.CreateAccessToken("agent", provider.TemporaryFamilyId, TimeSpan.FromMinutes(2));
using var httpClient = FIXMEGlobal.Provider.GetRequiredService<IHttpClientFactory>().CreateClient();
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
httpClient.BaseAddress = new System.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(CommandLineArguments agentConfig, ClaimedClientData keydata)
{
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 };
// Only allow re-keying if the settings encryption key is not set
if (!FIXMEGlobal.SettingsEncryptionKeyProvidedExternally)
{
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<Connection>().ChangeDbKey(keydata.LocalEncryptionKey);
// settings = settings with { SettingsEncryptionKey = keydata.LocalEncryptionKey };
}
}
settings.Save();
return Task.CompletedTask;
}
/// <summary>
/// Runs the server, restarting on crashes
/// </summary>
/// <param name="args">The commandline arguments passed to the server</param>
/// <param name="cancellationToken">The cancellation token to use for the process</param>
/// <returns>An awaitable task</returns>
private static async Task RunServer(string[] args, CancellationToken cancellationToken)
{
cancellationToken.Register(() =>
{
Server.Program.ApplicationExitEvent.Set();
});
var lastRestart = DateTime.Now;
while (!cancellationToken.IsCancellationRequested)
{
try
{
lastRestart = DateTime.Now;
Server.Program.Main(args);
}
catch (Exception ex)
{
if (!cancellationToken.IsCancellationRequested)
{
var nextRestart = lastRestart.AddSeconds(30);
var waitTime = nextRestart - DateTime.Now;
if (waitTime.TotalSeconds < 0)
waitTime = TimeSpan.FromSeconds(0);
Log.WriteMessage(LogMessageType.Error, LogTag, "ServerCrash", ex, "Server crashed, restarting in {0} seconds", waitTime.TotalSeconds);
if (waitTime.TotalSeconds > 0)
await Task.Delay(waitTime);
}
}
}
}
/// <summary>
/// Registers the machine with the remote server
/// </summary>
/// <param name="registrationUrl">The URL to register the machine with</param>
/// <param name="settingsFile">The file to save the settings to</param>
/// <param name="settingsPasshrase">The passphrase for the settings file</param>
/// <param name="cancellationToken">The cancellation token to use for the process</param>
/// <returns>The settings for the machine</returns>
private static async Task<Settings> Register(string registrationUrl, string settingsFile, string? settingsPasshrase, CancellationToken cancellationToken)
{
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");
using (var registration = new RegisterForRemote(registrationUrl, null, cancellationToken))
{
var registerClientData = await registration.Register();
if (registerClientData.RegistrationData != null)
{
Log.WriteMessage(LogMessageType.Information, LogTag, "ClientRegistered", $"Machine registered, claim it by visiting: {registerClientData.RegistrationData.ClaimLink}");
try
{
Library.Utility.UrlUtility.OpenURL(registerClientData.RegistrationData.ClaimLink);
}
catch (Exception ex)
{
Log.WriteMessage(LogMessageType.Warning, LogTag, "ClientClaimLink", ex, "Failed to open the claim link in system browser");
}
}
var claimedClientData = await registration.Claim();
Log.WriteMessage(LogMessageType.Information, LogTag, "ClientClaimed", "Machine claimed, saving JWT");
settings = settings with
{
JWT = claimedClientData.JWT,
ServerUrl = claimedClientData.ServerUrl,
SettingsEncryptionKey = claimedClientData.LocalEncryptionKey,
ServerCertificates = claimedClientData.ServerCertificates,
CertificateUrl = claimedClientData.CertificateUrl
};
settings.Save();
}
}
return settings;
}
/// <summary>
/// Starts the local webserver in locked down mode, restarting on crashes
/// </summary>
/// <param name="agentConfig">The agent configuration</param>
/// <param name="settings">The settings for the agent</param>
/// <param name="cancellationToken">The cancellation token that stops the server</param>
/// <returns>An awaitable task</returns>
private static async Task StartLocalServer(CommandLineArguments agentConfig, Settings settings, CancellationToken cancellationToken)
{
// TODO: Look into pipes for Kestrel to prevent network access
string? settingsEncryptionKey = null;
// 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);
}
}
+157
View File
@@ -0,0 +1,157 @@
// 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.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;
namespace Duplicati.Agent;
/// <summary>
/// Settings for the agent
/// </summary>
/// <param name="Filename">The filename of the settings</param>
/// <param name="JWT">The JWT used for authentication</param>
/// <param name="ServerUrl">The URL of the server</param>
/// <param name="SettingsEncryptionKey">The encryption key for the local settings</param>
/// <param name="ServerCertificates">The server certificates</param>
/// <param name="Key">The encryption key to use for agent settings</param>
public sealed record Settings(
string Filename,
string JWT,
string ServerUrl,
string CertificateUrl,
string? SettingsEncryptionKey,
IEnumerable<MiniServerCertificate> ServerCertificates,
EncryptedFieldHelper.KeyInstance? Key
)
{
/// <summary>
/// Gets the default settings file path
/// </summary>
public static readonly string DefaultSettingsFile = GetSettingsPath();
/// <summary>
/// Gets the default settings file name
/// </summary>
public const string DefaultSettingsFilename = "agent.json";
/// <summary>
/// The encryption key to use for agent settings
/// </summary>
private static readonly EncryptedFieldHelper.KeyInstance? DefaultKey = null;
/// <summary>
/// Shared JSON serialization options
/// </summary>
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
PropertyNameCaseInsensitive = true
};
/// <summary>
/// Loads the settings from a file
/// </summary>
/// <param name="path">The path to the file</param>
/// <returns>The loaded settings</returns>
public static Settings Load(string path, string? key)
{
var keyInstance = string.IsNullOrWhiteSpace(key)
? DefaultKey
: EncryptedFieldHelper.KeyInstance.CreateKey(key);
var tmp = (File.Exists(path) ? JsonSerializer.Deserialize<Settings>(File.ReadAllText(path), JsonOptions) : null)
?? new Settings(path, string.Empty, string.Empty, string.Empty, string.Empty, Array.Empty<MiniServerCertificate>(), null);
try
{
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);
}
}
/// <summary>
/// Saves the settings
/// </summary>
public void Save()
{
var tmp = this with { Key = null, Filename = null! };
tmp = Key == null || Key.IsBlacklisted
? tmp
: tmp with
{
JWT = EncryptFieldIfPossible(JWT, Key),
SettingsEncryptionKey = EncryptFieldIfPossible(SettingsEncryptionKey, Key)
};
File.WriteAllText(Filename, JsonSerializer.Serialize(tmp, JsonOptions));
}
/// <summary>
/// Encrypts a field if possible
/// </summary>
/// <param name="value">The value to encrypt</param>
/// <returns>The encrypted value</returns>
[return: NotNullIfNotNull("value")]
private static string? EncryptFieldIfPossible(string? value, EncryptedFieldHelper.KeyInstance? key)
{
if (key == null || key.IsBlacklisted)
return value;
if (value == null)
return null;
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
return EncryptedFieldHelper.Encrypt(value, key);
}
/// <summary>
/// Gets the default settings file path
/// </summary>
/// <returns>The path to the settings file</returns>
private static string GetSettingsPath()
{
var folder = DatabaseLocator.GetDefaultStorageFolderWithDebugSupport(DefaultSettingsFilename);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
return Path.Combine(folder, DefaultSettingsFilename);
}
}
@@ -1,218 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E7280DCA-7776-4A73-B9B5-41FD77FC8799}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Duplicati.CommandLine.BackendTester</RootNamespace>
<AssemblyName>Duplicati.CommandLine.BackendTester</AssemblyName>
<FileAlignment>512</FileAlignment>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation />
<AssemblyOriginatorKeyFile>Duplicati.snk</AssemblyOriginatorKeyFile>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
<UseMSBuildEngine>false</UseMSBuildEngine>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Commandlineparameters>~/.config/dupl-unittest.txt</Commandlineparameters>
<EnvironmentVariables>
<EnvironmentVariables>
<Variable name="AUTOUPDATER_Duplicati_SKIP_UPDATE" value="1" />
</EnvironmentVariables>
</EnvironmentVariables>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="NonSeekableStream.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Duplicati.snk" />
</ItemGroup>
<ItemGroup>
<None Include="app.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj">
<Project>{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}</Project>
<Name>Duplicati.Library.Backend.AzureBlob</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{b20a7cee-9c5b-47b9-8b76-bc85adfe8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj">
<Project>{2cd5dbc3-3da6-432d-ba97-f0b8d24501c2}</Project>
<Name>Duplicati.Library.Backend.Jottacloud</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj">
<Project>{851a1cb8-3ceb-41b4-956f-34d760d2a8e5}</Project>
<Name>Duplicati.Library.Backend.Rclone</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj">
<Project>{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}</Project>
<Name>Duplicati.Library.Backend.SharePoint</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj">
<Project>{32a74526-3e5f-413a-8cb4-1efdad4c8b91}</Project>
<Name>Duplicati.Library.Backend.Sia</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj">
<Project>{ae035e01-c917-4f13-a35e-78f21c1a2f17}</Project>
<Name>Duplicati.Library.Backend.Storj</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj">
<Project>{9a04cb37-da72-4008-9703-3ac5191974e9}</Project>
<Name>Duplicati.Library.Backend.Tardigrade</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj">
<Project>{545dd6d4-9476-42d6-b51c-a28e000c489e}</Project>
<Name>Duplicati.Library.Backend.TencentCOS</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj">
<Project>{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}</Project>
<Name>Duplicati.Library.DynamicLoader</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Interface\Duplicati.Library.Interface.csproj">
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
<Name>Duplicati.Library.Interface</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj">
<Project>{7E119745-1F62-43F0-936C-F312A1912C0B}</Project>
<Name>Duplicati.Library.AutoUpdater</Name>
</ProjectReference>
<ProjectReference Include="..\..\License\Duplicati.License.csproj">
<Project>{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}</Project>
<Name>Duplicati.License</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj">
<Project>{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}</Project>
<Name>Duplicati.Library.Backend.OneDrive</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj">
<Project>{C0270709-2A40-43B5-8CF1-69581B9FA2A1}</Project>
<Name>Duplicati.Library.Backend.TahoeLAFS</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj">
<Project>{5489181D-950C-44AF-873C-45EB0A3B6BD2}</Project>
<Name>Duplicati.Library.Backend.GoogleServices</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj">
<Project>{D9E4E686-423C-48EC-A392-404E7C00860C}</Project>
<Name>Duplicati.Library.Backend.OpenStack</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\HubiC\Duplicati.Library.Backend.HubiC.csproj">
<Project>{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}</Project>
<Name>Duplicati.Library.Backend.HubiC</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj">
<Project>{1BFAE226-8364-4086-825C-BB83F6F3EE4C}</Project>
<Name>Duplicati.Library.Backend.CloudFiles</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\File\Duplicati.Library.Backend.File.csproj">
<Project>{FC9B7611-836F-4127-8B44-A7C31F506807}</Project>
<Name>Duplicati.Library.Backend.File</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj">
<Project>{F61679A9-E5DE-468A-B5A4-05F92D0143D2}</Project>
<Name>Duplicati.Library.Backend.FTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj">
<Project>{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}</Project>
<Name>Duplicati.Library.Backend.S3</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj">
<Project>{FF2BF37C-E502-4C98-BEA0-701671DDFA08}</Project>
<Name>Duplicati.Library.Backend.SSHv2</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj">
<Project>{BAE27510-8B5D-44B2-B33E-372A98908041}</Project>
<Name>Duplicati.Library.Backend.WEBDAV</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj">
<Project>{52826615-7964-47FE-B4B3-1B2DBDF605B9}</Project>
<Name>Duplicati.Library.Modules.Builtin</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OAuthHelper\Duplicati.Library.OAuthHelper.csproj">
<Project>{D4C37C33-5E73-4B56-B2C3-DC4A6BAA36BB}</Project>
<Name>Duplicati.Library.OAuthHelper</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Localization\Duplicati.Library.Localization.csproj">
<Project>{B68F2214-951F-4F78-8488-66E1ED3F50BF}</Project>
<Name>Duplicati.Library.Localization</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Logging\Duplicati.Library.Logging.csproj">
<Project>{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}</Project>
<Name>Duplicati.Library.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj">
<Project>{61C43D61-4368-4942-84A3-1EB623F4EF2A}</Project>
<Name>Duplicati.Library.Backend.Backblaze</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj">
<Project>{6643A5AE-AB38-453F-ADCE-408E35A81A83}</Project>
<Name>Duplicati.Library.Backend.Mega</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj">
<Project>{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}</Project>
<Name>Duplicati.Library.Backend.Box</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\AlternativeFTP\Duplicati.Library.Backend.AlternativeFTP.csproj">
<Project>{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}</Project>
<Name>Duplicati.Library.Backend.AlternativeFTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Common\Duplicati.Library.Common.csproj">
<Project>{D63E53E4-A458-4C2F-914D-92F715F58ACF}</Project>
<Name>Duplicati.Library.Common</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Compression\Duplicati.Library.Compression.csproj">
<Project>{19ECCE09-B5EB-406C-8C57-BAC66997D469}</Project>
<Name>Duplicati.Library.Compression</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\SQLiteHelper\Duplicati.Library.SQLiteHelper.csproj">
<Project>{2C838169-B187-4B09-8768-1C24C2521C8D}</Project>
<Name>Duplicati.Library.SQLiteHelper</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Description>A backend debugging tool for Duplicati</Description>
<AssemblyName>Duplicati.CommandLine.BackendTester.Implementation</AssemblyName>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj" />
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj" />
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj" />
<ProjectReference Include="..\..\License\Duplicati.License.csproj" />
<ProjectReference Include="..\..\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj" />
<ProjectReference Include="..\..\Library\Backend\OAuthHelper\Duplicati.Library.OAuthHelper.csproj" />
<ProjectReference Include="..\..\Library\Localization\Duplicati.Library.Localization.csproj" />
<ProjectReference Include="..\..\Library\Logging\Duplicati.Library.Logging.csproj" />
<ProjectReference Include="..\..\Library\Common\Duplicati.Library.Common.csproj" />
<ProjectReference Include="..\..\Library\SQLiteHelper\Duplicati.Library.SQLiteHelper.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Binary file not shown.
@@ -1,22 +1,24 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
// 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;
+143 -113
View File
@@ -1,22 +1,24 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF 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;
@@ -24,19 +26,13 @@ using Duplicati.Library.Interface;
using System.Linq;
using System.Globalization;
using System.Threading;
using Duplicati.Library.Utility;
using Duplicati.StreamUtil;
namespace Duplicati.CommandLine.BackendTester
{
class Program
public class Program
{
/// <summary>
/// Used to maintain a reference to initialized system settings.
/// </summary>
#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;
@@ -61,16 +57,12 @@ namespace Duplicati.CommandLine.BackendTester
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
public static void RealMain(string[] _args)
public static int Main(string[] _args)
{
try
{
Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, Library.AutoUpdater.PackageHelper.NamedExecutable.BackendTester);
if (_args.Length == 1)
{
try
@@ -78,8 +70,8 @@ namespace Duplicati.CommandLine.BackendTester
var p = Environment.ExpandEnvironmentVariables(_args[0]);
if (System.IO.File.Exists(p))
_args = (from x in System.IO.File.ReadLines(p)
where !string.IsNullOrWhiteSpace(x) && !x.Trim().StartsWith("#", StringComparison.Ordinal)
select x.Trim()
where !string.IsNullOrWhiteSpace(x) && !x.Trim().StartsWith("#", StringComparison.Ordinal)
select x.Trim()
).ToArray();
}
catch
@@ -105,13 +97,13 @@ namespace Duplicati.CommandLine.BackendTester
foreach (string s in lines)
Console.WriteLine(s);
return;
return 0;
}
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");
@@ -127,19 +119,22 @@ namespace Duplicati.CommandLine.BackendTester
{
Console.WriteLine("Starting run no {0}", i);
if (!Run(args, options, i == 0))
return;
return 1;
}
Console.WriteLine("Unittest complete!");
return 0;
}
catch (Exception ex)
{
Console.WriteLine("Unittest failed: " + ex);
}
return 1;
}
static bool Run(List<string> args, Dictionary<string, string> options, bool first)
{
Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options);
var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options);
if (backend == null)
{
Console.WriteLine("Unsupported backend");
@@ -148,23 +143,21 @@ namespace Duplicati.CommandLine.BackendTester
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<Library.Interface.IGenericModule> loadedModules = new List<IGenericModule>();
foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules)
var loadedModules = new List<IGenericModule>();
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);
@@ -176,7 +169,7 @@ namespace Duplicati.CommandLine.BackendTester
IEnumerable<Library.Interface.IFileEntry> curlist = null;
try
{
backend.Test();
backend.TestAsync(CancellationToken.None).Await();
curlist = backend.List();
}
catch (FolderMissingException)
@@ -185,7 +178,7 @@ namespace Duplicati.CommandLine.BackendTester
{
try
{
backend.CreateFolder();
backend.CreateFolderAsync(CancellationToken.None).Await();
curlist = backend.List();
}
catch (Exception ex)
@@ -205,28 +198,35 @@ namespace Duplicati.CommandLine.BackendTester
if (Library.Utility.Utility.ParseBoolOption(options, "force"))
{
Console.WriteLine("Auto clean, removing file: {0}", fe.Name);
backend.Delete(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($"*** Remote folder contains {fileCount} file(s), aborting");
Console.WriteLine($"*** First {filenames.Count} file(s): {Environment.NewLine}{string.Join(Environment.NewLine, filenames)}");
if (fileCount > filenames.Count)
Console.WriteLine($"*** ... 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)
{
@@ -236,8 +236,8 @@ namespace Duplicati.CommandLine.BackendTester
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)
{
@@ -247,6 +247,17 @@ namespace Duplicati.CommandLine.BackendTester
throttleDownload = Duplicati.Library.Utility.Sizeparser.ParseSize(throttleDownloadString, "kb");
}
var readWriteTimeout = (int)TimeSpan.FromSeconds(10).TotalMilliseconds;
if (backend is ITimeoutExemptBackend)
readWriteTimeout = Timeout.Infinite;
// 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"))
@@ -259,21 +270,26 @@ 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<TempFile> files = new List<TempFile>();
var files = new List<TempFile>();
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));
}
@@ -281,16 +297,16 @@ namespace Duplicati.CommandLine.BackendTester
if (!skipOverwriteTest)
{
Console.WriteLine("Uploading wrong files ...");
using (Library.Utility.TempFile dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd)))
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);
}
}
@@ -298,12 +314,11 @@ namespace Duplicati.CommandLine.BackendTester
Console.WriteLine("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;
IRenameEnabledBackend renameEnabledBackend = backend as IRenameEnabledBackend;
if (renameEnabledBackend != null)
if (backend is IRenameEnabledBackend renameEnabledBackend)
{
// Rename the second file in the list, if there are more than one. If not, just do the first one.
int renameIndex = files.Count > 1 ? 1 : 0;
@@ -313,18 +328,24 @@ namespace Duplicati.CommandLine.BackendTester
Console.WriteLine("Renaming file {0} from {1} to {2}", renameIndex, originalRenamedFile.remotefilename, renamedFileNewName);
renameEnabledBackend.Rename(originalRenamedFile.remotefilename, renamedFileNewName);
renameEnabledBackend.RenameAsync(originalRenamedFile.remotefilename, renamedFileNewName, CancellationToken.None).Await();
files[renameIndex] = new TempFile(renamedFileNewName, originalRenamedFile.localfilename, originalRenamedFile.hash, originalRenamedFile.length);
}
if (waitAfterUpload > TimeSpan.Zero)
{
Console.WriteLine("Waiting {0} after upload", waitAfterUpload);
Thread.Sleep(waitAfterUpload);
}
Console.WriteLine("Verifying file list ...");
curlist = backend.List();
foreach (Library.Interface.IFileEntry fe in curlist)
foreach (var fe in curlist)
if (!fe.IsFolder)
{
bool found = false;
foreach (TempFile tx in files)
foreach (var tx in files)
if (tx.remotefilename == fe.Name)
{
if (tx.found)
@@ -349,7 +370,7 @@ namespace Duplicati.CommandLine.BackendTester
}
}
foreach (TempFile tx in files)
foreach (var tx in files)
if (!tx.found)
Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename);
@@ -357,7 +378,7 @@ namespace Duplicati.CommandLine.BackendTester
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);
@@ -366,13 +387,14 @@ namespace Duplicati.CommandLine.BackendTester
{
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.Get(files[i].remotefilename, nss);
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.Get(files[i].remotefilename, cf);
backend.GetAsync(files[i].remotefilename, cf, CancellationToken.None).Await();
e = null;
}
@@ -388,7 +410,7 @@ namespace Duplicati.CommandLine.BackendTester
Console.Write("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))
@@ -403,15 +425,21 @@ namespace Duplicati.CommandLine.BackendTester
Console.WriteLine("Deleting files...");
foreach (TempFile tx in files)
try { backend.Delete(tx.remotefilename); }
foreach (var tx in files)
try { backend.DeleteAsync(tx.remotefilename, CancellationToken.None).Await(); }
catch (Exception ex)
{
Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex);
}
if (waitAfterDelete > TimeSpan.Zero)
{
Console.WriteLine("Waiting {0} after delete", waitAfterDelete);
Thread.Sleep(waitAfterDelete);
}
curlist = backend.List();
foreach (Library.Interface.IFileEntry fe in curlist)
foreach (var fe in curlist)
if (!fe.IsFolder)
{
Console.WriteLine("*** Remote folder contains {0} after cleanup", fe.Name);
@@ -419,12 +447,12 @@ namespace Duplicati.CommandLine.BackendTester
// Test some error cases
Console.WriteLine("Checking retrieval of non-existent file...");
bool caughtExpectedException = false;
var caughtExpectedException = false;
try
{
using (Duplicati.Library.Utility.TempFile tempFile = new Duplicati.Library.Utility.TempFile())
using (var tempFile = new Duplicati.Library.Utility.TempFile())
{
backend.Get(string.Format("NonExistentFile-{0}", Guid.NewGuid()), tempFile.Name);
backend.GetAsync(string.Format("NonExistentFile-{0}", Guid.NewGuid()), tempFile.Name, CancellationToken.None).Await();
}
}
catch (FileMissingException)
@@ -444,15 +472,14 @@ namespace Duplicati.CommandLine.BackendTester
}
// Test quota retrieval
IQuotaEnabledBackend quotaEnabledBackend = backend as IQuotaEnabledBackend;
if (quotaEnabledBackend != null)
if (backend is IQuotaEnabledBackend quotaEnabledBackend)
{
Console.WriteLine("Checking quota...");
IQuotaInfo quota = null;
bool noException;
try
{
quota = quotaEnabledBackend.Quota;
quota = quotaEnabledBackend.GetQuotaInfoAsync(CancellationToken.None).Await();
noException = true;
}
catch (Exception ex)
@@ -479,7 +506,7 @@ namespace Duplicati.CommandLine.BackendTester
Console.WriteLine("Checking DNS names used by this backend...");
try
{
string[] dnsNames = backend.DNSName;
var dnsNames = backend.GetDNSNamesAsync(CancellationToken.None).Await();
if (dnsNames != null)
{
foreach (string dnsName in dnsNames)
@@ -499,7 +526,7 @@ namespace Duplicati.CommandLine.BackendTester
}
finally
{
foreach (Library.Interface.IGenericModule m in loadedModules)
foreach (var m in loadedModules)
if (m is IDisposable disposable)
disposable.Dispose();
}
@@ -507,7 +534,7 @@ namespace Duplicati.CommandLine.BackendTester
return true;
}
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));
Exception e = null;
@@ -516,13 +543,14 @@ namespace Duplicati.CommandLine.BackendTester
{
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).Wait();
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).Wait();
backend.PutAsync(remotefilename, localfilename, CancellationToken.None).Await();
e = null;
}
@@ -596,11 +624,13 @@ namespace Duplicati.CommandLine.BackendTester
new CommandLineArgument("max-file-size", CommandLineArgument.ArgumentType.Size, "The maximum allowed file size", "File sizes are chosen at random, this value is the upper bound", "50mb"),
new CommandLineArgument("min-filename-length", CommandLineArgument.ArgumentType.Integer, "The minimum allowed filename length", "File name lengths are chosen at random, this value is the lower bound", "5"),
new CommandLineArgument("max-filename-length", CommandLineArgument.ArgumentType.Integer, "The minimum allowed filename length", "File name lengths are chosen at random, this value is the upper bound", "80"),
new CommandLineArgument("trim-filename-spaces", CommandLineArgument.ArgumentType.Boolean, "Trims whitespace from filenames", "A value that indicates if whitespace should be trimmed from the ends of randomly generated filenames", "false"),
new CommandLineArgument("auto-create-folder", CommandLineArgument.ArgumentType.Boolean, "Allows automatic folder creation", "A value that indicates if missing folders are created automatically", "false"),
new CommandLineArgument("skip-overwrite-test", CommandLineArgument.ArgumentType.Boolean, "Bypasses 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, "Removes 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, "Activates file deletion", "A value that indicates if existing files should really be deleted when using auto-clean", "false"),
new CommandLineArgument("trim-filename-spaces", CommandLineArgument.ArgumentType.Boolean, "Trim whitespace from filenames", "A value that indicates if whitespace should be trimmed from the ends of randomly generated filenames", "false"),
new CommandLineArgument("auto-create-folder", CommandLineArgument.ArgumentType.Boolean, "Allow automatic folder creation", "A value that indicates if missing folders are created automatically", "false"),
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"),
});
}
}
@@ -1,55 +0,0 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("CommandLine.BackendTester")]
[assembly: AssemblyDescription("A backend debugging tool for Duplicati")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Duplicati Team")]
[assembly: AssemblyProduct("CommandLine.BackendTester")]
[assembly: AssemblyCopyright("Copyright © Duplicati Team 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("53df9eb0-a6e8-49a2-838d-b2a2c4a8d41f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyFileVersion("2.0.0.7")]
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion="3.0.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.3" newVersion="4.2.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -1,171 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<RootNamespace>Duplicati.CommandLine.BackendTool</RootNamespace>
<AssemblyName>Duplicati.CommandLine.BackendTool</AssemblyName>
<AssemblyOriginatorKeyFile>..\Duplicati.snk</AssemblyOriginatorKeyFile>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
<UseMSBuildEngine>false</UseMSBuildEngine>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<Prefer32Bit>false</Prefer32Bit>
<EnvironmentVariables>
<EnvironmentVariables>
<Variable name="AUTOUPDATER_Duplicati_SKIP_UPDATE" value="1" />
</EnvironmentVariables>
</EnvironmentVariables>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\..\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj">
<Project>{1BFAE226-8364-4086-825C-BB83F6F3EE4C}</Project>
<Name>Duplicati.Library.Backend.CloudFiles</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj">
<Project>{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}</Project>
<Name>Duplicati.Library.Backend.AzureBlob</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\File\Duplicati.Library.Backend.File.csproj">
<Project>{FC9B7611-836F-4127-8B44-A7C31F506807}</Project>
<Name>Duplicati.Library.Backend.File</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj">
<Project>{F61679A9-E5DE-468A-B5A4-05F92D0143D2}</Project>
<Name>Duplicati.Library.Backend.FTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj">
<Project>{2cd5dbc3-3da6-432d-ba97-f0b8d24501c2}</Project>
<Name>Duplicati.Library.Backend.Jottacloud</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj">
<Project>{851a1cb8-3ceb-41b4-956f-34d760d2a8e5}</Project>
<Name>Duplicati.Library.Backend.Rclone</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj">
<Project>{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}</Project>
<Name>Duplicati.Library.Backend.S3</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj">
<Project>{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}</Project>
<Name>Duplicati.Library.Backend.SharePoint</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj">
<Project>{32a74526-3e5f-413a-8cb4-1efdad4c8b91}</Project>
<Name>Duplicati.Library.Backend.Sia</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj">
<Project>{FF2BF37C-E502-4C98-BEA0-701671DDFA08}</Project>
<Name>Duplicati.Library.Backend.SSHv2</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj">
<Project>{C0270709-2A40-43B5-8CF1-69581B9FA2A1}</Project>
<Name>Duplicati.Library.Backend.TahoeLAFS</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj">
<Project>{ae035e01-c917-4f13-a35e-78f21c1a2f17}</Project>
<Name>Duplicati.Library.Backend.Storj</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj">
<Project>{9a04cb37-da72-4008-9703-3ac5191974e9}</Project>
<Name>Duplicati.Library.Backend.Tardigrade</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\TencentCOS\Duplicati.Library.Backend.TencentCOS.csproj">
<Project>{545dd6d4-9476-42d6-b51c-a28e000c489e}</Project>
<Name>Duplicati.Library.Backend.TencentCOS</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj">
<Project>{BAE27510-8B5D-44B2-B33E-372A98908041}</Project>
<Name>Duplicati.Library.Backend.WEBDAV</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj">
<Project>{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}</Project>
<Name>Duplicati.Library.DynamicLoader</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Interface\Duplicati.Library.Interface.csproj">
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
<Name>Duplicati.Library.Interface</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj">
<Project>{7E119745-1F62-43F0-936C-F312A1912C0B}</Project>
<Name>Duplicati.Library.AutoUpdater</Name>
</ProjectReference>
<ProjectReference Include="..\..\License\Duplicati.License.csproj">
<Project>{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}</Project>
<Name>Duplicati.License</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj">
<Project>{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}</Project>
<Name>Duplicati.Library.Backend.OneDrive</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj">
<Project>{5489181D-950C-44AF-873C-45EB0A3B6BD2}</Project>
<Name>Duplicati.Library.Backend.GoogleServices</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj">
<Project>{D9E4E686-423C-48EC-A392-404E7C00860C}</Project>
<Name>Duplicati.Library.Backend.OpenStack</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\HubiC\Duplicati.Library.Backend.HubiC.csproj">
<Project>{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}</Project>
<Name>Duplicati.Library.Backend.HubiC</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj">
<Project>{61C43D61-4368-4942-84A3-1EB623F4EF2A}</Project>
<Name>Duplicati.Library.Backend.Backblaze</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj">
<Project>{6643A5AE-AB38-453F-ADCE-408E35A81A83}</Project>
<Name>Duplicati.Library.Backend.Mega</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj">
<Project>{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}</Project>
<Name>Duplicati.Library.Backend.Box</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\AlternativeFTP\Duplicati.Library.Backend.AlternativeFTP.csproj">
<Project>{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}</Project>
<Name>Duplicati.Library.Backend.AlternativeFTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>Duplicati.CommandLine.BackendTool.Implementation</AssemblyName>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj" />
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj" />
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj" />
<ProjectReference Include="..\..\License\Duplicati.License.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
+63 -42
View File
@@ -1,26 +1,30 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
// Copyright (C) 2024, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF 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;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
namespace Duplicati.CommandLine.BackendTool
@@ -31,17 +35,13 @@ namespace Duplicati.CommandLine.BackendTool
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
public static int RealMain(string[] _args)
public static int Main(string[] _args)
{
bool debugoutput = false;
try
{
Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, Library.AutoUpdater.PackageHelper.NamedExecutable.BackendTool);
List<string> args = new List<string>(_args);
Dictionary<string, string> options = Library.Utility.CommandLineParser.ExtractOptions(args);
@@ -80,8 +80,8 @@ namespace Duplicati.CommandLine.BackendTool
{
Console.WriteLine("Unsupported command: {0}", args[0]);
Console.WriteLine();
}
}
Console.WriteLine("Usage: <command> <protocol>://<username>:<password>@<path> [filename]");
Console.WriteLine("Example: LIST ftp://user:pass@server/folder");
Console.WriteLine();
@@ -90,19 +90,41 @@ namespace Duplicati.CommandLine.BackendTool
return 200;
}
using(var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[1], options))
var modules = (from n in Library.DynamicLoader.GenericLoader.Modules
where n is Library.Interface.IConnectionModule
select n).ToArray();
var uri = new Library.Utility.Uri(args[1]);
var qp = uri.QueryParameters;
var backendOpts = new Dictionary<string, string>();
foreach (var k in qp.Keys.Cast<string>())
backendOpts[k] = qp[k];
foreach (var k in backendOpts.Keys)
{
options.Remove(k);
}
foreach (var n in modules)
{
n.Configure(options);
n.Configure(backendOpts);
}
using (var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[1], options))
{
if (backend == null)
throw new UserInformationException("Backend not supported", "InvalidBackend");
if (command == "list")
{
if (args.Count != 2)
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.List())
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;
@@ -112,8 +134,8 @@ namespace Duplicati.CommandLine.BackendTool
if (args.Count != 2)
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
backend.CreateFolder();
backend.CreateFolderAsync(CancellationToken.None).Await();
return 0;
}
else if (command == "delete")
@@ -122,8 +144,8 @@ namespace Duplicati.CommandLine.BackendTool
throw new UserInformationException("DELETE requires a filename argument", "BackendToolDeleteRequiresAnArgument");
if (args.Count > 3)
throw new Exception(string.Format("too many arguments: {0}", string.Join(",", args)));
backend.Delete(Path.GetFileName(args[2]));
backend.DeleteAsync(Path.GetFileName(args[2]), CancellationToken.None).Await();
return 0;
}
else if (command == "get")
@@ -134,25 +156,24 @@ namespace Duplicati.CommandLine.BackendTool
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
if (File.Exists(args[2]))
throw new UserInformationException("File already exists, not overwriting!", "BackendToolFileAlreadyExists");
backend.Get(Path.GetFileName(args[2]), args[2]);
backend.GetAsync(Path.GetFileName(args[2]), args[2], CancellationToken.None).Await();
return 0;
}
else if (command == "put")
{
if (args.Count < 3)
throw new UserInformationException("PUT requires a filename argument","BackendToolPutRequiresAndArgument");
throw new UserInformationException("PUT requires a filename argument", "BackendToolPutRequiresAndArgument");
if (args.Count > 3)
throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments");
backend.PutAsync(Path.GetFileName(args[2]), args[2], CancellationToken.None).Wait();
backend.PutAsync(Path.GetFileName(args[2]), args[2], CancellationToken.None).Await();
return 0;
}
throw new Exception("Internal error");
}
}
catch (Exception ex)
{
@@ -1,44 +0,0 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Duplicati.CommandLine.BackendTool")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("2.0.0.7")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.3" newVersion="4.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion="3.0.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -1,25 +1,31 @@
// Copyright (C) 2015, The Duplicati Team
// 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.
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Utility;
@@ -34,8 +40,9 @@ namespace Duplicati.CommandLine
private readonly System.Threading.ManualResetEvent m_readyEvent;
private readonly System.Threading.ManualResetEvent m_finishEvent;
private readonly ConsoleOutput m_output;
private System.Threading.Thread m_thread;
private readonly TimeSpan m_updateFrequency;
private Task m_task;
private readonly CancellationTokenSource m_cancellationTokenSource;
public PeriodicOutput(ConsoleOutput messageSink, TimeSpan updateFrequency)
{
@@ -43,10 +50,8 @@ namespace Duplicati.CommandLine
m_readyEvent = new System.Threading.ManualResetEvent(false);
m_finishEvent = new System.Threading.ManualResetEvent(false);
m_updateFrequency = updateFrequency;
m_thread = new System.Threading.Thread(this.ThreadMain);
m_thread.IsBackground = true;
m_thread.Name = "Periodic console writer";
m_thread.Start();
m_cancellationTokenSource = new CancellationTokenSource();
m_task = Task.Run(() => this.ThreadMain(m_cancellationTokenSource.Token), m_cancellationTokenSource.Token);
}
public void SetReady() { m_readyEvent.Set(); }
@@ -54,13 +59,13 @@ namespace Duplicati.CommandLine
public bool Join(TimeSpan wait)
{
if (m_thread != null)
return m_thread.Join(wait);
if (m_task != null)
return this.m_task.Wait(wait);
return true;
}
private void ThreadMain()
private void ThreadMain(CancellationToken cancellationToken)
{
m_readyEvent.WaitOne();
if (m_finishEvent.WaitOne(TimeSpan.FromMilliseconds(10), true))
@@ -69,7 +74,7 @@ namespace Duplicati.CommandLine
var last_count = -1L;
var finished = false;
while (true)
while (!cancellationToken.IsCancellationRequested)
{
float progress;
long filesprocessed;
@@ -106,22 +111,22 @@ namespace Duplicati.CommandLine
public void Dispose()
{
if (m_thread != null)
if (m_task != null)
{
try
{
m_finishEvent.Set();
m_readyEvent.Set();
if (m_thread != null && m_thread.IsAlive)
if (m_task != null)
{
m_thread.Abort();
m_thread.Join(500);
m_cancellationTokenSource.Cancel();
m_task.Wait(500);
}
}
finally
{
m_thread = null;
m_task = null;
}
}
}
@@ -129,13 +134,15 @@ namespace Duplicati.CommandLine
public static int Examples(TextWriter outwriter, Action<Duplicati.Library.Main.Controller> setup, List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
{
Duplicati.CommandLine.Help.PrintUsage(outwriter, "example", options);
using (new Duplicati.Library.Main.LocaleChange(options))
Duplicati.CommandLine.Help.PrintUsage(outwriter, "example", options);
return 0;
}
public static int Help(TextWriter outwriter, Action<Duplicati.Library.Main.Controller> setup, List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter)
{
Duplicati.CommandLine.Help.PrintUsage(outwriter, args.Count > 1? args[1] : "help", options);
using (new Duplicati.Library.Main.LocaleChange(options))
Duplicati.CommandLine.Help.PrintUsage(outwriter, args.Count > 1 ? args[1] : "help", options);
return 0;
}
@@ -170,8 +177,8 @@ namespace Duplicati.CommandLine
options["no-encryption"] = "true";
}
using(var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller(backend, options, console))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(backend, options, console))
{
setup(i);
i.ListAffected(args, res =>
@@ -254,7 +261,7 @@ namespace Duplicati.CommandLine
if (!containsSeparators && arg.StartsWith("@", StringComparison.Ordinal))
{
// Convert to Regexp filter and prefix with ".*/"
return $"[.*{Utility.ConvertLiteralToRegExp(Util.DirectorySeparatorString + arg.Substring(1))}]";
return $"[.*{Library.Utility.Utility.ConvertLiteralToRegExp(Util.DirectorySeparatorString + arg.Substring(1))}]";
}
else if (!containsSeparators && !containsWildcards && !arg.StartsWith("[", StringComparison.Ordinal))
{
@@ -285,7 +292,7 @@ namespace Duplicati.CommandLine
if (endsWithSeparator && arg.StartsWith("@", StringComparison.Ordinal))
{
// Convert to Regexp filter and suffix with ".*"
return $"[{Utility.ConvertLiteralToRegExp(arg.Substring(1))}.*]";
return $"[{Library.Utility.Utility.ConvertLiteralToRegExp(arg.Substring(1))}.*]";
}
else if (endsWithSeparator && !containsWildcards && !arg.StartsWith("[", StringComparison.Ordinal))
{
@@ -319,7 +326,7 @@ namespace Duplicati.CommandLine
filter = new Duplicati.Library.Utility.FilterExpression();
using (var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller(args[0], options, console))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
var backend = args[0];
@@ -407,7 +414,7 @@ namespace Duplicati.CommandLine
{
outwriter.WriteLine("Listing filesets:");
foreach(var e in res.Filesets)
foreach (var e in res.Filesets)
{
if (e.FileCount >= 0)
outwriter.WriteLine("{0}\t: {1} ({2} files, {3})", e.Version, e.Time, e.FileCount, Library.Utility.Utility.FormatSizeString(e.FileSizes));
@@ -439,10 +446,10 @@ namespace Duplicati.CommandLine
else
{
outwriter.WriteLine("Listing files and versions:");
foreach(var e in res.Files)
foreach (var e in res.Files)
{
outwriter.WriteLine(e.Path);
foreach(var nx in res.Filesets.Zip(e.Sizes, (a, b) => new { Index = a.Version, Time = a.Time, Size = b } ))
foreach (var nx in res.Filesets.Zip(e.Sizes, (a, b) => new { Index = a.Version, Time = a.Time, Size = b }))
outwriter.WriteLine("{0}\t: {1} {2}", nx.Index, nx.Time, nx.Size < 0 ? " - " : Library.Utility.Utility.FormatSizeString(nx.Size));
outwriter.WriteLine();
@@ -466,7 +473,7 @@ namespace Duplicati.CommandLine
}
using (var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller(args[0], options, console))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
args.RemoveAt(0);
@@ -483,7 +490,7 @@ namespace Duplicati.CommandLine
else
outwriter.WriteLine(Strings.Program.DeletedBackups);
foreach(var f in res.DeletedSets)
foreach (var f in res.DeletedSets)
outwriter.WriteLine("{0}: {1}", f.Item1, f.Item2);
}
}
@@ -520,7 +527,7 @@ namespace Duplicati.CommandLine
args = SuffixArgsWithAsterisk(PrefixArgsWithAsterisk(args)).ToList();
using(var output = new ConsoleOutput(outwriter, options))
using (var output = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(backend, options, output))
{
output.MessageEvent(string.Format("Restore started at {0}", DateTime.Now));
@@ -575,15 +582,6 @@ namespace Duplicati.CommandLine
output.MessageEvent(string.Format("Restored {0} ({1}) files to {2}", res.RestoredFiles, Library.Utility.Utility.FormatSizeString(res.SizeOfRestoredFiles), string.IsNullOrEmpty(restorePath) ? "original path" : restorePath));
output.MessageEvent(string.Format("Duration of restore: {0:hh\\:mm\\:ss}", res.Duration));
if (res.RestoredFiles > 0 && !Library.Main.Utility.SuppressDonationMessages)
{
output.MessageEvent("***********************************************");
output.MessageEvent("Did we help save your files? If so, please support Duplicati with a donation. We suggest 10€ for private use and 100€ for commercial use.");
output.MessageEvent("");
output.MessageEvent("https://www.duplicati.com/donate/");
output.MessageEvent("***********************************************");
}
if (output.FullResults)
Library.Utility.Utility.PrintSerializeObject(res, outwriter);
@@ -664,7 +662,7 @@ namespace Duplicati.CommandLine
}
var parsedStats = result.BackendStatistics as Duplicati.Library.Interface.IParsedBackendStatistics;
output.MessageEvent(string.Format(" Duration of backup: {0:hh\\:mm\\:ss}", result.Duration));
output.MessageEvent(string.Format(" Duration of backup: {0:c}", result.Duration));
if (parsedStats != null)
{
if (parsedStats.KnownFileCount > 0)
@@ -721,7 +719,7 @@ namespace Duplicati.CommandLine
if (args.Count != 1)
return PrintWrongNumberOfArguments(outwriter, args, 1);
using(var console = new ConsoleOutput(outwriter, options))
using (var console = new ConsoleOutput(outwriter, options))
using (var i = new Library.Main.Controller(args[0], options, console))
{
setup(i);
@@ -770,7 +768,7 @@ namespace Duplicati.CommandLine
}
else
{
foreach(var n in result.Verifications)
foreach (var n in result.Verifications)
{
var changecount = n.Value.Count();
if (changecount == 0)
@@ -1005,7 +1003,7 @@ namespace Duplicati.CommandLine
}
using (var console = new ConsoleOutput(outwriter, options))
using(var i = new Library.Main.Controller("dummy://", options, console))
using (var i = new Library.Main.Controller("dummy://", options, console))
{
setup(i);
var result = i.TestFilter(args.ToArray(), filter);
@@ -1,20 +1,24 @@
// Copyright (C) 2015, The Duplicati Team
// 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.
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using System.IO;
@@ -59,19 +63,19 @@ namespace Duplicati.CommandLine
switch (action)
{
case BackendActionType.Put:
Output.WriteLine(" Uploading file ({0}) ...", Library.Utility.Utility.FormatSizeString(size));
Output.WriteLine(" Uploading file {0} ({1}) ...", path, Library.Utility.Utility.FormatSizeString(size));
break;
case BackendActionType.Get:
Output.WriteLine(" Downloading file ({0}) ...", size < 0 ? "unknown" : Library.Utility.Utility.FormatSizeString(size));
Output.WriteLine(" Downloading file {0} ({1}) ...", path, size < 0 ? "unknown" : Library.Utility.Utility.FormatSizeString(size));
break;
case BackendActionType.List:
Output.WriteLine(" Listing remote folder ...");
Output.WriteLine(" Listing remote folder {0}...", path);
break;
case BackendActionType.CreateFolder:
Output.WriteLine(" Creating remote folder ...");
Output.WriteLine(" Creating remote folder {0} ...", path);
break;
case BackendActionType.Delete:
Output.WriteLine(" Deleting file {0}{1} ...", path, size < 0 ? "" : (" (" + Library.Utility.Utility.FormatSizeString(size) + ")"));
Output.WriteLine(" Deleting file {0} {1} ...", path, size < 0 ? "" : (" (" + Library.Utility.Utility.FormatSizeString(size) + ")"));
break;
}
}
@@ -79,7 +83,7 @@ namespace Duplicati.CommandLine
public void SetBackendProgress(IBackendProgress progress)
{
// Do nothing. Implementation needed for IMessageSink interface.
// Do nothing. Implementation needed for IMessageSink interface.
}
public void SetOperationProgress(IOperationProgress progress)
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Description>A commandline version of Duplicati</Description>
<AssemblyName>Duplicati.CommandLine.Implementation</AssemblyName>
<ApplicationIcon>TrayWarning.ico</ApplicationIcon>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj" />
<ProjectReference Include="..\..\Library\Common\Duplicati.Library.Common.csproj" />
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj" />
<ProjectReference Include="..\..\Library\Logging\Duplicati.Library.Logging.csproj" />
<ProjectReference Include="..\..\Library\Main\Duplicati.Library.Main.csproj" />
<ProjectReference Include="..\..\License\Duplicati.License.csproj" />
<ProjectReference Include="..\BackendTool\Duplicati.CommandLine.BackendTool.csproj" />
<ProjectReference Include="..\BackendTester\Duplicati.CommandLine.BackendTester.csproj" />
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj" />
<ProjectReference Include="..\..\Library\Localization\Duplicati.Library.Localization.csproj" />
<ProjectReference Include="..\RecoveryTool\Duplicati.CommandLine.RecoveryTool.csproj" />
<ProjectReference Include="..\..\Library\UsageReporter\Duplicati.Library.UsageReporter.csproj" />
<ProjectReference Include="..\..\Tools\Duplicati.Tools.csproj" />
<ProjectReference Include="..\..\Library\Crashlog\Duplicati.Library.Crashlog.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="help.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
@@ -1,9 +1,32 @@
using System;
// 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.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Duplicati.Library.Common;
using Duplicati.Library.AutoUpdater;
using Duplicati.Library.DynamicLoader;
using FilterGroup = Duplicati.Library.Utility.FilterGroup;
namespace Duplicati.CommandLine
@@ -11,18 +34,17 @@ namespace Duplicati.CommandLine
public static class Help
{
private static readonly Dictionary<string, string> _document;
private const string RESOURCE_NAME = "Duplicati.CommandLine.help.txt";
private const string RESOURCE_NAME = "help.txt";
private static readonly System.Text.RegularExpressions.Regex NAMEDOPTION_REGEX = new System.Text.RegularExpressions.Regex("\\%OPTION\\:(?<name>[^\\%]+)\\%");
static Help()
{
_document = new Dictionary<string,string>(StringComparer.OrdinalIgnoreCase);
using (System.IO.StreamReader sr = new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(RESOURCE_NAME)))
_document = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
using (var sr = new StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(Help), RESOURCE_NAME)))
{
List<string> keywords = new List<string>();
StringBuilder sb = new StringBuilder();
foreach(var line in sr.ReadToEnd().Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None))
foreach (var line in sr.ReadToEnd().Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.None))
{
if (line.Trim().StartsWith("#", StringComparison.Ordinal))
continue;
@@ -32,14 +54,14 @@ namespace Duplicati.CommandLine
if (sb.Length > 0)
{
string s = sb.ToString();
foreach(var k in keywords)
foreach (var k in keywords)
_document[k] = s;
keywords.Clear();
sb.Clear();
}
string[] elems = line.Split(new string[] {" ", "\t"}, StringSplitOptions.RemoveEmptyEntries);
string[] elems = line.Split(new string[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
if (elems.Length >= 2 && string.Equals(elems[elems.Length - 2], "help", StringComparison.OrdinalIgnoreCase))
keywords.Add(elems[elems.Length - 1]);
else if (elems.Length == 3 && string.Equals(elems[elems.Length - 1], "help", StringComparison.OrdinalIgnoreCase))
@@ -54,7 +76,7 @@ namespace Duplicati.CommandLine
if (sb.Length > 0)
{
string s = sb.ToString();
foreach(var k in keywords)
foreach (var k in keywords)
_document[k] = s;
}
}
@@ -67,8 +89,8 @@ namespace Duplicati.CommandLine
//Force translation off if this is from the commandline
if (Program.FROM_COMMANDLINE)
{
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
}
}
catch
@@ -88,20 +110,19 @@ namespace Duplicati.CommandLine
topic = "include";
}
if (_document.ContainsKey(topic))
{
string tp = _document[topic];
Library.Main.Options opts = new Library.Main.Options(new Dictionary<string, string>());
tp = tp.Replace("%CLI_EXE%", PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.CommandLine));
tp = tp.Replace("%VERSION%", License.VersionNumbers.Version);
tp = tp.Replace("%BACKENDS%", string.Join(", ", Library.DynamicLoader.BackendLoader.Keys));
tp = tp.Replace("%MONO%", Library.Utility.Utility.IsMono ? "mono " : "");
tp = tp.Replace("%APP_PATH%", System.IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location));
tp = tp.Replace("%APP_PATH%", Path.Combine(UpdaterManager.INSTALLATIONDIR, PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.CommandLine)));
tp = tp.Replace("%PATH_SEPARATOR%", System.IO.Path.PathSeparator.ToString());
tp = tp.Replace("%EXAMPLE_SOURCE_PATH%", Platform.IsClientPosix ? "/source" : @"D:\source");
tp = tp.Replace("%EXAMPLE_SOURCE_FILE%", Platform.IsClientPosix ? "/source/myfile.txt" : @"D:\source\file.txt");
tp = tp.Replace("%EXAMPLE_RESTORE_PATH%", Platform.IsClientPosix ? "/restore" : @"D:\restore");
tp = tp.Replace("%EXAMPLE_SOURCE_PATH%", !OperatingSystem.IsWindows() ? "/source" : @"D:\source");
tp = tp.Replace("%EXAMPLE_SOURCE_FILE%", !OperatingSystem.IsWindows() ? "/source/myfile.txt" : @"D:\source\file.txt");
tp = tp.Replace("%EXAMPLE_RESTORE_PATH%", !OperatingSystem.IsWindows() ? "/restore" : @"D:\restore");
tp = tp.Replace("%ENCRYPTIONMODULES%", string.Join(", ", Library.DynamicLoader.EncryptionLoader.Keys));
tp = tp.Replace("%COMPRESSIONMODULES%", string.Join(", ", Library.DynamicLoader.CompressionLoader.Keys));
tp = tp.Replace("%DEFAULTENCRYPTIONMODULE%", opts.EncryptionModule);
@@ -111,7 +132,7 @@ namespace Duplicati.CommandLine
tp = tp.Replace("%FILTER_GROUPS_SHORT%", string.Join(Environment.NewLine + " ", metaGroupNames.Concat(Enum.GetNames(typeof(FilterGroup)).Except(metaGroupNames, StringComparer.OrdinalIgnoreCase).OrderBy(x => x, StringComparer.OrdinalIgnoreCase)).Select(group => "{" + group + "}")));
tp = tp.Replace("%FILTER_GROUPS_LONG%", Library.Utility.FilterGroups.GetOptionDescriptions(4, true));
if (Platform.IsClientWindows)
if (OperatingSystem.IsWindows())
{
// These properties are only valid for Windows
tp = tp.Replace("%EXAMPLE_WILDCARD_DRIVE_SOURCE_PATH%", @"*:\source");
@@ -184,7 +205,7 @@ namespace Duplicati.CommandLine
tp = tp.Replace("%ALLOPTIONS%", string.Join(Environment.NewLine, lines.ToArray()));
}
if (tp.Contains("%MODULEOPTIONS%"))
{
//Figure out which module we are in
@@ -239,8 +260,20 @@ namespace Duplicati.CommandLine
}
}
if (tp.Contains("%SECRETPROVIDERS%"))
{
var lines = new List<string>();
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);
tp = NAMEDOPTION_REGEX.Replace(tp, new Matcher().MathEvaluator);
PrintFormatted(outwriter, tp.Split(new string[] { Environment.NewLine }, StringSplitOptions.None));
}
@@ -279,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);
@@ -374,18 +415,30 @@ namespace Duplicati.CommandLine
lines.Add("");
}
private static void PrintSecretProvider(Duplicati.Library.Interface.ISecretProvider mod, List<string> 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<string> lines)
{
int windowWidth = 80;
try
try
{
// This can go wrong if we have no attached console
if (outwriter == Console.Out)
windowWidth = Math.Max(12, Console.WindowWidth == 0 ? 80 : Console.WindowWidth);
windowWidth = Math.Max(12, Console.WindowWidth == 0 ? 80 : Console.WindowWidth);
}
catch { }
foreach (string s in lines)
{
if (string.IsNullOrEmpty(s) || s.Trim().Length == 0)
@@ -411,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)
@@ -451,7 +508,7 @@ namespace Duplicati.CommandLine
foreach (Duplicati.Library.Interface.IGenericModule mod in Library.DynamicLoader.GenericLoader.Modules)
if (mod.SupportedCommands != null)
foundArgs.Add(mod.SupportedCommands);
foreach (IEnumerable<Library.Interface.ICommandLineArgument> arglst in foundArgs)
if (arglst != null)
{
@@ -489,7 +546,7 @@ namespace Duplicati.CommandLine
return "";
List<string> lines = new List<string>();
foreach(Duplicati.Library.Interface.ICommandLineArgument arg in args)
foreach (Duplicati.Library.Interface.ICommandLineArgument arg in args)
if (!arg.Deprecated)
lines.Add(PrintArgSimple(arg, arg.Name));
@@ -1,29 +1,32 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
// 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.Localization.Short;
using System.IO;
using Duplicati.Library.Interface;
using Duplicati.Library.Logging;
using Duplicati.Library.AutoUpdater;
using Duplicati.Library.Crashlog;
namespace Duplicati.CommandLine
{
@@ -36,27 +39,27 @@ namespace Duplicati.CommandLine
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
PreloadSettingsLoader.ConfigurePreloadSettings(ref args, PackageHelper.NamedExecutable.CommandLine);
public static int RealMain(string[] args)
{
Library.UsageReporter.Reporter.Initialize();
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<string, Func<TextWriter, Action<Library.Main.Controller>, List<string>, Dictionary<string, string>, Library.Utility.IFilter, int>> CommandMap
{
get
@@ -90,7 +93,7 @@ namespace Duplicati.CommandLine
["systeminfo"] = Commands.SystemInfo,
["send-mail"] = Commands.SendMail
};
return knownCommands;
}
}
@@ -99,7 +102,7 @@ namespace Duplicati.CommandLine
private static int ShowChangeLog(TextWriter outwriter)
{
var path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "changelog.txt");
var path = System.IO.Path.Combine(UpdaterManager.INSTALLATIONDIR, "changelog.txt");
outwriter.WriteLine(System.IO.File.ReadAllText(path));
return 0;
}
@@ -112,18 +115,28 @@ namespace Duplicati.CommandLine
if (update != null && update.Version != Library.AutoUpdater.UpdaterManager.SelfVersion.Version)
{
outwriter.WriteLine("Found update \"{0}\", downloading ...", update.Displayname);
long lastpg = 0;
Library.AutoUpdater.UpdaterManager.DownloadAndUnpackUpdate(update, f =>
var package = update.FindPackage();
if (package == null)
{
var npg = (long)(f * 100);
if (Math.Abs(npg - lastpg) >= 5 || (npg == 100 && lastpg != 100))
outwriter.WriteLine($"Failed to locate a matching package for this machine, please visit this link and select the correct package: {update.GetGenericUpdatePageUrl()}");
}
else
{
var filename = Path.GetFullPath(package.GetFilename());
outwriter.WriteLine("Downloading update \"{0}\" to {1} ...", update.Displayname, filename);
long lastpg = 0;
Library.AutoUpdater.UpdaterManager.DownloadUpdate(update, package, filename, f =>
{
lastpg = npg;
outwriter.WriteLine("Downloading {0}% ...", npg);
}
});
outwriter.WriteLine("Update \"{0}\" ({1}) installed, using on next launch", update.Displayname, update.Version);
var npg = (long)(f * 100);
if (Math.Abs(npg - lastpg) >= 5 || (npg == 100 && lastpg != 100))
{
lastpg = npg;
outwriter.WriteLine("Downloading {0}% ...", npg);
}
});
outwriter.WriteLine("Update \"{0}\" ({1}) downloaded", update.Displayname, update.Version);
}
}
}
@@ -1,38 +1,60 @@
// 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.CommandLine.Strings {
internal static class Program {
namespace Duplicati.CommandLine.Strings
{
internal static class Program
{
public static string DeleteCommandNeedsOptions(string commandname, string[] options) { return LC.L(@"The command {0} needs at least one of the following options set: {1}", commandname, string.Join(", ", options)); }
public static string WrongNumberOfCommandsError_v2(int actualcommands, int expectedcommands, string[] commands) { return LC.L(@"Found {0} commands but expected {1}, commands:
{2}", actualcommands, expectedcommands, string.Join(System.Environment.NewLine, commands ?? new string[0])); }
public static string InvalidCommandError(string commandname) { return LC.L(@"Command not supported: {0}", commandname); }
public static string NoFilesetsMatching { get { return LC.L(@"No filesets matched the criteria"); } }
public static string NoFilesetsMatching { get { return LC.L(@"No filesets matched the criteria."); } }
public static string WouldDeleteBackups { get { return LC.L(@"The following filesets would be deleted:"); } }
public static string DeletedBackups { get { return LC.L(@"These filesets were deleted:"); } }
public static string SupportedBackendsHeader { get { return LC.L(@"Supported backends:"); } }
public static string SupportedCompressionModulesHeader { get { return LC.L(@"Supported compression modules:"); } }
public static string SupportedEncryptionModulesHeader { get { return LC.L(@"Supported encryption modules:"); } }
public static string SupportedOptionsHeader { get { return LC.L(@"Supported options:"); } }
public static string ModuleIsLoadedAutomatically { get { return LC.L(@"Module is loaded automatically, use --disable-module to prevent this"); } }
public static string ModuleIsNotLoadedAutomatically { get { return LC.L(@"Module is not loaded automatically, use --enable-module to load it"); } }
public static string ModuleIsLoadedAutomatically { get { return LC.L(@"Module is loaded automatically. Use --{0} to prevent this.", "disable-module"); } }
public static string ModuleIsNotLoadedAutomatically { get { return LC.L(@"Module is not loaded automatically Use --{0} to load it.", "enable-module"); } }
public static string GenericModulesHeader { get { return LC.L(@"Supported generic modules:"); } }
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 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 InternalOptionUsedError(string optionname) { return LC.L(@"The option --{0} was supplied, but it is reserved for internal use and may not be set on the commandline", optionname); }
public static string ParametersFileOptionLong2 { get { return LC.L(@"This option can be used to store some or all of the options given to the commandline client. The file must be a plain text file, UTF-8 encoding is preferred. Each line in the file should be of the format --option=value. The special options --{0} and --{1} can be used 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 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 InternalOptionUsedError(string optionname) { return LC.L(@"The option --{0} was supplied, but it is reserved for internal use and may not be set on the commandline.", optionname); }
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 ParametersFileOptionShort { get { return LC.L(@"Path to a file with parameters"); } }
public static string UnhandledException(string message) { return LC.L(@"An error occured: {0}", message); }
public static string UnhandledInnerException(string message) { return LC.L(@"The inner error message is: {0}", message); }
public static string IncludeLong { get { return LC.L(@"Include files that match this filter. The special character * means any number of character, and the special character ? means any single character, use *.txt to include all files with a txt extension. Regular expressions are also supported and can be supplied by using hard braces, i.e. [.*\.txt]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, i.e. {{Applications}}."); } }
public static string IncludeLong { get { return LC.L(@"Include files that match this filter. The special character * means any number of character, and the special character ? means any single character. Use *.txt to include all files with a txt extension. Regular expressions are also supported and can be supplied by using hard braces, e.g. [.*\.txt]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, e.g. {{Applications}}."); } }
public static string IncludeShort { get { return LC.L(@"Include files"); } }
public static string ExcludeLong { get { return LC.L(@"Exclude files that match this filter. The special character * means any number of character, and the special character ? means any single character, use *.txt to exclude all files with a txt extension. Regular expressions are also supported and can be supplied by using hard braces, i.e. [.*\.txt]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, i.e. {{TemporaryFiles}}."); } }
public static string ExcludeLong { get { return LC.L(@"Exclude files that match this filter. The special character * means any number of character, and the special character ? means any single character. Use *.txt to exclude all files with a txt extension. Regular expressions are also supported and can be supplied by using hard braces, e.g. [.*\.txt]. Filter groups (which encapsulate a built-in set of well-known files and folders) can be specified by using curly braces, e.g. {{TemporaryFiles}}."); } }
public static string ExcludeShort { get { return LC.L(@"Exclude files"); } }
public static string ControlFilesOptionShort { get { return LC.L(@"If this option is used with a backup operation, it is interpreted as a list of files to add to the filesets. When used with list or restore, it will list or restore the control files instead of the normal files."); } }
public static string ControlFilesOptionLong { get { return LC.L(@"Use control files"); } }
public static string ControlFilesOptionLong { get { return LC.L(@"If this option is used with a backup operation, it is interpreted as a list of files to add to the filesets. When used with list or restore, it will list or restore the control files instead of the normal files."); } }
public static string ControlFilesOptionShort { get { return LC.L(@"Use control files"); } }
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 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."; } }
// ReSharper disable once UnusedMember.Global
// This is a placeholder message that is intended to be used with the code
// for each error and log message. The idea is that the commandline will

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

@@ -9,17 +9,18 @@
> duplicati.commandline.exe
> duplicati.commandline.exe help
See duplicati.commandline.exe help <topic> for more information.
See %CLI_EXE% help <topic> for more information.
General: example, changelog
Commands: backup, find, restore, delete, compact, test, compare, purge, vacuum
Repair: repair, affected, list-broken-files, purge-broken-files
Debug: debug, logging, create-report, test-filters, system-info, send-mail
Targets: %BACKENDS%
Modules: %ENCRYPTIONMODULES%, %COMPRESSIONMODULES%, %GENERICMODULES%
Formats: date, time, size, encryption, compression
Formats: date, time, size, decimal, encryption, compression
Advanced: mail, advanced, returncodes, filter, filter-groups, <option>
Secrets: secret, <provider>
http://www.duplicati.com/ Version: %VERSION%
https://duplicati.com/ Version: %VERSION%
@@ -30,7 +31,7 @@ Show the last changes to Duplicati
==================================
Duplicati updates automatically when a newer version is available. The following command will show a list of versions and changes.
%MONO%"%APP_PATH%" changelog
"%APP_PATH%" changelog
# =============================================================================
> duplicati.commandline.exe help system-info
@@ -40,7 +41,7 @@ Show system information as seen by Duplicati
============================================
Issue the following command to see a variety of system information relevant to Duplicati.
%MONO%"%APP_PATH%" system-info
"%APP_PATH%" system-info
# =============================================================================
> duplicati.commandline.exe help example
@@ -50,25 +51,25 @@ Make an encrypted backup
========================
The following command will make an encrypted backup and store it on an FTP server. As no passphrase is specified, the user is prompted for a password. The upload speed is throttled to 500kB/s. Backups older than one month are considered old. Old backups are deleted automatically. The maximum file size is limited to 50MB (default).
%MONO%"%APP_PATH%" backup ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_PATH%" --throttle=500kB
"%APP_PATH%" backup ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_PATH%" --throttle=500kB
Search for files in a backup
============================
The backup can be searched for specific files. The following command returns all files "%EXAMPLE_SOURCE_FILE%" of the last backup. File names can also contain the wildcards * and ?.
%MONO%"%APP_PATH%" find ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%"
"%APP_PATH%" find ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%"
Restore a specific file
=======================
The following command will restore "%EXAMPLE_SOURCE_FILE%" from the backup in the latest version to its original destination and overwrite an existing file.
%MONO%"%APP_PATH%" restore ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%" --overwrite
"%APP_PATH%" restore ftp://me:example.com@ftp.example.com/target "%EXAMPLE_SOURCE_FILE%" --overwrite
# =============================================================================
> duplicati.commandline.exe help backup
Usage: backup <storage-URL> "<source-path>" [<options>]
Usage: %CLI_EXE% backup <storage-URL> "<source-path>" [<options>]
Makes a backup of <source-path> and stores it in <storage-URL>. The format for <storage-URL> is
protocol://username:password@hostname:port/path?backend_option1=value1&backend_option2=value2.
@@ -97,7 +98,7 @@ Usage: backup <storage-URL> "<source-path>" [<options>]
> duplicati.commandline.exe help find
> duplicati.commandline.exe help list
Usage: find <storage-URL> ["<filename>"] [<options>]
Usage: %CLI_EXE% find <storage-URL> ["<filename>"] [<options>]
Finds specific files in specific backups. If <filename> is specified, all occurrences of <filename> in the backup are listed. <filename> can contain * and ? as wildcards. File names in [brackets] are interpreted as regular expression. Latest backup is searched by default. If entire path is specified, all available versions of the file are listed. If no <filename> is specified, a list of all available backups is shown.
@@ -116,7 +117,7 @@ Usage: find <storage-URL> ["<filename>"] [<options>]
# =============================================================================
> duplicati.commandline.exe help restore
Usage: restore <storage-URL> ["<filename>"] [<options>]
Usage: %CLI_EXE% restore <storage-URL> ["<filename>"] [<options>]
Restores <filename> to its original destination. If <filename> exists already, <filename> is changed to <filename-timestamp.extension>. To restore all files use "*" or leave empty.
@@ -134,7 +135,7 @@ Usage: restore <storage-URL> ["<filename>"] [<options>]
# =============================================================================
> duplicati.commandline.exe help delete
Usage: delete <storage-URL> [<options>]
Usage: %CLI_EXE% delete <storage-URL> [<options>]
Marks old data deleted and removes outdated dlist files. A backup is deleted when it is older than <keep-time> or when there are more newer versions than <keep-versions>. Data is considered old, when it is not required from any existing backup anymore.
@@ -154,7 +155,7 @@ Usage: delete <storage-URL> [<options>]
# =============================================================================
> duplicati.commandline.exe help compact
Usage: compact <storage-URL> [<options>]
Usage: %CLI_EXE% compact <storage-URL> [<options>]
Old data is not deleted immediately as in most cases only small parts of a dblock file are old data. When the amount of old data in a dblock file grows it might be worth to replace it. This is especially the case when the number of dblock files and thus the required storage space can be reduced. When backups are frequently made and only few files have changed, the uploaded dblock files are small. At some point it might make sense to replace a large number of small files with one large file. This is what compacting does.
@@ -171,7 +172,7 @@ Usage: compact <storage-URL> [<options>]
# =============================================================================
> duplicati.commandline.exe help compare
Usage: compare <storage-URL> [<base-version>] [<compare-to>] [<options>]
Usage: %CLI_EXE% compare <storage-URL> [<base-version>] [<compare-to>] [<options>]
Compares two backups and shows differences. If no versions are given, changes are shown between the two latest backups. The versions can either be timestamps or backup version numbers. If only one version is given, the most recent backup is compared to that version.
@@ -187,7 +188,7 @@ Usage: compare <storage-URL> [<base-version>] [<compare-to>] [<options>]
# =============================================================================
> duplicati.commandline.exe help create-report
Usage: create-report <storage-URL> <output-file> [<options>]
Usage: %CLI_EXE% create-report <storage-URL> <output-file> [<options>]
Analyses the backup and prepares a report with anonymous information. This report can be sent to the developers for a better analysis in case something went wrong.
@@ -197,7 +198,7 @@ Usage: create-report <storage-URL> <output-file> [<options>]
# =============================================================================
> duplicati.commandline.exe help repair
Usage: repair <storage-URL> [<options>]
Usage: %CLI_EXE% repair <storage-URL> [<options>]
Tries to repair the backup. If no local db is found or the db is empty, the db is re-created with data from the storage. If the db is in place but the remote storage is corrupt, the remote storage gets repaired with local data (if available).
@@ -206,7 +207,7 @@ Usage: repair <storage-URL> [<options>]
# =============================================================================
> duplicati.commandline.exe help purge
Usage: purge <storage-URL> <filenames> [<options>]
Usage: %CLI_EXE% purge <storage-URL> <filenames> [<options>]
Purges (removes) files from remote backup data. This command can either take a list of filenames or use the filters to choose which files to purge. The purge process creates new filesets on the remote destination with the purged files removed, and will start the compacting process after a purge. By default, the matching files are purged in all versions, but this can be limited by choosing one or more versions. To test what will happen, use the --dry-run flag.
@@ -228,11 +229,11 @@ Usage: purge <storage-URL> <filenames> [<options>]
> duplicati.commandline.exe help list-broken-files
> duplicati.commandline.exe help purge-broken-files
Usage: list-broken-files <storage-URL> [<options>]
Usage: %CLI_EXE% list-broken-files <storage-URL> [<options>]
Checks the database for missing data that cause files not not be restoreable. Files can become unrestoreable if remote data files are defect or missing. Use the list-broken-files command to see what the purge-broken-files command will remove.
Usage: purge-broken-files <storage-URL> [<options>]
Usage: %CLI_EXE% purge-broken-files <storage-URL> [<options>]
Removes all files from the database and remote storage that are no longer restoreable. Use this operation with caution, and only if you cannot recover the missing remote files, but want to continue a backup. Even with missing remote files, it may be possible to restore parts of the files that will be removed with this command.
@@ -244,7 +245,7 @@ Usage: purge-broken-files <storage-URL> [<options>]
> duplicati.commandline.exe help test
> duplicati.commandline.exe help verify
Usage: test <storage-URL> <samples> [<options>]
Usage: %CLI_EXE% test <storage-URL> <samples> [<options>]
Verifies integrity of a backup. A random sample of dlist, dindex, dblock files is downloaded, decrypted and the content is checked against recorded size values and data hashes. <samples> specifies the number of samples to be tested. If "all” is specified, all files in the backup will be tested. This is a rolling check, i.e. when executed another time different samples are verified than in the first run. A sample consists of 1 dlist, 1 dindex, 1 dblock.
@@ -262,21 +263,21 @@ Usage: test <storage-URL> <samples> [<options>]
> duplicati.commandline.exe help test-filters
> duplicati.commandline.exe help test-filter
Usage: test-filters <source-path> [<options>]
Usage: %CLI_EXE% test-filters <source-path> [<options>]
Scans the source files and tests against the filters specified, the console output shows which files and folders are examined and the result.
# =============================================================================
> duplicati.commandline.exe help affected
Usage: affected <storage-URL> <remote-filename> [<remote-filenames>] [<options>]
Usage: %CLI_EXE% affected <storage-URL> <remote-filename> [<remote-filenames>] [<options>]
Returns a report explaining what backup sets and files are affected by a remote file. You can use this option to see what source files are affected if one or more remote files are damaged or deleted. Notes that this command requires a local database to be present, if the database is not found automatically, you can set --dbpath to point to the database to use.
# =============================================================================
> duplicati.commandline.exe help vacuum
Usage: vacuum <storage-URL> [<options>]
Usage: %CLI_EXE% vacuum <storage-URL> [<options>]
Rebuilds the local database, repacking it into a minimal amount of disk space.
@@ -434,6 +435,15 @@ Whenever a size is required, you can use any of the following suffixes:
For speed limits, use the same format. If you enter 1MB this will be 1MB/sec.
# =============================================================================
> duplicati.commandline.exe help decimal
For entering decimal values you must use the English notation, with a period as decimal separator:
0.1
12.34
# =============================================================================
> duplicati.commandline.exe help filter
> duplicati.commandline.exe help filters
@@ -469,7 +479,7 @@ Duplicati has several built-in groups of filters, which include commonly exclude
Duplicati can send email notifications after each operation. Use the send-mail command to test this:
%MONO%"%APP_PATH%" send-mail --send-mail-to=<email-address>
"%APP_PATH%" send-mail --send-mail-to=<email-address>
--send-mail-to=<email-address>
@@ -503,6 +513,16 @@ Duplicati can send email notifications after each operation. Use the send-mail c
The backend url
%LOCALPATH%
The path to the local folders involved (i.e. the folders being backed up)
%machine-id%
The assigned unique random identifier for the current machine, can be overridden with --machine-id
%backup-id%
The assigned id for the backup, can be overridden with --backup-id
%backup-name%
The name of the backup, can be overridden with --backup-name
%machine-name%
The name of the machine, can be overridden with --machine-name
# =============================================================================
> duplicati.commandline.exe help advanced
@@ -522,7 +542,40 @@ Duplicati offers more options than listed in the other topics. Those additional
Duplicati reports the following return/exit codes:
0 - Success
1 - Successful operation, but no files were changed
2 - Successful operation, but with warnings
2 - Successful operation, but with warning(s)
3 - For backup: finished with error(s), for test: error(s) found on the backend
50 - Backup uploaded some files, but did not finish
100 - An error occurred
200 - Invalid commandline arguments found
# =============================================================================
> duplicati.commandline.exe help secret
> duplicati.commandline.exe help secrets
> duplicati.commandline.exe help secret-provider
Duplicati supports reading secrets from an external secret provider.
The setup is done by adding an option:
--secret-provider=<config>
This setup will make Duplicati replace all values starting with a $-symbol, using the secret provider.
As an example, if the setting is:
--passphrase=$backup-passhrase
Duplicati will use the secret provider to fetch the secret for the key "backup-passphrase", and replace the value before starting the operation. If the value is not found the operation will fail.
Replacing can also be done inside the backend urls, such that a backend url could be:
s3://?aws-access-key-id=$awsid&aws-secret-access-key=$awskey
Here the values "awsid" and "awskey" would be fetched from the secret provider and replaced verbatim in the url (make sure to url encode the secrets in the provider).
To support more robust parsing, it is also possible to change the pattern from the default "$":
--secret-provider-pattern=!secret{}
This would extract keys inside that pattern, such as extracting "key name" from "!secret{key name}".
For providers that return a set of secrets, the key lookup is default case insensitive.
For providers that require a request per secret, the lookup is case-sensitive.
The following secret providers are supported:
%SECRETPROVIDERS%
View File
@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B93E3BF0-DAA7-49B7-B07D-0559C5816735}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Duplicati.CommandLine.ConfigurationImporter</RootNamespace>
<AssemblyName>Duplicati.CommandLine.ConfigurationImporter</AssemblyName>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{de3e5d4c-51ab-4e5e-bee8-e636cebfba65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Server\Duplicati.Server.csproj">
<Project>{19e661d2-c5da-4f35-b3ee-7586e5734b5f}</Project>
<Name>Duplicati.Server</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -1,89 +0,0 @@
// Copyright (C) 2019, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using Duplicati.Server.Serializable;
using Duplicati.Server.WebServer.RESTMethods;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Duplicati.CommandLine.ConfigurationImporter
{
public static class ConfigurationImporter
{
private static readonly string usageString = $"Usage: {nameof(ConfigurationImporter)}.exe <configuration-file> --import-metadata=(true | false) --server-datafolder=<folder containing Duplicati-server.sqlite>";
public static void Main(string[] args)
{
if (args.Length != 3)
{
throw new ArgumentException($"Incorrect number of input arguments. {ConfigurationImporter.usageString}");
}
string configurationFile = args[0];
Dictionary<string, string> importOptions = Duplicati.Library.Utility.CommandLineParser.ExtractOptions(args.Skip(1).ToList());
if (!importOptions.TryGetValue("import-metadata", out string importMetadataString))
{
throw new ArgumentException($"Invalid import-metadata argument. {ConfigurationImporter.usageString}");
}
bool importMetadata = Duplicati.Library.Utility.Utility.ParseBool(importMetadataString, false);
if (!importOptions.TryGetValue("server-datafolder", out string serverDatafolder))
{
throw new ArgumentException($"Invalid server-datafolder argument. {ConfigurationImporter.usageString}");
}
Dictionary<string, string> advancedOptions = new Dictionary<string, string>
{
{ "server-datafolder", serverDatafolder }
};
ImportExportStructure importedStructure = Backups.ImportBackup(configurationFile, importMetadata, () => ConfigurationImporter.ReadPassword($"Password for {configurationFile}: "), advancedOptions);
Console.WriteLine($"Imported \"{importedStructure.Backup.Name}\" with ID {importedStructure.Backup.ID} and local database at {importedStructure.Backup.DBPath}.");
}
private static string ReadPassword(string prompt)
{
Console.Write(prompt);
string password = "";
while (true)
{
ConsoleKeyInfo keyInfo = Console.ReadKey(true);
if (keyInfo.Key == ConsoleKey.Enter)
{
break;
}
if (keyInfo.Key == ConsoleKey.Backspace)
{
if (password.Length > 0)
{
password = password.Substring(0, password.Length - 1);
Console.Write("\b \b");
}
}
else if (keyInfo.KeyChar != '\u0000') // Only accept if the key maps to a Unicode character (e.g., ignore F1 or Home).
{
password += keyInfo.KeyChar;
Console.Write("*");
}
}
Console.WriteLine();
return password;
}
}
}
@@ -1,35 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Duplicati.CommandLine.ConfigurationImporter")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Duplicati.CommandLine.ConfigurationImporter")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("B93E3BF0-DAA7-49B7-B07D-0559C5816735")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyFileVersion("2.0.0.7")]
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
</startup>
<runtime>
<AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.3" newVersion="4.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion="3.0.4.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -1,263 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{81765A64-3661-4E3E-B850-2F6F87A51F74}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Duplicati.CommandLine</RootNamespace>
<AssemblyName>Duplicati.CommandLine</AssemblyName>
<AssemblyOriginatorKeyFile>Duplicati.snk</AssemblyOriginatorKeyFile>
<ApplicationIcon>TrayWarning.ico</ApplicationIcon>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<TargetFrameworkProfile />
<UseMSBuildEngine>false</UseMSBuildEngine>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<EnvironmentVariables>
<EnvironmentVariables>
<Variable name="AUTOUPDATER_Duplicati_SKIP_UPDATE" value="1" />
</EnvironmentVariables>
</EnvironmentVariables>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Azure.KeyVault.Core, Version=3.0.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Azure.KeyVault.Core.3.0.4\lib\net461\Microsoft.Azure.KeyVault.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Edm, Version=5.8.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Data.Edm.5.8.4\lib\net40\Microsoft.Data.Edm.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.8.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Data.OData.5.8.4\lib\net40\Microsoft.Data.OData.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Services.Client, Version=5.8.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Rest.ClientRuntime.2.3.20\lib\net461\Microsoft.Rest.ClientRuntime.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Rest.ClientRuntime.Azure, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.3.19\lib\net461\Microsoft.Rest.ClientRuntime.Azure.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Spatial, Version=5.8.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Spatial.5.8.4\lib\net40\System.Spatial.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Help.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Commands.cs" />
<Compile Include="ConsoleOutput.cs" />
<Compile Include="Strings.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj">
<Project>{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}</Project>
<Name>Duplicati.Library.Backend.AzureBlob</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\File\Duplicati.Library.Backend.File.csproj">
<Project>{FC9B7611-836F-4127-8B44-A7C31F506807}</Project>
<Name>Duplicati.Library.Backend.File</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj">
<Project>{2cd5dbc3-3da6-432d-ba97-f0b8d24501c2}</Project>
<Name>Duplicati.Library.Backend.Jottacloud</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj">
<Project>{851a1cb8-3ceb-41b4-956f-34d760d2a8e5}</Project>
<Name>Duplicati.Library.Backend.Rclone</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj">
<Project>{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}</Project>
<Name>Duplicati.Library.Backend.SharePoint</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj">
<Project>{32a74526-3e5f-413a-8cb4-1efdad4c8b91}</Project>
<Name>Duplicati.Library.Backend.Sia</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj">
<Project>{9a04cb37-da72-4008-9703-3ac5191974e9}</Project>
<Name>Duplicati.Library.Backend.Tardigrade</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Compression\Duplicati.Library.Compression.csproj">
<Project>{19ECCE09-B5EB-406C-8C57-BAC66997D469}</Project>
<Name>Duplicati.Library.Compression</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj">
<Project>{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}</Project>
<Name>Duplicati.Library.DynamicLoader</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Interface\Duplicati.Library.Interface.csproj">
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
<Name>Duplicati.Library.Interface</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Logging\Duplicati.Library.Logging.csproj">
<Project>{D10A5FC0-11B4-4E70-86AA-8AEA52BD9798}</Project>
<Name>Duplicati.Library.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Main\Duplicati.Library.Main.csproj">
<Project>{10D2D1B7-C664-41D8-9B3A-00040C3D421B}</Project>
<Name>Duplicati.Library.Main</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Modules\Builtin\Duplicati.Library.Modules.Builtin.csproj">
<Project>{52826615-7964-47FE-B4B3-1B2DBDF605B9}</Project>
<Name>Duplicati.Library.Modules.Builtin</Name>
</ProjectReference>
<ProjectReference Include="..\License\Duplicati.License.csproj">
<Project>{4D012CB1-4B92-47F4-89B7-BF80A73A2E99}</Project>
<Name>Duplicati.License</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj">
<Project>{F61679A9-E5DE-468A-B5A4-05F92D0143D2}</Project>
<Name>Duplicati.Library.Backend.FTP</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj">
<Project>{1BFAE226-8364-4086-825C-BB83F6F3EE4C}</Project>
<Name>Duplicati.Library.Backend.CloudFiles</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj">
<Project>{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}</Project>
<Name>Duplicati.Library.Backend.S3</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj">
<Project>{FF2BF37C-E502-4C98-BEA0-701671DDFA08}</Project>
<Name>Duplicati.Library.Backend.SSHv2</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj">
<Project>{C0270709-2A40-43B5-8CF1-69581B9FA2A1}</Project>
<Name>Duplicati.Library.Backend.TahoeLAFS</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj">
<Project>{BAE27510-8B5D-44B2-B33E-372A98908041}</Project>
<Name>Duplicati.Library.Backend.WEBDAV</Name>
</ProjectReference>
<ProjectReference Include="BackendTool\Duplicati.CommandLine.BackendTool.csproj">
<Project>{2AF960C0-357D-4D44-A3D5-8B6E89DB0F11}</Project>
<Name>Duplicati.CommandLine.BackendTool</Name>
</ProjectReference>
<ProjectReference Include="BackendTester\Duplicati.CommandLine.BackendTester.csproj">
<Project>{E7280DCA-7776-4A73-B9B5-41FD77FC8799}</Project>
<Name>Duplicati.CommandLine.BackendTester</Name>
</ProjectReference>
<ProjectReference Include="..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj">
<Project>{7E119745-1F62-43F0-936C-F312A1912C0B}</Project>
<Name>Duplicati.Library.AutoUpdater</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj">
<Project>{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}</Project>
<Name>Duplicati.Library.Backend.OneDrive</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Localization\Duplicati.Library.Localization.csproj">
<Project>{B68F2214-951F-4F78-8488-66E1ED3F50BF}</Project>
<Name>Duplicati.Library.Localization</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj">
<Project>{5489181D-950C-44AF-873C-45EB0A3B6BD2}</Project>
<Name>Duplicati.Library.Backend.GoogleServices</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj">
<Project>{D9E4E686-423C-48EC-A392-404E7C00860C}</Project>
<Name>Duplicati.Library.Backend.OpenStack</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\HubiC\Duplicati.Library.Backend.HubiC.csproj">
<Project>{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}</Project>
<Name>Duplicati.Library.Backend.HubiC</Name>
</ProjectReference>
<ProjectReference Include="RecoveryTool\Duplicati.CommandLine.RecoveryTool.csproj">
<Project>{4A010589-76E6-4F05-A5C4-4598D5DF11F8}</Project>
<Name>Duplicati.CommandLine.RecoveryTool</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj">
<Project>{61C43D61-4368-4942-84A3-1EB623F4EF2A}</Project>
<Name>Duplicati.Library.Backend.Backblaze</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj">
<Project>{6643A5AE-AB38-453F-ADCE-408E35A81A83}</Project>
<Name>Duplicati.Library.Backend.Mega</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj">
<Project>{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}</Project>
<Name>Duplicati.Library.Backend.Box</Name>
</ProjectReference>
<ProjectReference Include="..\Library\UsageReporter\Duplicati.Library.UsageReporter.csproj">
<Project>{BB014EA5-CE2C-4444-8D30-38983A0E8553}</Project>
<Name>Duplicati.Library.UsageReporter</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
<ProjectReference Include="..\Tools\Duplicati.Tools.csproj">
<Project>{0797AA22-C5DD-4950-BB60-34765AB8C6DD}</Project>
<Name>Duplicati.Tools</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Common\Duplicati.Library.Common.csproj">
<Project>{D63E53E4-A458-4C2F-914D-92F715F58ACF}</Project>
<Name>Duplicati.Library.Common</Name>
</ProjectReference>
<ProjectReference Include="..\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj">
<Project>{AE035E01-C917-4F13-A35E-78F21C1A2F17}</Project>
<Name>Duplicati.Library.Backend.Storj</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.manifest">
<SubType>Designer</SubType>
</None>
<None Include="Duplicati.snk" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<EmbeddedResource Include="help.txt" />
<Content Include="TrayWarning.ico" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Binary file not shown.
@@ -1,52 +0,0 @@
#region Disclaimer / License
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Duplicati.CommandLine")]
[assembly: AssemblyDescription("A commandline version of Duplicati")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Duplicati Team")]
[assembly: AssemblyProduct("Duplicati.CommandLine")]
[assembly: AssemblyCopyright("LGPL, Copyright © Duplicati Team 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("bfc1d214-8825-417d-8e2c-be4bb0d6524d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("2.0.0.7")]
[assembly: AssemblyFileVersion("2.0.0.7")]
+34 -27
View File
@@ -1,23 +1,30 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Duplicati.Library.Utility;
namespace Duplicati.CommandLine.RecoveryTool
{
@@ -31,7 +38,7 @@ namespace Duplicati.CommandLine.RecoveryTool
return 100;
}
using(var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[1], options))
using (var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[1], options))
{
if (backend == null)
{
@@ -60,7 +67,7 @@ namespace Duplicati.CommandLine.RecoveryTool
string passphrase;
options.TryGetValue("passphrase", out passphrase);
foreach(var file in lst)
foreach (var file in lst)
{
try
{
@@ -75,7 +82,7 @@ namespace Duplicati.CommandLine.RecoveryTool
var local = Path.Combine(targetfolder, file.Name);
if (p.EncryptionModule != null)
{
if (string.IsNullOrWhiteSpace(passphrase))
if (string.IsNullOrWhiteSpace(passphrase))
{
needspass++;
Console.WriteLine(" - No passphrase supplied, skipping");
@@ -99,15 +106,15 @@ namespace Duplicati.CommandLine.RecoveryTool
Console.Write(" - downloading ({0})...", Library.Utility.Utility.FormatSizeString(file.Size));
using(var tf = new Library.Utility.TempFile())
using (var tf = new Library.Utility.TempFile())
{
backend.Get(file.Name, tf);
backend.GetAsync(file.Name, tf, CancellationToken.None).Await();
if (p.EncryptionModule != null)
{
Console.Write(" - decrypting ...");
using(var m = Library.DynamicLoader.EncryptionLoader.GetModule(p.EncryptionModule, passphrase, options))
using(var tf2 = new Library.Utility.TempFile())
using (var m = Library.DynamicLoader.EncryptionLoader.GetModule(p.EncryptionModule, passphrase, options))
using (var tf2 = new Library.Utility.TempFile())
{
m.Decrypt(tf, tf2);
File.Copy(tf2, local);
@@ -118,9 +125,9 @@ namespace Duplicati.CommandLine.RecoveryTool
}
Console.WriteLine(" done!");
}
catch(Exception ex)
catch (Exception ex)
{
Console.WriteLine(" error: {0}", ex);
errors++;
@@ -143,8 +150,8 @@ namespace Duplicati.CommandLine.RecoveryTool
return 200;
else
return 0;
}
}
}
}
}
@@ -1,201 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4A010589-76E6-4F05-A5C4-4598D5DF11F8}</ProjectGuid>
<OutputType>Exe</OutputType>
<Prefer32Bit>False</Prefer32Bit>
<RootNamespace>Duplicati.CommandLine.RecoveryTool</RootNamespace>
<AssemblyName>Duplicati.CommandLine.RecoveryTool</AssemblyName>
<AssemblyOriginatorKeyFile>Duplicati.snk</AssemblyOriginatorKeyFile>
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
<UseMSBuildEngine>false</UseMSBuildEngine>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<EnvironmentVariables>
<EnvironmentVariables>
<Variable name="AUTOUPDATER_Duplicati_SKIP_UPDATE" value="1" />
</EnvironmentVariables>
</EnvironmentVariables>
<Commandlineparameters>index /Users/kenneth/testdata/recovery --targetpath=/Users/kenneth/testdata/recovery-restored</Commandlineparameters>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Azure.KeyVault.Core, Version=3.0.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Azure.KeyVault.Core.3.0.4\lib\net461\Microsoft.Azure.KeyVault.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Rest.ClientRuntime.2.3.20\lib\net461\Microsoft.Rest.ClientRuntime.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Rest.ClientRuntime.Azure, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.3.19\lib\net461\Microsoft.Rest.ClientRuntime.Azure.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Net" />
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.Serialization" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileIndex.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Download.cs" />
<Compile Include="Help.cs" />
<Compile Include="Index.cs" />
<Compile Include="List.cs" />
<Compile Include="Recompress.cs" />
<Compile Include="Restore.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj">
<Project>{7E119745-1F62-43F0-936C-F312A1912C0B}</Project>
<Name>Duplicati.Library.AutoUpdater</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\AzureBlob\Duplicati.Library.Backend.AzureBlob.csproj">
<Project>{8E4CECFB-0413-4B00-AB93-78D1C3902BD5}</Project>
<Name>Duplicati.Library.Backend.AzureBlob</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj">
<Project>{1BFAE226-8364-4086-825C-BB83F6F3EE4C}</Project>
<Name>Duplicati.Library.Backend.CloudFiles</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\File\Duplicati.Library.Backend.File.csproj">
<Project>{FC9B7611-836F-4127-8B44-A7C31F506807}</Project>
<Name>Duplicati.Library.Backend.File</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\FTP\Duplicati.Library.Backend.FTP.csproj">
<Project>{F61679A9-E5DE-468A-B5A4-05F92D0143D2}</Project>
<Name>Duplicati.Library.Backend.FTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\GoogleServices\Duplicati.Library.Backend.GoogleServices.csproj">
<Project>{5489181D-950C-44AF-873C-45EB0A3B6BD2}</Project>
<Name>Duplicati.Library.Backend.GoogleServices</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\HubiC\Duplicati.Library.Backend.HubiC.csproj">
<Project>{D60AD540-0E7D-40CE-83AE-D26E01FFE9B8}</Project>
<Name>Duplicati.Library.Backend.HubiC</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj">
<Project>{2cd5dbc3-3da6-432d-ba97-f0b8d24501c2}</Project>
<Name>Duplicati.Library.Backend.Jottacloud</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OneDrive\Duplicati.Library.Backend.OneDrive.csproj">
<Project>{CCD76347-7DC7-4B42-B7E1-E500E624CAC3}</Project>
<Name>Duplicati.Library.Backend.OneDrive</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\OpenStack\Duplicati.Library.Backend.OpenStack.csproj">
<Project>{D9E4E686-423C-48EC-A392-404E7C00860C}</Project>
<Name>Duplicati.Library.Backend.OpenStack</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj">
<Project>{851a1cb8-3ceb-41b4-956f-34d760d2a8e5}</Project>
<Name>Duplicati.Library.Backend.Rclone</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\S3\Duplicati.Library.Backend.S3.csproj">
<Project>{C03F6DFD-805A-4BE0-9338-64870ADDB4A2}</Project>
<Name>Duplicati.Library.Backend.S3</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\SharePoint\Duplicati.Library.Backend.SharePoint.csproj">
<Project>{59C8BBC5-6E42-46FB-AB3E-6C183A82459A}</Project>
<Name>Duplicati.Library.Backend.SharePoint</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj">
<Project>{32a74526-3e5f-413a-8cb4-1efdad4c8b91}</Project>
<Name>Duplicati.Library.Backend.Sia</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\SSHv2\Duplicati.Library.Backend.SSHv2.csproj">
<Project>{FF2BF37C-E502-4C98-BEA0-701671DDFA08}</Project>
<Name>Duplicati.Library.Backend.SSHv2</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\TahoeLAFS\Duplicati.Library.Backend.TahoeLAFS.csproj">
<Project>{C0270709-2A40-43B5-8CF1-69581B9FA2A1}</Project>
<Name>Duplicati.Library.Backend.TahoeLAFS</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Tardigrade\Duplicati.Library.Backend.Tardigrade.csproj">
<Project>{9a04cb37-da72-4008-9703-3ac5191974e9}</Project>
<Name>Duplicati.Library.Backend.Tardigrade</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\WEBDAV\Duplicati.Library.Backend.WEBDAV.csproj">
<Project>{BAE27510-8B5D-44B2-B33E-372A98908041}</Project>
<Name>Duplicati.Library.Backend.WEBDAV</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Compression\Duplicati.Library.Compression.csproj">
<Project>{19ECCE09-B5EB-406C-8C57-BAC66997D469}</Project>
<Name>Duplicati.Library.Compression</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj">
<Project>{0CA86ECF-5BEC-4909-B4F6-110A03B30B92}</Project>
<Name>Duplicati.Library.DynamicLoader</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Interface\Duplicati.Library.Interface.csproj">
<Project>{C5899F45-B0FF-483C-9D38-24A9FCAAB237}</Project>
<Name>Duplicati.Library.Interface</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj">
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
<Name>Duplicati.Library.Utility</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Main\Duplicati.Library.Main.csproj">
<Project>{10D2D1B7-C664-41D8-9B3A-00040C3D421B}</Project>
<Name>Duplicati.Library.Main</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Backblaze\Duplicati.Library.Backend.Backblaze.csproj">
<Project>{61C43D61-4368-4942-84A3-1EB623F4EF2A}</Project>
<Name>Duplicati.Library.Backend.Backblaze</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Mega\Duplicati.Library.Backend.Mega.csproj">
<Project>{6643A5AE-AB38-453F-ADCE-408E35A81A83}</Project>
<Name>Duplicati.Library.Backend.Mega</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Box\Duplicati.Library.Backend.Box.csproj">
<Project>{3FF7DD0B-5284-4BF9-97D9-1E4417FDABB2}</Project>
<Name>Duplicati.Library.Backend.Box</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\AlternativeFTP\Duplicati.Library.Backend.AlternativeFTP.csproj">
<Project>{3F47CB0C-CEE1-447F-9C26-7F8E419E3E3F}</Project>
<Name>Duplicati.Library.Backend.AlternativeFTP</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Dropbox\Duplicati.Library.Backend.Dropbox.csproj">
<Project>{B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493}</Project>
<Name>Duplicati.Library.Backend.Dropbox</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Common\Duplicati.Library.Common.csproj">
<Project>{D63E53E4-A458-4C2F-914D-92F715F58ACF}</Project>
<Name>Duplicati.Library.Common</Name>
</ProjectReference>
<ProjectReference Include="..\..\Library\Backend\Storj\Duplicati.Library.Backend.Storj.csproj">
<Project>{AE035E01-C917-4F13-A35E-78F21C1A2F17}</Project>
<Name>Duplicati.Library.Backend.Storj</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="help.txt" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Description>RecoveryTool for Duplicati</Description>
<AssemblyName>Duplicati.CommandLine.RecoveryTool.Implementation</AssemblyName>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\AutoUpdater\Duplicati.Library.AutoUpdater.csproj" />
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj" />
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj" />
<ProjectReference Include="..\..\Library\Main\Duplicati.Library.Main.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="help.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Binary file not shown.
+20 -16
View File
@@ -1,19 +1,23 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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;
+28 -18
View File
@@ -1,21 +1,27 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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 Duplicati.Library.AutoUpdater;
namespace Duplicati.CommandLine.RecoveryTool
{
@@ -30,9 +36,13 @@ namespace Duplicati.CommandLine.RecoveryTool
Console.WriteLine("Unsupported command: {0}", args[0]);
Console.WriteLine();
return 100;
}
}
Console.Write(new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + "." + RESOURCE_NAME)).ReadToEnd());
using var s = new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(Help), RESOURCE_NAME));
Console.Write(s.ReadToEnd()
.Replace("%CLI_EXE%", PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.CommandLine))
.Replace("%RECOVERY_EXE%", PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.RecoveryTool))
);
return 0;
}
+20 -16
View File
@@ -1,19 +1,23 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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;
+21 -16
View File
@@ -1,19 +1,24 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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;
+29 -28
View File
@@ -1,19 +1,24 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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;
@@ -22,22 +27,18 @@ namespace Duplicati.CommandLine.RecoveryTool
{
public static class Program
{
private delegate int CommandRunner(List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static int Main(string[] args)
{
Duplicati.Library.AutoUpdater.UpdaterManager.IgnoreWebrootFolder = true;
return Duplicati.Library.AutoUpdater.UpdaterManager.RunFromMostRecent(typeof(Program).GetMethod("RealMain"), args);
}
private delegate int CommandRunner(List<string> args, Dictionary<string, string> options, Library.Utility.IFilter filter);
public static int RealMain(string[] _args)
public static int Main(string[] _args)
{
try
{
Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, Library.AutoUpdater.PackageHelper.NamedExecutable.RecoveryTool);
var args = new List<string>(_args);
var tmpparsed = Library.Utility.FilterCollector.ExtractOptions(args);
var options = tmpparsed.Item1;
@@ -100,7 +101,7 @@ namespace Duplicati.CommandLine.RecoveryTool
return command(args, options, filter);
}
catch(Exception ex)
catch (Exception ex)
{
if (ex is Duplicati.Library.Interface.UserInformationException)
Console.WriteLine(ex.Message);
@@ -128,7 +129,7 @@ namespace Duplicati.CommandLine.RecoveryTool
if (!newfilter.Empty)
filter = newfilter;
foreach(KeyValuePair<String, String> keyvalue in opt)
foreach (KeyValuePair<String, String> keyvalue in opt)
options[keyvalue.Key] = keyvalue.Value;
cargs.AddRange(
@@ -136,7 +137,7 @@ namespace Duplicati.CommandLine.RecoveryTool
where !string.IsNullOrWhiteSpace(c) && !c.StartsWith("#", StringComparison.Ordinal) && !c.StartsWith("!", StringComparison.Ordinal) && !c.StartsWith("REM ", StringComparison.OrdinalIgnoreCase)
select c
);
return true;
}
catch (Exception e)
@@ -1,43 +0,0 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Duplicati.CommandLine.RecoveryTool")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("kenneth")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("2.0.0.7")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
@@ -0,0 +1,10 @@
{
"profiles": {
"Duplicati.CommandLine.RecoveryTool": {
"commandName": "Project",
"environmentVariables": {
"AUTOUPDATER_Duplicati_SKIP_UPDATE": "1"
}
}
}
}
@@ -1,4 +1,25 @@
using Duplicati.Library.Interface;
// 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.Interface;
using Duplicati.Library.Main;
using Duplicati.Library.Main.Volumes;
using Duplicati.Library.Utility;
@@ -7,8 +28,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading;
using System.Security.Cryptography;
namespace Duplicati.CommandLine.RecoveryTool
{
public static class Recompress
@@ -150,7 +172,7 @@ namespace Duplicati.CommandLine.RecoveryTool
using (var tf = new TempFile())
{
backend.Get(remoteFile.File.Name, tf);
backend.GetAsync(remoteFile.File.Name, tf, CancellationToken.None).Await();
originLastWriteTime = new FileInfo(tf).LastWriteTime;
downloaded++;
@@ -177,7 +199,7 @@ namespace Duplicati.CommandLine.RecoveryTool
{
Console.Write(" recompressing ...");
//Recompressing from eg. zip to zip
//Recompressing from e.g. ZIP to ZIP
if (localFileSource == localFileTarget)
{
File.Move(localFileSource, localFileSource + ".same");
@@ -196,7 +218,7 @@ namespace Duplicati.CommandLine.RecoveryTool
{
// Correct inner filename extension to target compression type
cmfileNew = cmfileNew.Replace("." + cmFileVolume.CompressionModule, "." + target_compr_module);
if (!reencrypt)
if (!reencrypt && !string.IsNullOrWhiteSpace(cmFileVolume.EncryptionModule))
cmfileNew = cmfileNew.Replace("." + cmFileVolume.EncryptionModule, "");
//Because compression changes blocks file sizes - needs to be updated
@@ -207,8 +229,7 @@ namespace Duplicati.CommandLine.RecoveryTool
textJSON = sourceStreamReader.ReadToEnd();
JToken token = JObject.Parse(textJSON);
var fileInfoBlocks = new FileInfo(Path.Combine(targetfolder, cmfileNew.Replace("vol/", "")));
var filehasher = HashAlgorithmHelper.Create(m_Options.FileHashAlgorithm);
using (var filehasher = HashFactory.CreateHasher(m_Options.FileHashAlgorithm))
using (var fileStream = fileInfoBlocks.Open(FileMode.Open))
{
fileStream.Position = 0;
@@ -254,8 +275,8 @@ namespace Duplicati.CommandLine.RecoveryTool
if (reupload)
{
Console.Write(" reuploading ...");
backend.PutAsync((new FileInfo(localFileTarget)).Name, localFileTarget, CancellationToken.None).Wait();
backend.Delete(remoteFile.File.Name);
backend.PutAsync((new FileInfo(localFileTarget)).Name, localFileTarget, CancellationToken.None).Await();
backend.DeleteAsync(remoteFile.File.Name, CancellationToken.None).Await();
File.Delete(localFileTarget);
}
@@ -276,7 +297,7 @@ namespace Duplicati.CommandLine.RecoveryTool
if (remoteverificationfileexist)
{
Console.WriteLine("Found verification file {0} - deleting", m_Options.Prefix + "-verification.json");
backend.Delete(m_Options.Prefix + "-verification.json");
backend.DeleteAsync(m_Options.Prefix + "-verification.json", CancellationToken.None).Await();
}
}
@@ -301,4 +322,3 @@ namespace Duplicati.CommandLine.RecoveryTool
}
}
}
+294 -162
View File
@@ -1,25 +1,32 @@
// Copyright (C) 2015, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 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;
using System.Linq;
using System.Collections.Generic;
using Duplicati.Library.Common.IO;
using Duplicati.Library.Common;
using Duplicati.Library.Utility;
using System.IO.Compression;
using Duplicati.Library.Interface;
namespace Duplicati.CommandLine.RecoveryTool
{
@@ -45,11 +52,8 @@ namespace Duplicati.CommandLine.RecoveryTool
Directory.SetCurrentDirectory(folder);
string targetpath;
options.TryGetValue("targetpath", out targetpath);
string ixfile;
options.TryGetValue("indexfile", out ixfile);
options.TryGetValue("targetpath", out var targetpath);
options.TryGetValue("indexfile", out var ixfile);
if (string.IsNullOrWhiteSpace(ixfile))
ixfile = "index.txt";
@@ -76,12 +80,13 @@ namespace Duplicati.CommandLine.RecoveryTool
Library.Main.Volumes.VolumeReaderBase.UpdateOptionsFromManifest(Path.GetExtension(filelist).Trim('.'), filelist, new Duplicati.Library.Main.Options(options));
string blocksize_str;
options.TryGetValue("blocksize", out blocksize_str);
string blockhash_str;
options.TryGetValue("block-hash-algorithm", out blockhash_str);
string filehash_str;
options.TryGetValue("block-hash-algorithm", out filehash_str);
options.TryGetValue("blocksize", out var blocksize_str);
options.TryGetValue("block-hash-algorithm", out var blockhash_str);
options.TryGetValue("block-hash-algorithm", out var filehash_str);
var offset = 0L;
if (options.TryGetValue("offset", out var offset_str))
offset = long.Parse(offset_str);
long blocksize = string.IsNullOrWhiteSpace(blocksize_str) ? 0 : Library.Utility.Sizeparser.ParseSize(blocksize_str);
@@ -91,79 +96,66 @@ namespace Duplicati.CommandLine.RecoveryTool
return 100;
}
var blockhasher = string.IsNullOrWhiteSpace(blockhash_str) ? null : Library.Utility.HashAlgorithmHelper.Create(blockhash_str);
var filehasher = string.IsNullOrWhiteSpace(filehash_str) ? null : Library.Utility.HashAlgorithmHelper.Create(filehash_str);
if (blockhasher == null)
if (string.IsNullOrWhiteSpace(blockhash_str))
throw new Duplicati.Library.Interface.UserInformationException(string.Format("Block hash algorithm not valid: {0}", blockhash_str), "BlockHashAlgorithmNotSupported");
if (filehasher == null)
if (string.IsNullOrWhiteSpace(filehash_str))
throw new Duplicati.Library.Interface.UserInformationException(string.Format("File hash algorithm not valid: {0}", filehash_str), "FileHashAlgorithmNotSupported");
var hashesprblock = blocksize / (blockhasher.HashSize / 8);
var useIndexMap = !Library.Utility.Utility.ParseBoolOption(options, "reduce-memory-use");
var disableFileVerify = Library.Utility.Utility.ParseBoolOption(options, "disable-file-verify");
var startedCount = 0L;
var restoredCount = 0L;
var errorCount = 0L;
var fileErrors = 0L;
var totalSize = 0L;
using (var blockhasher = HashFactory.CreateHasher(blockhash_str))
using (var filehasher = HashFactory.CreateHasher(filehash_str))
using (var mru = new CompressedFileMRUCache(options))
{
var hashesprblock = blocksize / (blockhasher.HashSize / 8);
Console.WriteLine("Building lookup table for file hashes");
using (HashLookupHelper lookup = new HashLookupHelper(ixfile, mru, (int) blocksize, blockhasher.HashSize / 8))
// Source OS can have different directory separator
string sourceDirsep = null;
using (HashLookupHelper lookup = new HashLookupHelper(ixfile, mru, (int)blocksize, blockhasher.HashSize / 8, useIndexMap))
{
var filecount = 0L;
string largestprefix = null;
string[] largestprefixparts = null;
if (!string.IsNullOrWhiteSpace(targetpath))
Console.WriteLine("Computing restore path");
foreach (var f in List.EnumerateFilesInDList(filelist, filter, options))
{
if (largestprefix == null)
{
largestprefix = f.Path;
largestprefixparts = largestprefix.Split(new char[] { Path.DirectorySeparatorChar });
}
else if (largestprefix.Length > 1)
{
var parts = f.Path.Split(new char[] { Path.DirectorySeparatorChar });
var ni = 0;
for (; ni < Math.Min(parts.Length, largestprefixparts.Length); ni++)
if (!Library.Utility.Utility.ClientFilenameStringComparer.Equals(parts[ni], largestprefixparts[ni]))
break;
if (ni != largestprefixparts.Length)
{
if (ni == 0)
{
largestprefixparts = new string[0];
largestprefix = string.Empty;
}
else
{
Array.Resize(ref largestprefixparts, ni - 1);
largestprefix = string.Join(Util.DirectorySeparatorString, largestprefixparts);
}
}
}
filecount++;
}
string largestprefix = GetLargestPrefix(from f in List.EnumerateFilesInDList(filelist, filter, options) select f.Path, out sourceDirsep, out filecount);
Console.WriteLine("Restoring {0} files to {1}", filecount, string.IsNullOrWhiteSpace(targetpath) ? "original position" : targetpath);
if (Platform.IsClientPosix || largestprefix.Length > 0)
largestprefix = Util.AppendDirSeparator(largestprefix);
if (!string.IsNullOrEmpty(largestprefix))
Console.WriteLine("Removing common prefix {0} from files", largestprefix);
var i = 0L;
// Allocate buffers for block and blocklist hashes
var buffer = new byte[blocksize];
var blocklistbuffer = new byte[blocksize];
foreach (var f in List.EnumerateFilesInDList(filelist, filter, options))
{
if (i < offset)
{
Console.WriteLine($"Skipping {i}: {f.Path} ({Utility.FormatSizeString(f.Size)})");
i++;
continue;
}
startedCount++;
try
{
var targetfile = MapToRestorePath(f.Path, largestprefix, targetpath);
var targetfile = MapToRestorePath(f.Path, largestprefix, targetpath, sourceDirsep);
if (!systemIO.DirectoryExists(systemIO.PathGetDirectoryName(targetfile)))
systemIO.DirectoryCreate(systemIO.PathGetDirectoryName(targetfile));
Console.Write("{0}: {1} ({2})", i, targetfile, Library.Utility.Utility.FormatSizeString(f.Size));
Console.Write($"{i} ({Utility.FormatSizeString(totalSize)}): {targetfile} ({Utility.FormatSizeString(f.Size)})");
totalSize += f.Size;
using (var tf = new Library.Utility.TempFile())
{
@@ -171,7 +163,8 @@ namespace Duplicati.CommandLine.RecoveryTool
{
if (f.BlocklistHashes == null)
{
lookup.WriteHash(sw, f.Hash);
if (f.Size > 0)
lookup.WriteHash(sw, f.Hash, buffer);
}
else
{
@@ -184,15 +177,16 @@ namespace Duplicati.CommandLine.RecoveryTool
try
{
var bi = 0;
foreach (var h in lookup.ReadBlocklistHashes(blh))
foreach (var h in lookup.ReadBlocklistHashes(blh, blocklistbuffer))
{
try
{
sw.Position = blockhashoffset + (bi * blocksize);
lookup.WriteHash(sw, h);
lookup.WriteHash(sw, h, buffer);
}
catch (Exception ex)
{
errorCount++;
Console.WriteLine("Failed to read hash: {0}{1}{2}", h, Environment.NewLine, ex);
}
@@ -201,6 +195,7 @@ namespace Duplicati.CommandLine.RecoveryTool
}
catch (Exception ex)
{
errorCount++;
Console.WriteLine("Failed to read Blocklist hash: {0}{1}{2}", blh, Environment.NewLine, ex);
}
@@ -209,30 +204,45 @@ namespace Duplicati.CommandLine.RecoveryTool
}
}
string fh;
using (var fs = File.OpenRead(tf))
fh = Convert.ToBase64String(filehasher.ComputeHash(fs));
restoredCount++;
if (fh == f.Hash)
if (disableFileVerify)
{
Console.WriteLine(" done!");
systemIO.FileCopy(tf, targetfile, true);
Console.WriteLine(" done! (not verified)");
if (systemIO.FileExists(targetfile))
systemIO.FileCopy(tf, targetfile, true);
else
systemIO.FileMove(tf, targetfile);
}
else
{
Console.Write(" - Restored file hash mismatch");
if (systemIO.FileExists(targetfile))
Console.WriteLine(" - not overwriting existing file: {0}", targetfile);
string fh;
using (var fs = File.OpenRead(tf))
fh = Convert.ToBase64String(filehasher.ComputeHash(fs));
if (fh == f.Hash)
{
Console.WriteLine(" done!");
if (systemIO.FileExists(targetfile))
systemIO.FileCopy(tf, targetfile, true);
else
systemIO.FileMove(tf, targetfile);
}
else
Console.WriteLine(" - restoring file in damaged condition");
{
fileErrors++;
Console.Write(" - Restored file hash mismatch");
if (systemIO.FileExists(targetfile))
Console.WriteLine(" - not overwriting existing file: {0}", targetfile);
else
Console.WriteLine(" - restoring file in damaged condition");
}
}
}
}
catch (Exception ex)
{
errorCount++;
Console.WriteLine(" error: {0}", ex);
}
i++;
@@ -240,11 +250,80 @@ namespace Duplicati.CommandLine.RecoveryTool
}
}
return 0;
if (fileErrors > 0 || restoredCount != startedCount)
Console.WriteLine($"Restored {restoredCount} files of {startedCount} attempted ({Library.Utility.Utility.FormatSizeString(totalSize)}); {fileErrors} files had verification errors");
else
Console.WriteLine($"Restored {restoredCount} files ({Library.Utility.Utility.FormatSizeString(totalSize)})");
if (errorCount > 0)
Console.WriteLine("Errors: {0}", errorCount);
return errorCount != 0 || restoredCount != startedCount || fileErrors > 0
? 1
: 0;
}
private static string MapToRestorePath(string path, string prefixpath, string restorepath)
public static string GetLargestPrefix(IEnumerable<string> filePaths, out string sourceDirsep, out long filecount)
{
// Get dir separator like in LocalRestoreDatabase.GetLargestPrefix():
string largestprefix = filePaths.OrderByDescending(p => p.Length).FirstOrDefault() ?? string.Empty;
sourceDirsep = Util.GuessDirSeparator(largestprefix);
string[] dirsepSplit = new string[] { sourceDirsep };
string[] largestprefixparts = largestprefix.Split(dirsepSplit, StringSplitOptions.None);
// Because only files are in the list, need to remove filename from prefix
// Otherwise, in case of a single file the prefix is not a directory
if (largestprefixparts.Length > 0)
Array.Resize(ref largestprefixparts, largestprefixparts.Length - 1);
largestprefix = string.Join(sourceDirsep, largestprefixparts);
filecount = 0;
foreach (var path in filePaths)
{
if (largestprefix.Length > 1)
{
// Unix paths starting with / have an empty string in paths[0]
// Completely empty strings should not combine with that, so should have no parts at all
var parts = path.Length == 0 ? new string[0] : path.Split(dirsepSplit, StringSplitOptions.None);
var ni = 0;
for (; ni < Math.Min(parts.Length, largestprefixparts.Length); ni++)
if (!Library.Utility.Utility.ClientFilenameStringComparer.Equals(parts[ni], largestprefixparts[ni]))
break;
if (ni != largestprefixparts.Length)
{
if (ni == 0)
{
largestprefixparts = new string[0];
largestprefix = string.Empty;
}
else
{
// Only the first ni parts match
Array.Resize(ref largestprefixparts, ni);
largestprefix = string.Join(sourceDirsep, largestprefixparts);
}
}
}
filecount++;
}
return largestprefixparts.Length == 0 ? "" : Util.AppendDirSeparator(largestprefix, sourceDirsep);
}
public static string MapToRestorePath(string path, string prefixpath, string restorepath, string sourceDirsep)
{
if (sourceDirsep != null && sourceDirsep != Util.DirectorySeparatorString && sourceDirsep != Util.AltDirectorySeparatorString)
{
// Replace directory separator in source and prefix path
path = path.Replace(sourceDirsep, Util.DirectorySeparatorString);
if (!string.IsNullOrWhiteSpace(prefixpath))
{
prefixpath = prefixpath.Replace(sourceDirsep, Util.DirectorySeparatorString);
}
}
if (string.IsNullOrWhiteSpace(restorepath))
return path;
@@ -274,53 +353,69 @@ namespace Duplicati.CommandLine.RecoveryTool
private Stream m_indexfile;
private readonly List<string> m_lookup = new List<string>();
private readonly List<long> m_offsets = new List<long>();
private readonly Dictionary<string, string> m_indexMap;
private readonly byte[] m_linebuf = new byte[128];
private readonly byte[] m_newline = Environment.NewLine.Select(x => (byte)x).ToArray();
public HashLookupHelper(string indexfile, CompressedFileMRUCache cache, int blocksize, int hashsize)
public HashLookupHelper(string indexfile, CompressedFileMRUCache cache, int blocksize, int hashsize, bool useIndexMap)
{
m_cache = cache;
m_blocksize = blocksize;
m_hashsize = hashsize;
m_indexfile = File.OpenRead(indexfile);
// Build index ....
var hashes = 0L;
string prev = null;
m_indexfile.Position = 0;
foreach (var n in AllFileLines())
if (useIndexMap)
{
if (n.Key != prev)
{
hashes++;
prev = n.Key;
}
m_indexMap = new Dictionary<string, string>();
Console.WriteLine("Building in-memory index table ... ");
m_indexfile.Position = 0;
foreach (var n in AllFileLines())
m_indexMap[n.Key] = n.Value;
Console.WriteLine("Index table has {0} unique hashes", m_indexMap.Count);
}
Console.WriteLine("Index file has {0} hashes in total", hashes);
var lookuptablesize = Math.Max(1, Math.Min(LOOKUP_TABLE_SIZE, hashes) - 1);
var lookupincrements = hashes / lookuptablesize;
Console.WriteLine("Building lookup table with {0} entries, giving increments of {1}", lookuptablesize, lookupincrements);
prev = null;
var prevoff = 0L;
var hc = 0L;
m_indexfile.Position = 0;
foreach (var n in AllFileLines())
else
{
if (n.Key != prev)
// Build index ....
var hashes = 0L;
string prev = null;
m_indexfile.Position = 0;
foreach (var n in AllFileLines())
{
if ((hc % lookupincrements) == 0)
if (n.Key != prev)
{
m_lookup.Add(n.Key);
m_offsets.Add(prevoff);
hashes++;
prev = n.Key;
}
}
hc++;
prev = n.Key;
prevoff = m_indexfile.Position;
Console.WriteLine("Index file has {0} hashes in total", hashes);
var lookuptablesize = Math.Max(1, Math.Min(LOOKUP_TABLE_SIZE, hashes) - 1);
var lookupincrements = hashes / lookuptablesize;
Console.WriteLine("Building lookup table with {0} entries, giving increments of {1}", lookuptablesize, lookupincrements);
m_indexfile.Position = 0;
prev = null;
var prevoff = 0L;
var hc = 0L;
foreach (var n in AllFileLines())
{
if (n.Key != prev)
{
if ((hc % lookupincrements) == 0)
{
m_lookup.Add(n.Key);
m_offsets.Add(prevoff);
}
hc++;
prev = n.Key;
prevoff = m_indexfile.Position;
}
}
}
}
@@ -373,49 +468,55 @@ namespace Duplicati.CommandLine.RecoveryTool
}
}
public IEnumerable<string> ReadBlocklistHashes(string hash)
public IEnumerable<string> ReadBlocklistHashes(string hash, byte[] buffer)
{
var bytes = ReadHash(hash);
for (var i = 0; i < bytes.Length; i += m_hashsize)
yield return Convert.ToBase64String(bytes, i, m_hashsize);
var bytes = ReadHash(hash, buffer);
for (var i = 0; i < bytes; i += m_hashsize)
yield return Convert.ToBase64String(buffer, i, m_hashsize);
}
public byte[] ReadHash(string hash)
public int ReadHash(string hash, byte[] buffer)
{
var ix = m_lookup.BinarySearch(hash, StringComparer.Ordinal);
if (ix < 0)
// Use in-memory map if available
if (m_indexMap != null)
{
ix = ~ix;
if (ix != 0)
ix--;
if (!m_indexMap.TryGetValue(hash, out var filename))
throw new Exception(string.Format("Unable to locate block with hash: {0}", hash));
using (var fs = m_cache.ReadBlock(filename, hash))
return Duplicati.Library.Utility.Utility.ForceStreamRead(fs, buffer, buffer.Length);
}
m_indexfile.Position = m_offsets[ix];
foreach (var v in AllFileLines())
else
{
if (v.Key == hash)
using (var fs = m_cache.ReadBlock(v.Value, hash))
{
var buf = new byte[m_blocksize];
var l = Duplicati.Library.Utility.Utility.ForceStreamRead(fs, buf, buf.Length);
Array.Resize(ref buf, l);
var ix = m_lookup.BinarySearch(hash, StringComparer.Ordinal);
if (ix < 0)
{
ix = ~ix;
if (ix != 0)
ix--;
}
return buf;
}
m_indexfile.Position = m_offsets[ix];
foreach (var v in AllFileLines())
{
if (v.Key == hash)
using (var fs = m_cache.ReadBlock(v.Value, hash))
return Duplicati.Library.Utility.Utility.ForceStreamRead(fs, buffer, buffer.Length);
if (StringComparer.Ordinal.Compare(hash, v.Key) < 0)
break;
if (StringComparer.Ordinal.Compare(hash, v.Key) < 0)
break;
}
throw new Exception(string.Format("Unable to locate block with hash: {0}", hash));
}
throw new Exception(string.Format("Unable to locate block with hash: {0}", hash));
}
public void WriteHash(Stream sw, string hash)
public void WriteHash(Stream sw, string hash, byte[] buffer)
{
var h = ReadHash(hash);
sw.Write(h, 0, h.Length);
var h = ReadHash(hash, buffer);
sw.Write(buffer, 0, h);
}
@@ -427,28 +528,59 @@ namespace Duplicati.CommandLine.RecoveryTool
}
}
private class WrappedZipReader : IArchiveReader
{
private readonly ZipArchive m_zip;
public WrappedZipReader(Stream stream)
{
m_zip = new ZipArchive(stream, ZipArchiveMode.Read, true);
}
public void Dispose()
{
m_zip.Dispose();
}
public bool FileExists(string file) => throw new NotImplementedException();
public DateTime GetLastWriteTime(string file) => throw new NotImplementedException();
public string[] ListFiles(string prefix) => throw new NotImplementedException();
public IEnumerable<KeyValuePair<string, long>> ListFilesWithSize(string prefix) => throw new NotImplementedException();
public Stream OpenRead(string file) => m_zip.GetEntry(file).Open();
}
private class CompressedFileMRUCache : IDisposable
{
private Dictionary<string, Library.Interface.ICompression> m_lookup = new Dictionary<string, Duplicati.Library.Interface.ICompression>();
private Dictionary<string, Library.Interface.IArchiveReader> m_lookup = new Dictionary<string, Library.Interface.IArchiveReader>();
private Dictionary<string, Stream> m_streams = new Dictionary<string, Stream>();
private List<string> m_mru = new List<string>();
private readonly Dictionary<string, string> m_options;
private readonly bool m_useWrappedZip;
private const int MAX_OPEN_ARCHIVES = 20;
private readonly int MAX_OPEN_ARCHIVES = 200;
public CompressedFileMRUCache(Dictionary<string, string> options)
{
m_options = options;
m_useWrappedZip = !Library.Utility.Utility.ParseBoolOption(options, "disable-wrapped-zip");
if (options.TryGetValue("max-open-archives", out var maxopenarchives))
MAX_OPEN_ARCHIVES = int.Parse(maxopenarchives);
}
public Stream ReadBlock(string filename, string hash)
{
Library.Interface.ICompression cf;
Stream stream;
if (!m_lookup.TryGetValue(filename, out cf) || !m_streams.TryGetValue(filename, out stream))
if (!m_lookup.TryGetValue(filename, out var cf) || !m_streams.ContainsKey(filename))
{
stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
cf = Library.DynamicLoader.CompressionLoader.GetModule(Path.GetExtension(filename).Trim('.'), stream, Library.Interface.ArchiveMode.Read, m_options);
var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
var ext = Path.GetExtension(filename).Trim('.');
cf = m_useWrappedZip && ext.Equals("zip", StringComparison.OrdinalIgnoreCase)
? new WrappedZipReader(stream)
: Library.DynamicLoader.CompressionLoader.GetModule(ext, stream, Library.Interface.ArchiveMode.Read, m_options);
if (cf == null)
{
stream.Dispose();
@@ -458,7 +590,7 @@ namespace Duplicati.CommandLine.RecoveryTool
m_streams[filename] = stream;
}
if (m_mru.Count > 0 && m_mru[0] != filename)
if (m_mru.Count == 0 || m_mru[0] != filename)
{
m_mru.Remove(filename);
m_mru.Insert(0, filename);
@@ -467,7 +599,7 @@ namespace Duplicati.CommandLine.RecoveryTool
while (m_mru.Count > MAX_OPEN_ARCHIVES)
{
var f = m_mru.Last();
m_lookup[f].Dispose();
(m_lookup[f] as IDisposable)?.Dispose();
m_lookup.Remove(f);
m_streams[f].Dispose();
m_streams.Remove(f);
@@ -482,7 +614,7 @@ namespace Duplicati.CommandLine.RecoveryTool
public void Dispose()
{
foreach (var v in m_lookup.Values)
try { v.Dispose(); }
try { (v as IDisposable)?.Dispose(); }
catch { }
foreach (var v in m_streams.Values)
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.4.0" newVersion="3.0.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.3" newVersion="4.2.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
+18 -10
View File
@@ -2,7 +2,7 @@
=======================
This tool performs a recovery of as much data as possible in small steps that must be performed in order.
We recommend that you use Duplicati.CommandLine.exe to do the restore, and rely only on this tool if all else fails.
We recommend that you use %CLI_EXE% to do the restore, and rely only on this tool if all else fails.
The steps to perform are:
@@ -25,7 +25,7 @@ Optionally you can also run:
Shows what files are available and tests filters
5: Recompress
Ability to change compression type of files on remote backend eg. from 7z to zip
Ability to change compression type of files on remote backend e.g. from 7z to ZIP
@@ -38,17 +38,17 @@ Download
--------
Usage:
Duplicati.RecoveryTool.exe download <remoteurl> <localfolder> [options]
%RECOVERY_EXE% download <remoteurl> <localfolder> [options]
Downloads all files matching the Duplicati filenames from <remoteurl> to <localfolder>, and decrypts them in the process.
The remote url must be one supported by Duplicati.
Use `Duplicati.CommandLine.exe help backends` to see backends and options.
Use `%CLI_EXE% help backends` to see backends and options.
Index
-----
Duplicati.RecoveryTool.exe index <localfolder> [options]
%RECOVERY_EXE% index <localfolder> [options]
Examines all files found in localfolder and produces an index.txt file, which is a list of all block hashes found in the files.
The index file can be rather large. It defaults to being stored in <localfolder> but can be specified with --indexfile.
@@ -58,17 +58,25 @@ Some files are created in the system temporary folder, use --tempdir to set an a
Restore
-------
Duplicati.RecoveryTool.exe restore <localfolder> [version] [options]
%RECOVERY_EXE% restore <localfolder> [version] [options]
Restores all files to their respective destinations. Use --targetpath to choose another folder where the files are restored into.
Use the filters, --exclude, to perform a partial restore.
Version can be either a number, a filename or a date. If omitted the most recent backup is used.
The restore process requires a fast lookup, which is optimal if all the hashes can be kept in memory.
Use the option to --reduce-memory-use=true to toggle a slower low-memory restore.
If the process is interrupted for any reason, note the file counter and use --offset=1 to start the restore after the first file.
Advanced performance options are:
--reduce-memory-use: Disables keeping all hashes in memory; use if memory is limited on the restoring machine
--disable-file-verify: Disables the initial hashing of the restored file
--disable-wrapped-zip: Disable using the faster .NET native ZIP archive in favor of the more resilient one in Duplicati
--max-open-archives: Sets the number of archives to keep open for faster access (uses some memory pr. archive); default 200
List
----
Duplicati.RecoveryTool.exe list <localfolder> [version] [options]
%RECOVERY_EXE% list <localfolder> [version] [options]
Lists contents of <localfolder> backups.
Version can be either a number, a filename or a date.
@@ -79,7 +87,7 @@ Use the filters, --exclude, to show a subset of files.
Recompress
----
Duplicati.RecoveryTool.exe recompress <targetcompression> <remoteurl> <localfolder> --reupload --reencrypt [options]
%RECOVERY_EXE% recompress <targetcompression> <remoteurl> <localfolder> --reupload --reencrypt [options]
1) Downloads whole remote storage to <localfolder>.
2) Recompress from existing compression type to <targetcompression>
@@ -96,7 +104,7 @@ Supported Options
-----------------
The backend modules support all their normal options.
To see what options a specific backend supports, type:
Duplicati.CommandLine.exe help <backend>
%CLI_EXE% help <backend>
The environment variables AUTH_USERNAME and AUTH_PASSWORD are supported.
The options --parameters-file and --tempdir are supported.
The options --parameters-file and --tempdir are supported.
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Azure.KeyVault.Core" version="3.0.4" targetFramework="net471" />
<package id="Microsoft.Rest.ClientRuntime" version="2.3.20" targetFramework="net471" />
<package id="Microsoft.Rest.ClientRuntime.Azure" version="3.3.19" targetFramework="net471" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net471" />
</packages>
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Duplicati.CommandLine.SecretTool.Implementation</AssemblyName>
<Copyright>Copyright © 2024 Team Duplicati, MIT license</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\DynamicLoader\Duplicati.Library.DynamicLoader.csproj" />
<ProjectReference Include="..\..\Library\Utility\Duplicati.Library.Utility.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.Analyzers.Compatibility" Version="0.2.12-alpha">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
+134
View File
@@ -0,0 +1,134 @@
// 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.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.NamingConventionBinder;
using System.CommandLine.Parsing;
using Duplicati.Library.DynamicLoader;
using Duplicati.Library.Interface;
namespace Duplicati.CommandLine.SecretTool;
/// <summary>
/// The main entry point for the SecretTool application.
/// </summary>
public static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
public static Task<int> Main(string[] args)
{
Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.SecretTool);
var testcmd = new Command("test", "Tests the secret provider")
{
new Argument<string>("secret-url", description: "The connection string to the secret provider"),
new Argument<string[]>("secrets", description: "The secrets to fetch") { Arity = ArgumentArity.OneOrMore }
};
testcmd.Handler = CommandHandler.Create((string secretUrl, string[] secrets) => RunTest(secretUrl, secrets));
var infocmd = new Command("info", "Displays information about the secret provider")
{
new Argument<string>("secret-url", description: "The connection string to the secret provider, or just the leading part of the URL")
};
infocmd.Handler = CommandHandler.Create((string secretUrl) => ShowInfo(secretUrl));
var cmd = new RootCommand("Duplicati Secret Tool")
{
testcmd,
infocmd
};
return new CommandLineBuilder(cmd)
.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;
}
}).Build()
.InvokeAsync(args);
}
private static async Task<int> RunTest(string secretUrl, string[] secrets)
{
var secretProvider = SecretProviderLoader.CreateInstance(secretUrl);
await secretProvider.InitializeAsync(new Uri(secretUrl), CancellationToken.None);
var result = await secretProvider.ResolveSecretsAsync(secrets, CancellationToken.None);
Console.WriteLine("NOTE: Secret values are not displayed for security reasons");
Console.WriteLine("Secrets:");
foreach (var secret in secrets)
Console.WriteLine($"- {secret}: {(result.ContainsKey(secret) ? "Found!" : "Not found")}");
return 0;
}
private static Task<int> ShowInfo(string secretUrl)
{
var p = secretUrl.IndexOf(':');
var key = p >= 0 ? secretUrl.Substring(0, p) : secretUrl;
try
{
var metadata = SecretProviderLoader.GetProviderMetadata(key);
Console.WriteLine($"Secret provider '{key}'");
var lines = metadata.Description.Trim().Split(new char[] { '\r', '\n' }, StringSplitOptions.None);
foreach (var line in lines)
Console.WriteLine($" {line.Trim()}");
Console.WriteLine();
if (metadata.SupportedCommands.Count > 0)
{
Console.WriteLine("Supported options:");
foreach (var cmd in metadata.SupportedCommands)
{
Console.WriteLine($" {cmd.Name}: {cmd.ShortDescription}");
Console.WriteLine($" Type: {cmd.Type}");
if (cmd.DefaultValue != null)
Console.WriteLine($" Default: {cmd.DefaultValue}");
if (cmd.ValidValues != null)
Console.WriteLine($" Valid values: {string.Join(", ", cmd.ValidValues)}");
Console.WriteLine($" {cmd.LongDescription}");
Console.WriteLine();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"No information found for secret provider '{key}': {ex.Message}");
return Task.FromResult(1);
}
return Task.FromResult(0);
}
}
@@ -0,0 +1,22 @@
using System.CommandLine;
using System.CommandLine.Invocation;
namespace Duplicati.CommandLine.ServerUtil;
/// <summary>
/// Extensions for <see cref="Command"/>.
/// </summary>
public static class CommandExtensions
{
/// <summary>
/// Adds the missing WithHandler method to <see cref="Command"/>.
/// </summary>
/// <param name="command">The command to add the handler to.</param>
/// <param name="handler">The handler to add.</param>
/// <returns>The command with the handler added.</returns>
public static Command WithHandler(this Command command, ICommandHandler handler)
{
command.Handler = handler;
return command;
}
}
@@ -0,0 +1,36 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
using Duplicati.Library.Main;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class ChangePassword
{
public static Command Create() =>
new Command("change-password", "Changes the server password")
{
new Argument<string>("new-password", "The new password to use") {
Arity = ArgumentArity.ZeroOrOne
},
}
.WithHandler(CommandHandler.Create<Settings, string>(async (settings, newPassword) =>
{
// Ask for previous password first, if needed
var connection = await settings.GetConnection();
if (string.IsNullOrWhiteSpace(newPassword))
newPassword = HelperMethods.ReadPasswordFromConsole("Please provide the new password: ");
if (string.IsNullOrWhiteSpace(newPassword))
throw new UserReportedException("No password provided");
if (settings.SecretProvider != null)
{
var opts = new Dictionary<string, string?>() { { "password", newPassword } };
await settings.ReplaceSecrets(opts).ConfigureAwait(false);
newPassword = opts["password"]!;
}
await connection.ChangePassword(newPassword);
}));
}
@@ -0,0 +1,93 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Export
{
public static Command Create() =>
new Command("export", "Export a backup configuration")
{
new Argument<string[]>("backups", "The backup id or name to export, use 'all' to export all backups") {
Arity = ArgumentArity.OneOrMore
},
new Option<string?>(name: "--encryption-passphrase", description: "The passphrase to use for encrypting the backup configuration", getDefaultValue: () => null),
new Option<bool?>(name: "--export-passwords", description: "Flag toggling the inclusion of sensitive values, such as passwords, defaults to true if a passphrase is supplied", getDefaultValue: () => null),
new Option<bool>(name: "--overwrite", description: "Flag toggling the overwriting of existing files", getDefaultValue: () => false),
new Option<bool>(name: "--unencrypted", description: "Flag toggling unencrypted export of configurations", getDefaultValue: () => false),
new Option<DirectoryInfo>(name: "--destination", description: "The folder where the backup configuration should be exported to", getDefaultValue: () => new DirectoryInfo(Directory.GetCurrentDirectory())),
}
.WithHandler(CommandHandler.Create<Settings, string[], string?, bool?, bool, bool, DirectoryInfo>(async (settings, backups, encryptionPassphrase, exportPasswords, overwrite, unencrypted, destination) =>
{
if (!destination.Exists)
{
Console.WriteLine($"Creating destination folder {destination.FullName}");
destination.Create();
}
var connection = await settings.GetConnection();
var serverbackups = await connection.ListBackups();
var includeAllBackups = backups.Any(x => string.Equals(x, "all", StringComparison.OrdinalIgnoreCase));
var targetbackups = serverbackups.Where(b => includeAllBackups || backups.Any(x => b.Name.Contains(x, StringComparison.OrdinalIgnoreCase)) || backups.Contains(b.ID.ToString())).ToArray();
if (targetbackups.Length == 0)
throw new UserReportedException($"No backups found matching: {string.Join(", ", backups)}");
if (unencrypted)
{
if (!string.IsNullOrWhiteSpace(encryptionPassphrase))
throw new UserReportedException("The --unencrypted flag cannot be used with a passphrase");
if (!exportPasswords.HasValue)
{
Console.WriteLine("The --export-passwords flag is not set, sensitive keys will not be included in the exported file");
exportPasswords = false;
}
else if (exportPasswords.Value)
{
Console.WriteLine("Warning: Exporting unencrypted configurations with sensitive keys included");
}
}
else
{
if (string.IsNullOrWhiteSpace(encryptionPassphrase))
{
encryptionPassphrase = HelperMethods.ReadPasswordFromConsole("Please provide a passphrase to encrypt the backup configuration: ");
if (string.IsNullOrWhiteSpace(encryptionPassphrase))
throw new UserReportedException("No passphrase provided, use --unencrypted to export unencrypted configurations");
}
if (settings.SecretProvider != null)
{
var opts = new Dictionary<string, string?>() { { "password", encryptionPassphrase } };
await settings.ReplaceSecrets(opts).ConfigureAwait(false);
encryptionPassphrase = opts["password"]!;
}
if (!exportPasswords.HasValue)
exportPasswords = true;
}
Console.WriteLine($"Exporting {targetbackups.Length} backup{(targetbackups.Length == 1 ? "" : "s")} to {destination.FullName}");
foreach (var backup in targetbackups)
{
var name = backup.Name;
foreach (var c in Path.GetInvalidFileNameChars())
name = name.Replace(c, '_');
var file = new FileInfo(Path.Combine(destination.FullName, $"{backup.ID}-{backup.Name}.json{(unencrypted ? "" : ".aes")}"));
if (file.Exists && !overwrite)
{
Console.WriteLine($"Skipping existing file {file.FullName}, use --overwrite to force");
continue;
}
using (var s = await connection.ExportBackup(backup.ID, encryptionPassphrase, exportPasswords.Value))
using (var fs = file.Create())
await s.CopyToAsync(fs);
Console.WriteLine($"- Exported to {file.Name}");
}
}));
}
@@ -0,0 +1,38 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Health
{
public static Command Create() =>
new Command("health", "Checks the server health endpoint")
.WithHandler(CommandHandler.Create<Settings>(async (settings) =>
{
using var client = new HttpClient(new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = settings.Insecure
? HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
: null
})
{
BaseAddress = settings.HostUrl,
Timeout = TimeSpan.FromSeconds(10)
};
try
{
var response = await client.GetAsync("health");
response.EnsureSuccessStatusCode();
Console.WriteLine("Server is healthy");
return 0;
}
catch (HttpRequestException)
{
Console.WriteLine("Server is unhealthy");
return 1;
}
})
);
}
@@ -0,0 +1,56 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Import
{
public static Command Create() =>
new Command("import", "Import a backup configuration")
{
new Argument<FileInfo>("file", "The file to import, may be encrypted") {
Arity = ArgumentArity.ExactlyOne
},
new Argument<string>("passphrase", "The passphrase to use for decryption") {
Arity = ArgumentArity.ZeroOrOne
},
new Option<bool>(name: "--import-metadata", description: "Import metadata from the backup", getDefaultValue: () => false)
}
.WithHandler(CommandHandler.Create<Settings, FileInfo, string, bool>(async (settings, file, passphrase, importMetadata) =>
{
if (!file.Exists)
throw new UserReportedException($"File {file.FullName} does not exist");
Console.WriteLine($"Importing backup configuration from {file.FullName}");
if (IsEncrypted(file))
{
if (string.IsNullOrWhiteSpace(passphrase))
passphrase = HelperMethods.ReadPasswordFromConsole("The file is encrypted. Please provide the encryption password: ");
if (string.IsNullOrWhiteSpace(passphrase))
throw new UserReportedException("No password provided");
if (settings.SecretProvider != null)
{
var opts = new Dictionary<string, string?>() { { "password", passphrase } };
await settings.ReplaceSecrets(opts).ConfigureAwait(false);
passphrase = opts["password"]!;
}
}
var connection = await settings.GetConnection();
var result = await connection.ImportBackup(file.FullName, passphrase, importMetadata);
Console.WriteLine($"Imported \"{result.Name}\" with ID {result.ID}");
}));
private static bool IsEncrypted(FileInfo file)
{
using var fs = file.OpenRead();
var header = new byte[3].AsSpan();
if (fs.Read(header) != 3)
return false;
return header.SequenceEqual("AES"u8);
}
}
@@ -0,0 +1,24 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class IssueForeverToken
{
public static Command Create() =>
new Command("issue-forever-token", "Issues a long-lived access token")
{
}
.WithHandler(CommandHandler.Create<Settings>(async (settings) =>
{
var token = await (await settings.GetConnection()).CreateForeverToken();
Console.WriteLine("Token issued with a lifetime of 10 years.");
Console.WriteLine("Make sure you disable the forever token API on the server, to avoid generating new tokens.");
Console.WriteLine();
Console.WriteLine($"If you need to revoke the token, you can reset the JWT signing keys by restarting the server with the command '--{"reset-jwt-config"}=true', or the environment variable '{"DUPLICATI__RESET_JWT_CONFIG=true"}'.");
Console.WriteLine();
Console.WriteLine("The issued token is:");
Console.WriteLine($"Authorization: Bearer {token}");
Console.WriteLine();
}));
}
@@ -0,0 +1,28 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class ListBackups
{
public static Command Create() =>
new Command("list-backups", "List all backups")
.WithHandler(CommandHandler.Create<Settings>(async (settings) =>
{
var bks = await (await settings.GetConnection()).ListBackups();
if (!bks.Any())
{
Console.WriteLine("No backups found");
return;
}
foreach (var bk in bks)
{
Console.WriteLine($"{bk.ID}: {bk.Name}");
if (!string.IsNullOrEmpty(bk.Description))
Console.WriteLine($" {bk.Description}");
Console.WriteLine();
}
}));
}
@@ -0,0 +1,18 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Login
{
public static Command Create() =>
new Command("login", "Logs in to the server")
.WithHandler(CommandHandler.Create<Settings>(async (settings) =>
{
Console.WriteLine("Logging in to the server");
await Connection.Connect(settings, true);
Console.WriteLine("Logged in, persistent token saved");
})
);
}
@@ -0,0 +1,13 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Logout
{
public static Command Create()
=> new Command("logout", "Logs out of the server")
.WithHandler(CommandHandler.Create<Settings>(async (settings) =>
await (await settings.GetConnection()).Logout(settings))
);
}
@@ -0,0 +1,24 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Pause
{
public static Command Create() =>
new Command("pause", "Pauses the server")
{
new Argument<string?>("duration", description: "The duration to pause the server for", getDefaultValue: () => null) {
Arity = ArgumentArity.ZeroOrOne
},
}
.WithHandler(CommandHandler.Create<Settings, string?>(async (settings, duration) =>
{
if (string.IsNullOrWhiteSpace(duration))
Console.WriteLine("Pausing the server indefinitely");
else
Console.WriteLine($"Pausing the server for {duration}");
await (await settings.GetConnection()).Pause(duration);
}));
}
@@ -0,0 +1,13 @@
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
namespace Duplicati.CommandLine.ServerUtil.Commands;
public static class Resume
{
public static Command Create()
=> new Command("resume", "Resumes the server")
.WithHandler(CommandHandler.Create<Settings>(async (settings) =>
await (await settings.GetConnection()).Resume())
);
}

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