@@ -2,7 +2,7 @@ language: csharp
|
||||
solution: Duplicati.sln
|
||||
install:
|
||||
- nuget restore Duplicati.sln
|
||||
- nuget install NUnit.Runners -Version 2.6.4 -OutputDirectory testrunner
|
||||
- nuget install NUnit.Runners -Version 3.4.1 -OutputDirectory testrunner
|
||||
- sudo pip install selenium
|
||||
addons:
|
||||
sauce_connect:
|
||||
@@ -12,6 +12,9 @@ env:
|
||||
general:
|
||||
- USAGEREPORTER_Duplicati_LEVEL=none
|
||||
- AUTOUPDATER_Duplicati_SKIP_UPDATE=1
|
||||
matrix:
|
||||
- JOB=job1
|
||||
- JOB=job2
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
@@ -42,9 +45,24 @@ script:
|
||||
- ls -la ~/testdata/data
|
||||
- echo "travis_fold:end:download_extract_testdata"
|
||||
# run unit tests
|
||||
- echo "travis_fold:start:unit_tests"
|
||||
- mono ./testrunner/NUnit.Runners.2.6.4/tools/nunit-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll
|
||||
- echo "travis_fold:end:unit_tests"
|
||||
- echo "travis_fold:start:unit_tests_border"
|
||||
- if [[ "$JOB" == "job1" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==Border --workers=1; fi
|
||||
- echo "travis_fold:end:unit_tests_border"
|
||||
- echo "travis_fold:start:unit_tests_bulknormal"
|
||||
- if [[ "$JOB" == "job2" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==BulkNormal --workers=1; fi
|
||||
- echo "travis_fold:end:unit_tests_bulknormal"
|
||||
- echo "travis_fold:start:unit_tests_bulknosize"
|
||||
- if [[ "$JOB" == "job2" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==BulkNoSize --workers=1; fi
|
||||
- echo "travis_fold:end:unit_tests_bulknosize"
|
||||
- echo "travis_fold:start:unit_tests_svndata"
|
||||
- if [[ "$JOB" == "job1" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==SVNData --workers=1; fi
|
||||
- echo "travis_fold:end:unit_tests_svndata"
|
||||
- echo "travis_fold:start:unit_tests_svndatalong"
|
||||
- if [[ "$JOB" == "job1" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==SVNDataLong --workers=1; fi
|
||||
- echo "travis_fold:end:unit_tests_svndatalong"
|
||||
- echo "travis_fold:start:unit_tests_targeted"
|
||||
- if [[ "$JOB" == "job1" ]]; then mono ./testrunner/NUnit.ConsoleRunner.3.4.1/tools/nunit3-console.exe ./Duplicati/UnitTest/bin/Release/Duplicati.UnitTest.dll --where:cat==Targeted --workers=1; fi
|
||||
- echo "travis_fold:end:unit_tests_targeted"
|
||||
# start server and run gui tests
|
||||
- mono ./Duplicati/GUI/Duplicati.GUI.TrayIcon/bin/Release/Duplicati.Server.exe &
|
||||
- python guiTests/guiTest.py
|
||||
- if [[ "$JOB" == "job2" ]]; then python guiTests/guiTest.py; fi
|
||||
|
||||
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,36 @@
|
||||
@echo off
|
||||
rem ImageMagick and inkscape needed
|
||||
|
||||
mkdir temp
|
||||
inkscape -z -e temp/application_16.png -w 16 -h 16 application_icon.svg
|
||||
inkscape -z -e temp/application_20.png -w 20 -h 20 application_icon.svg
|
||||
inkscape -z -e temp/application_24.png -w 24 -h 24 application_icon.svg
|
||||
inkscape -z -e temp/application_32.png -w 32 -h 32 application_icon.svg
|
||||
inkscape -z -e temp/application_40.png -w 40 -h 40 application_icon.svg
|
||||
inkscape -z -e temp/application_48.png -w 48 -h 48 application_icon.svg
|
||||
inkscape -z -e temp/application_64.png -w 64 -h 64 application_icon.svg
|
||||
inkscape -z -e temp/application_96.png -w 96 -h 96 application_icon.svg
|
||||
inkscape -z -e temp/application_256.png -w 256 -h 256 application_icon.svg
|
||||
|
||||
mkdir osx_appicon
|
||||
inkscape -z -e osx_appicon/icon_16x16.png -w 16 -h 16 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_16x16@2x.png -w 32 -h 32 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_32x32.png -w 32 -h 32 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_32x32@2x.png -w 64 -h 64 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_64x64.png -w 64 -h 64 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_64x64@2x.png -w 128 -h 128 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_128x128.png -w 128 -h 128 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_128x128@2x.png -w 256 -h 256 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_256x256.png -w 256 -h 256 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_256x256@2x.png -w 512 -h 512 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_512x512.png -w 512 -h 512 application_icon.svg
|
||||
inkscape -z -e osx_appicon/icon_512x512@2x.png -w 1024 -h 1024 application_icon.svg
|
||||
|
||||
rem On OSX, the icns is created by:
|
||||
rem mv osx_appicon osx_appicon.iconset
|
||||
rem iconutil -c icns osx_appicon.iconset
|
||||
|
||||
convert temp/application_16.png temp/application_20.png temp/application_24.png temp/application_32.png temp/application_40.png temp/application_48.png temp/application_64.png temp/application_96.png temp/application_256.png application_icon.ico
|
||||
|
||||
rd /s /q temp
|
||||
pause
|
||||
@@ -1,65 +0,0 @@
|
||||
@echo off
|
||||
rem ImageMagick and inkscape needed
|
||||
|
||||
rem On non-windows, replace \ with /
|
||||
|
||||
mkdir temp
|
||||
inkscape -z -e temp\application_16.png -w 16 -h 16 application_icon.svg
|
||||
inkscape -z -e temp\application_20.png -w 20 -h 20 application_icon.svg
|
||||
inkscape -z -e temp\application_24.png -w 24 -h 24 application_icon.svg
|
||||
inkscape -z -e temp\application_32.png -w 32 -h 32 application_icon.svg
|
||||
inkscape -z -e temp\application_40.png -w 40 -h 40 application_icon.svg
|
||||
inkscape -z -e temp\application_48.png -w 48 -h 48 application_icon.svg
|
||||
inkscape -z -e temp\application_64.png -w 64 -h 64 application_icon.svg
|
||||
inkscape -z -e temp\application_96.png -w 96 -h 96 application_icon.svg
|
||||
inkscape -z -e temp\application_256.png -w 256 -h 256 application_icon.svg
|
||||
|
||||
inkscape -z -e temp\error_16.png -w 16 -h 16 tray_icons_error.svg
|
||||
inkscape -z -e temp\error_24.png -w 24 -h 24 tray_icons_error.svg
|
||||
inkscape -z -e temp\error_32.png -w 32 -h 32 tray_icons_error.svg
|
||||
inkscape -z -e temp\error_48.png -w 48 -h 48 tray_icons_error.svg
|
||||
inkscape -z -e temp\error_256.png -w 256 -h 256 tray_icons_error.svg
|
||||
|
||||
inkscape -z -e temp\inactive_16.png -w 16 -h 16 tray_icons_inactive.svg
|
||||
inkscape -z -e temp\inactive_24.png -w 24 -h 24 tray_icons_inactive.svg
|
||||
inkscape -z -e temp\inactive_32.png -w 32 -h 32 tray_icons_inactive.svg
|
||||
inkscape -z -e temp\inactive_48.png -w 48 -h 48 tray_icons_inactive.svg
|
||||
inkscape -z -e temp\inactive_256.png -w 256 -h 256 tray_icons_inactive.svg
|
||||
|
||||
inkscape -z -e temp\paused_16.png -w 16 -h 16 tray_icons_paused.svg
|
||||
inkscape -z -e temp\paused_24.png -w 24 -h 24 tray_icons_paused.svg
|
||||
inkscape -z -e temp\paused_32.png -w 32 -h 32 tray_icons_paused.svg
|
||||
inkscape -z -e temp\paused_48.png -w 48 -h 48 tray_icons_paused.svg
|
||||
inkscape -z -e temp\paused_256.png -w 256 -h 256 tray_icons_paused.svg
|
||||
|
||||
inkscape -z -e temp\running_16.png -w 16 -h 16 tray_icons_running.svg
|
||||
inkscape -z -e temp\running_24.png -w 24 -h 24 tray_icons_running.svg
|
||||
inkscape -z -e temp\running_32.png -w 32 -h 32 tray_icons_running.svg
|
||||
inkscape -z -e temp\running_48.png -w 48 -h 48 tray_icons_running.svg
|
||||
inkscape -z -e temp\running_256.png -w 256 -h 256 tray_icons_running.svg
|
||||
|
||||
mkdir osx_appicon
|
||||
inkscape -z -e osx_appicon\icon_16x16.png -w 16 -h 16 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_16x16@2x.png -w 32 -h 32 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_32x32.png -w 32 -h 32 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_32x32@2x.png -w 64 -h 64 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_64x64.png -w 64 -h 64 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_64x64@2x.png -w 128 -h 128 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_128x128.png -w 128 -h 128 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_128x128@2x.png -w 256 -h 256 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_256x256.png -w 256 -h 256 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_256x256@2x.png -w 512 -h 512 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_512x512.png -w 512 -h 512 application_icon.svg
|
||||
inkscape -z -e osx_appicon\icon_512x512@2x.png -w 1024 -h 1024 application_icon.svg
|
||||
|
||||
rem On OSX, the icns is created by:
|
||||
rem mv osx_appicon osx_appicon.iconset
|
||||
rem iconutil -c icns osx_appicon.iconset
|
||||
|
||||
convert temp\application_16.png temp\application_20.png temp\application_24.png temp\application_32.png temp\application_40.png temp\application_48.png temp\application_64.png temp\application_96.png temp\application_256.png application_icon.ico
|
||||
convert temp\error_16.png temp\error_24.png temp\error_32.png temp\error_48.png temp\error_256.png error.ico
|
||||
convert temp\inactive_16.png temp\inactive_24.png temp\inactive_32.png temp\inactive_48.png temp\inactive_256.png inactive.ico
|
||||
convert temp\paused_16.png temp\paused_24.png temp\paused_32.png temp\paused_48.png temp\paused_256.png paused.ico
|
||||
convert temp\running_16.png temp\running_24.png temp\running_32.png temp\running_48.png temp\running_256.png running.ico
|
||||
|
||||
rd /s /q temp
|
||||
@@ -0,0 +1,35 @@
|
||||
@echo off
|
||||
rem ImageMagick and inkscape needed
|
||||
|
||||
mkdir temp
|
||||
inkscape -z -e temp/pause_16.png -w 16 -h 16 tray_context_menu_pause.svg
|
||||
inkscape -z -e temp/pause_24.png -w 24 -h 24 tray_context_menu_pause.svg
|
||||
inkscape -z -e temp/pause_32.png -w 32 -h 32 tray_context_menu_pause.svg
|
||||
inkscape -z -e temp/pause_48.png -w 48 -h 48 tray_context_menu_pause.svg
|
||||
inkscape -z -e temp/pause_256.png -w 256 -h 256 tray_context_menu_pause.svg
|
||||
|
||||
inkscape -z -e temp/resume_16.png -w 16 -h 16 tray_context_menu_resume.svg
|
||||
inkscape -z -e temp/resume_24.png -w 24 -h 24 tray_context_menu_resume.svg
|
||||
inkscape -z -e temp/resume_32.png -w 32 -h 32 tray_context_menu_resume.svg
|
||||
inkscape -z -e temp/resume_48.png -w 48 -h 48 tray_context_menu_resume.svg
|
||||
inkscape -z -e temp/resume_256.png -w 256 -h 256 tray_context_menu_resume.svg
|
||||
|
||||
inkscape -z -e temp/open_16.png -w 16 -h 16 tray_context_menu_open.svg
|
||||
inkscape -z -e temp/open_24.png -w 24 -h 24 tray_context_menu_open.svg
|
||||
inkscape -z -e temp/open_32.png -w 32 -h 32 tray_context_menu_open.svg
|
||||
inkscape -z -e temp/open_48.png -w 48 -h 48 tray_context_menu_open.svg
|
||||
inkscape -z -e temp/open_256.png -w 256 -h 256 tray_context_menu_open.svg
|
||||
|
||||
inkscape -z -e temp/quit_16.png -w 16 -h 16 tray_context_menu_quit.svg
|
||||
inkscape -z -e temp/quit_24.png -w 24 -h 24 tray_context_menu_quit.svg
|
||||
inkscape -z -e temp/quit_32.png -w 32 -h 32 tray_context_menu_quit.svg
|
||||
inkscape -z -e temp/quit_48.png -w 48 -h 48 tray_context_menu_quit.svg
|
||||
inkscape -z -e temp/quit_256.png -w 256 -h 256 tray_context_menu_quit.svg
|
||||
|
||||
convert temp/pause_16.png temp/pause_24.png temp/pause_32.png temp/pause_48.png temp/pause_256.png context_menu_pause.ico
|
||||
convert temp/resume_16.png temp/resume_24.png temp/resume_32.png temp/resume_48.png temp/resume_256.png context_menu_play.ico
|
||||
convert temp/open_16.png temp/open_24.png temp/open_32.png temp/open_48.png temp/open_256.png context_menu_open.ico
|
||||
convert temp/quit_16.png temp/quit_24.png temp/quit_32.png temp/quit_48.png temp/quit_256.png context_menu_quit.ico
|
||||
|
||||
rd /s /q temp
|
||||
pause
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg id="tray_menu_icons" data-name="tray menu icons" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #65b1dd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<title>tray context menu icons</title>
|
||||
<g id="show_Kopie" data-name="show Kopie">
|
||||
<polygon class="cls-1" points="8 4.001 2 10.301 3.5 11.876 8 7.072 12.5 11.876 14 10.301 8 4.001"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 427 B |
@@ -0,0 +1,14 @@
|
||||
<svg id="tray_menu_icons" data-name="tray menu icons" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #65b1dd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<title>tray context menu icons</title>
|
||||
<g id="pause_Kopie" data-name="pause Kopie">
|
||||
<rect class="cls-1" x="4" y="2" width="3" height="12"/>
|
||||
<rect class="cls-1" x="9" y="2" width="3" height="12"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 445 B |
@@ -0,0 +1,13 @@
|
||||
<svg id="tray_menu_icons" data-name="tray menu icons" xmlns="http://www.w3.org/2000/svg" width="15.997" height="16" viewBox="0 0 15.997 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #65b1dd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<title>tray context menu icons</title>
|
||||
<g id="stop_Kopie" data-name="stop Kopie">
|
||||
<polygon id="stop" class="cls-1" points="13.998 3.95 12.048 2 7.998 6.05 3.948 2 1.998 3.95 6.049 8 1.998 12.05 3.948 14 7.998 9.95 12.048 14 13.998 12.05 9.949 8 13.998 3.95"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 513 B |
@@ -0,0 +1,13 @@
|
||||
<svg id="tray_menu_icons" data-name="tray menu icons" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #65b1dd;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<title>tray context menu icons</title>
|
||||
<g id="resume_Kopie" data-name="resume Kopie">
|
||||
<polygon class="cls-1" points="5 14 13 8 5 2 5 14"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 384 B |
@@ -0,0 +1,35 @@
|
||||
@echo off
|
||||
rem ImageMagick and inkscape needed
|
||||
|
||||
mkdir temp
|
||||
inkscape -z -e temp/error_16.png -w 16 -h 16 tray_icons_error.svg
|
||||
inkscape -z -e temp/error_24.png -w 24 -h 24 tray_icons_error.svg
|
||||
inkscape -z -e temp/error_32.png -w 32 -h 32 tray_icons_error.svg
|
||||
inkscape -z -e temp/error_48.png -w 48 -h 48 tray_icons_error.svg
|
||||
inkscape -z -e temp/error_256.png -w 256 -h 256 tray_icons_error.svg
|
||||
|
||||
inkscape -z -e temp/inactive_16.png -w 16 -h 16 tray_icons_inactive.svg
|
||||
inkscape -z -e temp/inactive_24.png -w 24 -h 24 tray_icons_inactive.svg
|
||||
inkscape -z -e temp/inactive_32.png -w 32 -h 32 tray_icons_inactive.svg
|
||||
inkscape -z -e temp/inactive_48.png -w 48 -h 48 tray_icons_inactive.svg
|
||||
inkscape -z -e temp/inactive_256.png -w 256 -h 256 tray_icons_inactive.svg
|
||||
|
||||
inkscape -z -e temp/paused_16.png -w 16 -h 16 tray_icons_paused.svg
|
||||
inkscape -z -e temp/paused_24.png -w 24 -h 24 tray_icons_paused.svg
|
||||
inkscape -z -e temp/paused_32.png -w 32 -h 32 tray_icons_paused.svg
|
||||
inkscape -z -e temp/paused_48.png -w 48 -h 48 tray_icons_paused.svg
|
||||
inkscape -z -e temp/paused_256.png -w 256 -h 256 tray_icons_paused.svg
|
||||
|
||||
inkscape -z -e temp/running_16.png -w 16 -h 16 tray_icons_running.svg
|
||||
inkscape -z -e temp/running_24.png -w 24 -h 24 tray_icons_running.svg
|
||||
inkscape -z -e temp/running_32.png -w 32 -h 32 tray_icons_running.svg
|
||||
inkscape -z -e temp/running_48.png -w 48 -h 48 tray_icons_running.svg
|
||||
inkscape -z -e temp/running_256.png -w 256 -h 256 tray_icons_running.svg
|
||||
|
||||
convert temp/error_16.png temp/error_24.png temp/error_32.png temp/error_48.png temp/error_256.png error.ico
|
||||
convert temp/inactive_16.png temp/inactive_24.png temp/inactive_32.png temp/inactive_48.png temp/inactive_256.png inactive.ico
|
||||
convert temp/paused_16.png temp/paused_24.png temp/paused_32.png temp/paused_48.png temp/paused_256.png paused.ico
|
||||
convert temp/running_16.png temp/running_24.png temp/running_32.png temp/running_48.png temp/running_256.png running.ico
|
||||
|
||||
rd /s /q temp
|
||||
pause
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 451 B |
|
Before Width: | Height: | Size: 372 B After Width: | Height: | Size: 372 B |
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
|
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 418 B |
|
Before Width: | Height: | Size: 778 B After Width: | Height: | Size: 778 B |
|
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 626 B |
|
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 690 B After Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -1,26 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationTool", "LocalizationTool\LocalizationTool.csproj", "{F093C3D3-28A8-405E-96C0-97AE2C296CCC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Utility", "..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj", "{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F093C3D3-28A8-405E-96C0-97AE2C296CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F093C3D3-28A8-405E-96C0-97AE2C296CCC}.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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,122 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LocalizationTool
|
||||
{
|
||||
public class CSVReader : IDisposable
|
||||
{
|
||||
private System.IO.StreamReader m_reader;
|
||||
|
||||
public CSVReader(string filename)
|
||||
{
|
||||
m_reader = new System.IO.StreamReader(filename, System.Text.Encoding.UTF8, true);
|
||||
}
|
||||
|
||||
public List<string> AdvanceLine()
|
||||
{
|
||||
bool inQuote = false;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
do
|
||||
{
|
||||
string line = m_reader.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
if (sb.Length == 0)
|
||||
return null;
|
||||
else
|
||||
throw new Exception("Unexpected EOF, buffer was: " + sb.ToString());
|
||||
}
|
||||
|
||||
sb.AppendLine(line);
|
||||
|
||||
foreach (char c in line)
|
||||
if (c == '"')
|
||||
inQuote = !inQuote;
|
||||
} while (inQuote);
|
||||
|
||||
string txt = sb.ToString();
|
||||
|
||||
if (txt.EndsWith("\r\n"))
|
||||
txt = txt.Substring(0, txt.Length - 2);
|
||||
else if (txt.EndsWith("\r\n"))
|
||||
txt = txt.Substring(0, txt.Length - 2);
|
||||
else if (txt.EndsWith("\n"))
|
||||
txt = txt.Substring(0, txt.Length - 1);
|
||||
else if (txt.EndsWith("\r"))
|
||||
txt = txt.Substring(0, txt.Length - 1);
|
||||
|
||||
|
||||
inQuote = false;
|
||||
int ix = 0;
|
||||
List<string> result = new List<string>();
|
||||
for(int i = 0; i < txt.Length; i++)
|
||||
if (inQuote)
|
||||
{
|
||||
if (txt[i] == '"')
|
||||
inQuote = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (txt[i] == '"')
|
||||
inQuote = true;
|
||||
else if (txt[i] == ',')
|
||||
{
|
||||
result.Add(CleanString(txt.Substring(ix, i - ix)));
|
||||
ix = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (inQuote)
|
||||
throw new Exception("Failed to parse line: " + txt);
|
||||
|
||||
if (ix != txt.Length)
|
||||
result.Add(CleanString(txt.Substring(ix)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string CleanString(string v)
|
||||
{
|
||||
//v = v.Trim();
|
||||
|
||||
if (v.StartsWith("\"") && v.EndsWith("\""))
|
||||
return v.Substring(1, v.Length - 2).Replace("\"\"", "\"");
|
||||
else
|
||||
return v;
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_reader != null)
|
||||
{
|
||||
m_reader.Dispose();
|
||||
m_reader = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,239 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LocalizationTool
|
||||
{
|
||||
public static class LinqHelpers
|
||||
{
|
||||
//
|
||||
// Summary:
|
||||
// Creates a System.Collections.Generic.Dictionary<TKey,TValue> from an System.Collections.Generic.IEnumerable<T>
|
||||
// according to a specified key selector function.
|
||||
//
|
||||
// Parameters:
|
||||
// source:
|
||||
// An System.Collections.Generic.IEnumerable<T> to create a System.Collections.Generic.Dictionary<TKey,TValue>
|
||||
// from.
|
||||
//
|
||||
// keySelector:
|
||||
// A function to extract a key from each element.
|
||||
//
|
||||
// Type parameters:
|
||||
// TSource:
|
||||
// The type of the elements of source.
|
||||
//
|
||||
// TKey:
|
||||
// The type of the key returned by keySelector.
|
||||
//
|
||||
// Returns:
|
||||
// A System.Collections.Generic.Dictionary<TKey,TValue> that contains keys and
|
||||
// values.
|
||||
//
|
||||
// Exceptions:
|
||||
// System.ArgumentNullException:
|
||||
// source or keySelector is null. -or- keySelector produces a key that is null.
|
||||
public static Dictionary<TKey, TSource> ToSafeDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, string locationName)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
if (keySelector == null)
|
||||
throw new ArgumentNullException("keySelector");
|
||||
|
||||
Dictionary<TKey, TSource> res = new Dictionary<TKey, TSource>();
|
||||
foreach (TSource el in source)
|
||||
{
|
||||
TKey k = keySelector(el);
|
||||
if (res.ContainsKey(k))
|
||||
Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName);
|
||||
else
|
||||
res.Add(k, el);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
//
|
||||
// Summary:
|
||||
// Creates a System.Collections.Generic.Dictionary<TKey,TValue> from an System.Collections.Generic.IEnumerable<T>
|
||||
// according to specified key selector and element selector functions.
|
||||
//
|
||||
// Parameters:
|
||||
// source:
|
||||
// An System.Collections.Generic.IEnumerable<T> to create a System.Collections.Generic.Dictionary<TKey,TValue>
|
||||
// from.
|
||||
//
|
||||
// keySelector:
|
||||
// A function to extract a key from each element.
|
||||
//
|
||||
// elementSelector:
|
||||
// A transform function to produce a result element value from each element.
|
||||
//
|
||||
// Type parameters:
|
||||
// TSource:
|
||||
// The type of the elements of source.
|
||||
//
|
||||
// TKey:
|
||||
// The type of the key returned by keySelector.
|
||||
//
|
||||
// TElement:
|
||||
// The type of the value returned by elementSelector.
|
||||
//
|
||||
// Returns:
|
||||
// A System.Collections.Generic.Dictionary<TKey,TValue> that contains values
|
||||
// of type TElement selected from the input sequence.
|
||||
//
|
||||
// Exceptions:
|
||||
// System.ArgumentNullException:
|
||||
// source or keySelector or elementSelector is null. -or- keySelector produces
|
||||
// a key that is null.
|
||||
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, string locationName)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
if (keySelector == null)
|
||||
throw new ArgumentNullException("keySelector");
|
||||
if (elementSelector == null)
|
||||
throw new ArgumentNullException("elementSelector");
|
||||
|
||||
Dictionary<TKey, TElement> res = new Dictionary<TKey, TElement>();
|
||||
foreach (TSource el in source)
|
||||
{
|
||||
TKey k = keySelector(el);
|
||||
if (res.ContainsKey(k))
|
||||
Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName);
|
||||
else
|
||||
res.Add(k, elementSelector(el));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
//
|
||||
// Summary:
|
||||
// Creates a System.Collections.Generic.Dictionary<TKey,TValue> from an System.Collections.Generic.IEnumerable<T>
|
||||
// according to a specified key selector function and key comparer.
|
||||
//
|
||||
// Parameters:
|
||||
// source:
|
||||
// An System.Collections.Generic.IEnumerable<T> to create a System.Collections.Generic.Dictionary<TKey,TValue>
|
||||
// from.
|
||||
//
|
||||
// keySelector:
|
||||
// A function to extract a key from each element.
|
||||
//
|
||||
// comparer:
|
||||
// An System.Collections.Generic.IEqualityComparer<T> to compare keys.
|
||||
//
|
||||
// Type parameters:
|
||||
// TSource:
|
||||
// The type of the elements of source.
|
||||
//
|
||||
// TKey:
|
||||
// The type of the keys returned by keySelector.
|
||||
//
|
||||
// Returns:
|
||||
// A System.Collections.Generic.Dictionary<TKey,TValue> that contains keys and
|
||||
// values.
|
||||
//
|
||||
// Exceptions:
|
||||
// System.ArgumentNullException:
|
||||
// source or keySelector is null. -or- keySelector produces a key that is null.
|
||||
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer, string locationName)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
if (keySelector == null)
|
||||
throw new ArgumentNullException("keySelector");
|
||||
|
||||
Dictionary<TKey, TSource> res = new Dictionary<TKey, TSource>(comparer);
|
||||
foreach (TSource el in source)
|
||||
{
|
||||
TKey k = keySelector(el);
|
||||
if (res.ContainsKey(k))
|
||||
Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName);
|
||||
else
|
||||
res.Add(k, el);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
//
|
||||
// Summary:
|
||||
// Creates a System.Collections.Generic.Dictionary<TKey,TValue> from an System.Collections.Generic.IEnumerable<T>
|
||||
// according to a specified key selector function, a comparer, and an element
|
||||
// selector function.
|
||||
//
|
||||
// Parameters:
|
||||
// source:
|
||||
// An System.Collections.Generic.IEnumerable<T> to create a System.Collections.Generic.Dictionary<TKey,TValue>
|
||||
// from.
|
||||
//
|
||||
// keySelector:
|
||||
// A function to extract a key from each element.
|
||||
//
|
||||
// elementSelector:
|
||||
// A transform function to produce a result element value from each element.
|
||||
//
|
||||
// comparer:
|
||||
// An System.Collections.Generic.IEqualityComparer<T> to compare keys.
|
||||
//
|
||||
// Type parameters:
|
||||
// TSource:
|
||||
// The type of the elements of source.
|
||||
//
|
||||
// TKey:
|
||||
// The type of the key returned by keySelector.
|
||||
//
|
||||
// TElement:
|
||||
// The type of the value returned by elementSelector.
|
||||
//
|
||||
// Returns:
|
||||
// A System.Collections.Generic.Dictionary<TKey,TValue> that contains values
|
||||
// of type TElement selected from the input sequence.
|
||||
//
|
||||
// Exceptions:
|
||||
// System.ArgumentNullException:
|
||||
// source or keySelector or elementSelector is null. -or- keySelector produces
|
||||
// a key that is null.
|
||||
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer, string locationName)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
if (keySelector == null)
|
||||
throw new ArgumentNullException("keySelector");
|
||||
if (elementSelector == null)
|
||||
throw new ArgumentNullException("elementSelector");
|
||||
|
||||
Dictionary<TKey, TElement> res = new Dictionary<TKey, TElement>(comparer);
|
||||
foreach (TSource el in source)
|
||||
{
|
||||
TKey k = keySelector(el);
|
||||
if (res.ContainsKey(k))
|
||||
Console.WriteLine("*** Warning, duplicate key: " + k.ToString() + " in " + locationName);
|
||||
else
|
||||
res.Add(k, elementSelector(el));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{F093C3D3-28A8-405E-96C0-97AE2C296CCC}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>LocalizationTool</RootNamespace>
|
||||
<AssemblyName>LocalizationTool</AssemblyName>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\..\..\Duplicati\Localization\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Commandlineparameters>export</Commandlineparameters>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>..\..\..\Duplicati\Localization\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CSVReader.cs" />
|
||||
<Compile Include="LinqHelpers.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="UpdateGUI.resx">
|
||||
<DependentUpon>UpdateGUI.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<Compile Include="ResXCompiler.cs" />
|
||||
<Compile Include="UpdateGUI.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="UpdateGUI.Designer.cs">
|
||||
<DependentUpon>UpdateGUI.cs</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="Resources\Empty_resx.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Duplicati\Library\Utility\Duplicati.Library.Utility.csproj">
|
||||
<Project>{DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65}</Project>
|
||||
<Name>Duplicati.Library.Utility</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.2.0">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 2.0 %28x86%29</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.0">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.0 %28x86%29</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\Batchjob.bat.txt" />
|
||||
</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>
|
||||
@@ -1,999 +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;
|
||||
using System.Windows.Forms;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace LocalizationTool
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
//All relative paths are to the app dir
|
||||
System.IO.Directory.SetCurrentDirectory(Application.StartupPath);
|
||||
|
||||
string loc = args.Length >= 2 ? args[1] : null;
|
||||
|
||||
switch (args[0].ToLower())
|
||||
{
|
||||
case "clean":
|
||||
case "cleanup":
|
||||
Clean(loc);
|
||||
break;
|
||||
case "build":
|
||||
Compile(loc);
|
||||
break;
|
||||
case "update":
|
||||
Update(loc);
|
||||
break;
|
||||
case "create":
|
||||
if (args.Length != 2)
|
||||
{
|
||||
Console.WriteLine("missing locale identifier");
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
Create(loc);
|
||||
break;
|
||||
case "report":
|
||||
Report(loc);
|
||||
break;
|
||||
case "export":
|
||||
Export(loc);
|
||||
break;
|
||||
case "exportdiff":
|
||||
if (args.Length != 3)
|
||||
{
|
||||
Console.WriteLine("missing locale identifier or input CSV file");
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
ExportDiff(loc, args[2]);
|
||||
break;
|
||||
case "import":
|
||||
if (args.Length != 3)
|
||||
{
|
||||
Console.WriteLine("missing locale identifier or input CSV file");
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
Import(loc, args[2]);
|
||||
break;
|
||||
case "guiupdate":
|
||||
Application.EnableVisualStyles();
|
||||
Application.DoEvents();
|
||||
|
||||
Report(loc);
|
||||
new UpdateGUI(System.IO.Path.Combine(Application.StartupPath, "report." + loc + ".xml")).ShowDialog();
|
||||
break;
|
||||
case "webpull":
|
||||
WebPull(loc, false);
|
||||
break;
|
||||
case "webupdate":
|
||||
WebPull(loc, true);
|
||||
break;
|
||||
default:
|
||||
PrintUsage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void PrintUsage()
|
||||
{
|
||||
Console.WriteLine("Usage: ");
|
||||
|
||||
Console.WriteLine("LocalizationTool.exe CLEAN [locale identifier]");
|
||||
Console.WriteLine("LocalizationTool.exe BUILD [locale identifier]");
|
||||
Console.WriteLine("LocalizationTool.exe UPDATE [locale identifier]");
|
||||
Console.WriteLine("LocalizationTool.exe REPORT [locale identifier]");
|
||||
Console.WriteLine("LocalizationTool.exe CREATE <locale indentifier>");
|
||||
Console.WriteLine("LocalizationTool.exe GUIUPDATE <locale identifier>");
|
||||
Console.WriteLine("LocalizationTool.exe EXPORT [locale identifier]");
|
||||
Console.WriteLine("LocalizationTool.exe IMPORT <locale identifier> <input CSV file>");
|
||||
Console.WriteLine("LocalizationTool.exe EXPORTDIFF <locale identifier> <input CSV file>");
|
||||
Console.WriteLine("LocalizationTool.exe WEBPULL [locale identifier]");
|
||||
Console.WriteLine("LocalizationTool.exe WEBUPDATE [locale identifier]");
|
||||
}
|
||||
|
||||
private struct CSVEntry
|
||||
{
|
||||
public string Filename;
|
||||
public string Fieldkey;
|
||||
public string Origvalue;
|
||||
public string Value;
|
||||
public string Status;
|
||||
|
||||
public string[] extraFields;
|
||||
|
||||
public CSVEntry(List<string> fields)
|
||||
{
|
||||
Filename = fields[0];
|
||||
Fieldkey = fields[1];
|
||||
Status = fields[2];
|
||||
Origvalue = fields[3];
|
||||
Value = fields[4];
|
||||
|
||||
if (fields.Count > 5)
|
||||
{
|
||||
extraFields = new string[fields.Count - 5];
|
||||
fields.CopyTo(5, extraFields, 0, extraFields.Length);
|
||||
}
|
||||
else
|
||||
extraFields = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsValidCulture(string c)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Globalization.CultureInfo.GetCultureInfo(c);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads updated CSV files from the web and generates diff files, and optionally updates the local CSV files
|
||||
/// </summary>
|
||||
/// <param name="loc">The culture to use</param>
|
||||
/// <param name="update">True if the elements should be updated as well</param>
|
||||
private static void WebPull(string loc, bool update)
|
||||
{
|
||||
var locs =
|
||||
from n in Directory.GetFiles(Application.StartupPath, "report.*.url")
|
||||
where IsValidCulture(Path.GetFileNameWithoutExtension(n).Substring("report.".Length))
|
||||
select Path.GetFileNameWithoutExtension(n).Substring("report.".Length);
|
||||
|
||||
if (!string.IsNullOrEmpty(loc))
|
||||
{
|
||||
locs = locs.Where(c => string.Equals(c, loc, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (locs.Count() == 0)
|
||||
{
|
||||
Console.WriteLine("No URL file found for culture {0}", loc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (locs.Count() == 0)
|
||||
{
|
||||
Console.WriteLine("No culture.url files found");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var culture in locs)
|
||||
{
|
||||
var url = string.Format("report.{0}.url", culture);
|
||||
var file = string.Format("report.{0}.new.csv", culture);
|
||||
|
||||
if (File.Exists(file))
|
||||
File.Delete(file);
|
||||
Console.Write("Downloading updated CSV for {0} ... ", culture);
|
||||
try { new WebClient().DownloadFile(File.ReadAllText(url).Trim(), file); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Failed!");
|
||||
Console.WriteLine(" -> {0}", ex.Message);
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine("Done!");
|
||||
|
||||
if (update)
|
||||
{
|
||||
Console.Write("Updating local files for {0} .", culture);
|
||||
ExportDiff(culture, file);
|
||||
Console.Write(".");
|
||||
Import(culture, file);
|
||||
Console.Write(".");
|
||||
Export(culture);
|
||||
Console.WriteLine(" Done!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports a CSV file into a dictionary format,
|
||||
/// Outer key is filename, inner key is fieldname, inner value is translated text
|
||||
/// </summary>
|
||||
/// <param name="file">The CSV file to read from</param>
|
||||
/// <param name="loc">The current culture string</param>
|
||||
/// <returns>Dictionary where outer key is filename, inner key is fieldname, inner value is translated text</returns>
|
||||
private static Dictionary<string, Dictionary<string, CSVEntry>> ImportCSV(string file, string loc, Func<CSVEntry, bool> filter)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> values = new Dictionary<string, Dictionary<string, CSVEntry>>();
|
||||
|
||||
using (CSVReader r = new CSVReader(file))
|
||||
{
|
||||
List<string> fields = null;
|
||||
while ((fields = r.AdvanceLine()) != null)
|
||||
if (fields.Count >= 5)
|
||||
{
|
||||
CSVEntry e = new CSVEntry(fields);
|
||||
e.Filename = System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, loc), e.Filename);
|
||||
|
||||
if (filter != null && filter(e))
|
||||
continue;
|
||||
|
||||
if (!values.ContainsKey(e.Filename))
|
||||
values.Add(e.Filename, new Dictionary<string, CSVEntry>());
|
||||
|
||||
Dictionary<string, CSVEntry> l = values[e.Filename];
|
||||
l[e.Fieldkey] = e;
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static void Import(string loc, string p)
|
||||
{
|
||||
//Outer key is filename, inner key is fieldname, inner value is translated text
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> values = ImportCSV(p, loc, e => e.Value.Trim().Length == 0 || e.Value == e.Origvalue);
|
||||
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
{
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> remap = new Dictionary<string, Dictionary<string, CSVEntry>>();
|
||||
foreach(string key in values.Keys)
|
||||
remap[key.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString ())] = values[key];
|
||||
|
||||
values = remap;
|
||||
}
|
||||
|
||||
string folder = System.IO.Path.Combine(Application.StartupPath, loc);
|
||||
if (!System.IO.Directory.Exists(folder))
|
||||
Create(loc);
|
||||
else
|
||||
Update(loc);
|
||||
|
||||
XNamespace xmlns = "";
|
||||
foreach (ResXFileInfo inf in GetResXList(loc))
|
||||
{
|
||||
if (System.IO.File.Exists(inf.TargetFile) && values.ContainsKey(inf.TargetFile))
|
||||
{
|
||||
XDocument targetDoc = XDocument.Load(inf.TargetFile);
|
||||
XNode insertTarget = targetDoc.Element("root").LastNode;
|
||||
var targetVals = targetDoc.Element("root").Elements("data").ToSafeDictionary(c => c.Attribute("name").Value, inf.TargetFile);
|
||||
var sourceVals = values[inf.TargetFile];
|
||||
values.Remove(inf.TargetFile);
|
||||
|
||||
bool updated = false;
|
||||
foreach (var item in sourceVals)
|
||||
if (targetVals.ContainsKey(item.Key))
|
||||
{
|
||||
if (targetVals[item.Key].Element("value").Value.Replace("\r\n", "\n") != item.Value.Value.Replace("\r\n", "\n"))
|
||||
{
|
||||
updated = true;
|
||||
targetVals[item.Key].Element("value").Value = item.Value.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
updated = true;
|
||||
insertTarget.AddAfterSelf(new XElement("data",
|
||||
new XAttribute("name", item.Key),
|
||||
new XAttribute(xmlns + "space", "preserve"),
|
||||
new XElement("value", item.Value.Value)
|
||||
));
|
||||
}
|
||||
|
||||
if (updated)
|
||||
targetDoc.Save(inf.TargetFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (values.Count != 0)
|
||||
Console.WriteLine("The following files were translated but did not exist: " + string.Join(Environment.NewLine, values.Keys.ToArray()));
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<string> GetLocaleFolders(string locid)
|
||||
{
|
||||
if (string.IsNullOrEmpty(locid))
|
||||
return System.IO.Directory.GetDirectories(Application.StartupPath).Where(c =>
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Globalization.CultureInfo.GetCultureInfo(System.IO.Path.GetFileName(c));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}).Select(c => System.IO.Path.GetFileName(c));
|
||||
else
|
||||
return new string[] { locid };
|
||||
}
|
||||
|
||||
private static void Export(string cultures)
|
||||
{
|
||||
Update(cultures);
|
||||
Report(cultures);
|
||||
|
||||
foreach (string culture in GetLocaleFolders(cultures))
|
||||
{
|
||||
XDocument doc = XDocument.Load(System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".xml"));
|
||||
string outfile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".csv");
|
||||
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> elems = new Dictionary<string, Dictionary<string, CSVEntry>>();
|
||||
foreach (var file in doc.Element("root").Elements("file"))
|
||||
{
|
||||
string filename = file.Attribute("filename").Value.Substring(Duplicati.Library.Utility.Utility.AppendDirSeparator(Application.StartupPath).Length);
|
||||
filename = filename.Substring(culture.Length + 1);
|
||||
|
||||
Dictionary<string, CSVEntry> e = new Dictionary<string, CSVEntry>();
|
||||
elems[filename] = e;
|
||||
|
||||
foreach (var item in file.Element("updated").Elements("item"))
|
||||
{
|
||||
CSVEntry c = new CSVEntry();
|
||||
c.Filename = filename;
|
||||
c.Fieldkey = item.Attribute("name").Value;
|
||||
c.Status = item.Parent.Name.LocalName;
|
||||
c.Origvalue = item.Element("original").Value;
|
||||
c.Value = item.Element("translated").Value;
|
||||
e.Add(c.Fieldkey, c);
|
||||
|
||||
}
|
||||
|
||||
foreach (var item in file.Element("missing").Elements("item"))
|
||||
{
|
||||
CSVEntry c = new CSVEntry();
|
||||
c.Filename = filename;
|
||||
c.Fieldkey = item.Attribute("name").Value;
|
||||
c.Status = item.Parent.Name.LocalName;
|
||||
c.Origvalue = item.Value;
|
||||
c.Value = "";
|
||||
e.Add(c.Fieldkey, c);
|
||||
}
|
||||
|
||||
foreach (var item in file.Element("not-updated").Elements("item"))
|
||||
{
|
||||
CSVEntry c = new CSVEntry();
|
||||
c.Filename = filename;
|
||||
c.Fieldkey = item.Attribute("name").Value;
|
||||
c.Status = item.Parent.Name.LocalName;
|
||||
c.Origvalue = item.Value;
|
||||
c.Value = "";
|
||||
e.Add(c.Fieldkey, c);
|
||||
}
|
||||
|
||||
foreach (var item in file.Element("unused").Elements("item"))
|
||||
{
|
||||
CSVEntry c = new CSVEntry();
|
||||
c.Filename = filename;
|
||||
c.Fieldkey = item.Attribute("name").Value;
|
||||
c.Status = item.Parent.Name.LocalName;
|
||||
c.Origvalue = "";
|
||||
c.Value = item.Value;
|
||||
e.Add(c.Fieldkey, c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WriteCSVFile(outfile, culture, elems, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExportDiff(string culture, string inputfile)
|
||||
{
|
||||
inputfile = System.IO.Path.GetFullPath(inputfile);
|
||||
|
||||
string currentFile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".csv");
|
||||
string diffFile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".diff.csv");
|
||||
|
||||
if (currentFile.Equals(inputfile, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision))
|
||||
throw new Exception("Input file will be overwritten, please use another file");
|
||||
|
||||
Export(culture);
|
||||
|
||||
|
||||
//Outer key is filename, inner key is fieldname, inner value is translated text
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> inputValues = ImportCSV(inputfile, culture, null);
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> currentValues = ImportCSV(currentFile, culture, null);
|
||||
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> added = new Dictionary<string, Dictionary<string, CSVEntry>>();
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> removed = new Dictionary<string, Dictionary<string, CSVEntry>>();
|
||||
|
||||
foreach (var f in currentValues)
|
||||
{
|
||||
Dictionary<string, CSVEntry> other;
|
||||
inputValues.TryGetValue(f.Key, out other);
|
||||
|
||||
if (other == null)
|
||||
{
|
||||
added.Add(f.Key, f.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<string, CSVEntry> a = new Dictionary<string,CSVEntry>();
|
||||
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
{
|
||||
if (other.ContainsKey(s.Key))
|
||||
other.Remove(s.Key);
|
||||
else
|
||||
a.Add(s.Key, s.Value);
|
||||
}
|
||||
|
||||
if (a.Count > 0)
|
||||
added.Add(f.Key, a);
|
||||
|
||||
if (other.Count > 0)
|
||||
removed.Add(f.Key, other);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, Dictionary<string, string>> overrides = new Dictionary<string, Dictionary<string, string>>();
|
||||
Dictionary<string, Dictionary<string, CSVEntry>> diffElems = new Dictionary<string, Dictionary<string, CSVEntry>>();
|
||||
|
||||
foreach (KeyValuePair<string, Dictionary<string, CSVEntry>> f in added)
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
if (s.Value.Origvalue == s.Value.Value || s.Value.Value.Trim().Length == 0)
|
||||
{
|
||||
if (!overrides.ContainsKey(f.Key))
|
||||
overrides.Add(f.Key, new Dictionary<string, string>());
|
||||
if (!diffElems.ContainsKey(f.Key))
|
||||
diffElems.Add(f.Key, new Dictionary<string, CSVEntry>());
|
||||
overrides[f.Key].Add(s.Key, "not-updated");
|
||||
diffElems[f.Key].Add(s.Key, s.Value);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, Dictionary<string, CSVEntry>> f in removed)
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
{
|
||||
if (!overrides.ContainsKey(f.Key))
|
||||
overrides.Add(f.Key, new Dictionary<string, string>());
|
||||
if (!diffElems.ContainsKey(f.Key))
|
||||
diffElems.Add(f.Key, new Dictionary<string, CSVEntry>());
|
||||
overrides[f.Key].Add(s.Key, "unused");
|
||||
diffElems[f.Key].Add(s.Key, s.Value);
|
||||
}
|
||||
|
||||
WriteCSVFile(diffFile, culture, diffElems, overrides);
|
||||
|
||||
//Re-read the file
|
||||
inputValues = ImportCSV(inputfile, culture, null);
|
||||
|
||||
//Add the new entries
|
||||
foreach (KeyValuePair<string, Dictionary<string, CSVEntry>> f in added)
|
||||
{
|
||||
if (!inputValues.ContainsKey(f.Key))
|
||||
inputValues.Add(f.Key, new Dictionary<string, CSVEntry>());
|
||||
|
||||
Dictionary<string, CSVEntry> o = inputValues[f.Key];
|
||||
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
o.Add(s.Key, s.Value);
|
||||
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, Dictionary<string, CSVEntry>> f in currentValues)
|
||||
{
|
||||
Dictionary<string, CSVEntry> o = inputValues[f.Key];
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
{
|
||||
CSVEntry c = o[s.Key];
|
||||
if (s.Value.Status == "unused")
|
||||
c.Status = "unused";
|
||||
else if (c.Origvalue == c.Value)
|
||||
c.Status = "not-updated";
|
||||
else if (string.IsNullOrEmpty(c.Value) || c.Value.Trim().Length == 0)
|
||||
c.Status = "missing";
|
||||
else
|
||||
c.Status = "updated";
|
||||
|
||||
o[s.Key] = c;
|
||||
}
|
||||
}
|
||||
|
||||
//Update the removed entries
|
||||
foreach (KeyValuePair<string, Dictionary<string, CSVEntry>> f in removed)
|
||||
{
|
||||
Dictionary<string, CSVEntry> o = inputValues[f.Key];
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
{
|
||||
CSVEntry c = o[s.Key];
|
||||
c.Status = "unused";
|
||||
o[s.Key] = c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Write the output file
|
||||
diffFile = System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".updated.csv");
|
||||
WriteCSVFile(diffFile, culture, inputValues, null);
|
||||
}
|
||||
|
||||
private static void WriteCSVFile(string filename, string culture, Dictionary<string, Dictionary<string, CSVEntry>> entries, Dictionary<string, Dictionary<string, string>> status_overrides)
|
||||
{
|
||||
if (status_overrides == null)
|
||||
status_overrides = new Dictionary<string, Dictionary<string, string>>();
|
||||
|
||||
List<KeyValuePair<string, KeyValuePair<string, CSVEntry>>> sortlist = new List<KeyValuePair<string, KeyValuePair<string, CSVEntry>>>();
|
||||
foreach (KeyValuePair<string, Dictionary<string, CSVEntry>> f in entries)
|
||||
foreach (KeyValuePair<string, CSVEntry> s in f.Value)
|
||||
sortlist.Add(new KeyValuePair<string, KeyValuePair<string, CSVEntry>>(f.Key, s));
|
||||
|
||||
var sortedlist = sortlist.OrderBy(x => x.Key + System.IO.Path.PathSeparator + x.Value.Key);
|
||||
|
||||
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(filename, false, new System.Text.UTF8Encoding(false)))
|
||||
{
|
||||
string filenameprefix = System.IO.Path.Combine(Application.StartupPath, culture);
|
||||
int pfl = filenameprefix.Length + 1;
|
||||
|
||||
foreach (KeyValuePair<string, KeyValuePair<string, CSVEntry>> f in sortedlist)
|
||||
{
|
||||
string status = f.Value.Value.Status;
|
||||
if (status_overrides.ContainsKey(f.Key) && status_overrides[f.Key].ContainsKey(f.Value.Key))
|
||||
status = status_overrides[f.Key][f.Value.Key];
|
||||
|
||||
string path = f.Key;
|
||||
if (f.Key.StartsWith(filenameprefix, Duplicati.Library.Utility.Utility.ClientFilenameStringComparision))
|
||||
path = f.Key.Substring(Math.Min(pfl, f.Key.Length));
|
||||
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
path = path.Replace(System.IO.Path.DirectorySeparatorChar.ToString(), "\\");
|
||||
|
||||
WriteCSVLine(sw, path, f.Value.Key, status, f.Value.Value.Origvalue, f.Value.Value.Value, f.Value.Value.extraFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string CSV_SEPARATOR = ",";
|
||||
|
||||
private static void WriteCSVLine(System.IO.StreamWriter sw, string filename, string key, string status, string originalText, string translatedText, string[] extraFields)
|
||||
{
|
||||
sw.Write(EscapeCSVString(filename));
|
||||
sw.Write(CSV_SEPARATOR);
|
||||
sw.Write(EscapeCSVString(key));
|
||||
sw.Write(CSV_SEPARATOR);
|
||||
sw.Write(EscapeCSVString(status));
|
||||
sw.Write(CSV_SEPARATOR);
|
||||
sw.Write(EscapeCSVString(originalText));
|
||||
sw.Write(CSV_SEPARATOR);
|
||||
sw.Write(EscapeCSVString(translatedText));
|
||||
|
||||
if (extraFields != null && extraFields.Length > 0)
|
||||
foreach (string s in extraFields)
|
||||
{
|
||||
sw.Write(CSV_SEPARATOR);
|
||||
sw.Write(EscapeCSVString(s));
|
||||
}
|
||||
|
||||
sw.Write("\r\n");
|
||||
}
|
||||
|
||||
private static string EscapeCSVString(string value)
|
||||
{
|
||||
//.Replace("\\", "\\\\").Replace("\r\n", "\\n").Replace("\r", "\\n").Replace("\n", "\\n").Replace("\t", "\\t")
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
private static void Report(string cultures)
|
||||
{
|
||||
foreach (string culture in GetLocaleFolders(cultures))
|
||||
{
|
||||
Dictionary<string, string> ignores = new Dictionary<string, string>();
|
||||
string ignorefile = System.IO.Path.Combine(Application.StartupPath, "ignore." + culture + ".xml");
|
||||
if (System.IO.File.Exists(ignorefile))
|
||||
ignores = XDocument.Load(ignorefile).Element("root").Elements("ignore").Select(c => c.Value).ToSafeDictionary(c => c.ToLower().Trim(), ignorefile);
|
||||
|
||||
//Make sure we ignore empty strings
|
||||
ignores[""] = "";
|
||||
|
||||
//Ignore common leftover values
|
||||
for (int i = 1; i < 20; i++)
|
||||
{
|
||||
string _i = i.ToString();
|
||||
ignores["textbox" + _i] = "";
|
||||
ignores["combobox" + _i] = "";
|
||||
ignores["checkbox" + _i] = "";
|
||||
ignores["label" + _i] = "";
|
||||
ignores["radiobutton" + _i] = "";
|
||||
ignores["button" + _i] = "";
|
||||
ignores["toolstrip" + _i] = "";
|
||||
ignores["toolstripbutton" + _i] = "";
|
||||
ignores["statusstrip" + _i] = "";
|
||||
}
|
||||
|
||||
|
||||
XDocument report = new XDocument(
|
||||
new XElement("root")
|
||||
);
|
||||
XElement reportRoot = report.Element("root");
|
||||
|
||||
IEnumerable<ResXFileInfo> files = GetResXList(culture);
|
||||
var missingFiles = files.Where(c => !System.IO.File.Exists(c.TargetFile));
|
||||
var existingFiles = files.Where(c => System.IO.File.Exists(c.TargetFile));
|
||||
|
||||
var extraFiles = from x in Duplicati.Library.Utility.Utility.EnumerateFiles(System.IO.Path.Combine(Application.StartupPath, culture))
|
||||
where
|
||||
x.EndsWith("." + culture + ".resx")
|
||||
&&
|
||||
!files.Select(c => c.TargetFile).Contains(x)
|
||||
select x;
|
||||
|
||||
reportRoot.Add(
|
||||
new XElement("files",
|
||||
new XElement("missing",
|
||||
from x in missingFiles
|
||||
select new XElement("file", x.TargetFile)
|
||||
),
|
||||
new XElement("unused",
|
||||
from x in extraFiles
|
||||
select new XElement("file", x)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
foreach(ResXFileInfo inf in existingFiles)
|
||||
{
|
||||
IEnumerable<XElement> sourceElements = XDocument.Load(inf.SourceFile).Element("root").Elements("data");
|
||||
IEnumerable<XElement> targetElements = XDocument.Load(inf.TargetFile).Element("root").Elements("data");
|
||||
|
||||
if (inf.IsForm)
|
||||
{
|
||||
//Look only for strings
|
||||
Func<XElement, bool> filter =
|
||||
c =>
|
||||
!c.Attribute("name").Value.StartsWith(">>")
|
||||
&&
|
||||
c.Attribute("mimetype") == null
|
||||
&&
|
||||
(
|
||||
c.Attribute("type") == null
|
||||
||
|
||||
c.Attribute("type").Value == "System.String, mscorlib"
|
||||
||
|
||||
c.Attribute("type").Value == "System.String"
|
||||
);
|
||||
|
||||
sourceElements = sourceElements.Where(filter);
|
||||
targetElements = targetElements.Where(filter);
|
||||
}
|
||||
|
||||
//Filter the source and target before proceeding
|
||||
sourceElements = sourceElements.Where(c => !ignores.ContainsKey(c.Element("value").Value.Trim().ToLower()) && !c.Element("value").Value.Trim().ToLower().StartsWith("..\\resources\\") && !c.Element("value").Value.Trim().ToLower().StartsWith("../resources/"));
|
||||
targetElements = targetElements.Where(c => !ignores.ContainsKey(c.Element("value").Value.Trim().ToLower()) && !c.Element("value").Value.Trim().ToLower().StartsWith("..\\resources\\") && !c.Element("value").Value.Trim().ToLower().StartsWith("../resources/"));
|
||||
|
||||
var sourceVals = sourceElements.ToSafeDictionary(c => c.Attribute("name").Value, inf.SourceFile);
|
||||
var targetVals = targetElements.ToSafeDictionary(c => c.Attribute("name").Value, inf.TargetFile);
|
||||
|
||||
var missing = sourceVals.Where(c => !targetVals.ContainsKey(c.Key)).ToList();
|
||||
var unused = targetVals.Where(c => !sourceVals.ContainsKey(c.Key)).ToList();
|
||||
var notUpdated = sourceVals.Where(c =>
|
||||
targetVals.ContainsKey(c.Key)
|
||||
&&
|
||||
targetVals[c.Key].Element("value").Value == c.Value.Element("value").Value).ToList();
|
||||
|
||||
var updated = sourceVals.Where(c =>
|
||||
!missing.Contains(c)
|
||||
&&
|
||||
!unused.Contains(c)
|
||||
&&
|
||||
!notUpdated.Contains(c)
|
||||
&&
|
||||
targetVals.ContainsKey(c.Key)
|
||||
);
|
||||
|
||||
var yy = sourceVals.Values.Where(x => x.Value.Contains("System.Draw")).Concat(targetVals.Values.Where(x => x.Value.Contains("System.Draw")));
|
||||
if (yy.Count() != 0)
|
||||
{
|
||||
}
|
||||
|
||||
reportRoot.Add(
|
||||
new XElement("file",
|
||||
new XAttribute("filename", inf.TargetFile),
|
||||
new XElement("missing",
|
||||
from x in missing
|
||||
select new XElement("item",
|
||||
new XAttribute("name", x.Key),
|
||||
x.Value.Element("value").Value
|
||||
)
|
||||
),
|
||||
new XElement("unused",
|
||||
from x in unused
|
||||
select new XElement("item",
|
||||
new XAttribute("name", x.Key),
|
||||
x.Value.Element("value").Value
|
||||
)
|
||||
),
|
||||
new XElement("not-updated",
|
||||
from x in notUpdated
|
||||
select new XElement("item",
|
||||
new XAttribute("name", x.Key),
|
||||
x.Value.Element("value").Value
|
||||
)
|
||||
),
|
||||
new XElement("updated",
|
||||
from x in updated
|
||||
select new XElement("item",
|
||||
new XAttribute("name", x.Key),
|
||||
new XElement("original",
|
||||
x.Value.Element("value").Value
|
||||
),
|
||||
new XElement("translated",
|
||||
targetVals[x.Key].Element("value").Value
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
report.Save(System.IO.Path.Combine(Application.StartupPath, "report." + culture + ".xml"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void Create(string culture)
|
||||
{
|
||||
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.GetCultureInfo(culture); //Validate
|
||||
string folder = System.IO.Path.Combine(Application.StartupPath, culture);
|
||||
if (!System.IO.Directory.Exists(folder))
|
||||
System.IO.Directory.CreateDirectory(folder);
|
||||
Update(culture);
|
||||
}
|
||||
|
||||
public class ResXFileInfo
|
||||
{
|
||||
public string SourceFile { get; set; }
|
||||
public string TargetFile { get; set; }
|
||||
public string NetrualTargetFile { get; set; }
|
||||
public bool IsForm { get; set; }
|
||||
}
|
||||
|
||||
public static IEnumerable<ResXFileInfo> GetResXList(string culture)
|
||||
{
|
||||
List<ResXFileInfo> res = new List<ResXFileInfo>();
|
||||
foreach (XElement conf in XDocument.Load(System.IO.Path.Combine(Application.StartupPath, "configuration.xml")).Element("root").Elements("configuration"))
|
||||
{
|
||||
string sourcefolder = conf.Element("sourcefolder").Value;
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
sourcefolder = sourcefolder.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
|
||||
string outputfolder = System.IO.Path.GetFullPath(Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.Combine(Application.StartupPath, culture)));
|
||||
sourcefolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.GetFullPath(sourcefolder));
|
||||
|
||||
foreach (XElement fn in conf.Elements("assembly"))
|
||||
{
|
||||
string subfolder = fn.Attribute("folder").Value;
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
subfolder = subfolder.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
|
||||
foreach (string s in Duplicati.Library.Utility.Utility.EnumerateFiles(System.IO.Path.Combine(sourcefolder, subfolder)))
|
||||
{
|
||||
if (s.ToLower().StartsWith(Application.StartupPath.ToLower()))
|
||||
continue;
|
||||
|
||||
if (s.EndsWith(".resx"))
|
||||
{
|
||||
string targetNameNeutral = System.IO.Path.Combine(outputfolder, s.Substring(sourcefolder.Length));
|
||||
|
||||
if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(targetNameNeutral)))
|
||||
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(targetNameNeutral));
|
||||
|
||||
string targetFilename = targetNameNeutral.Substring(0, targetNameNeutral.Length - "resx".Length) + culture + ".resx";
|
||||
if (res.Where(x => x.TargetFile == targetFilename).FirstOrDefault() == null)
|
||||
res.Add(new ResXFileInfo()
|
||||
{
|
||||
SourceFile = s,
|
||||
IsForm = System.IO.File.Exists(System.IO.Path.ChangeExtension(s, ".cs")),
|
||||
NetrualTargetFile = targetNameNeutral,
|
||||
TargetFile = targetFilename
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void Update(string cultures)
|
||||
{
|
||||
foreach (string culture in GetLocaleFolders(cultures))
|
||||
{
|
||||
foreach (ResXFileInfo inf in GetResXList(culture))
|
||||
{
|
||||
if (inf.IsForm)
|
||||
System.IO.File.Copy(inf.SourceFile, inf.NetrualTargetFile, true); //Copy the updated resx
|
||||
|
||||
if (System.IO.File.Exists(inf.TargetFile))
|
||||
{
|
||||
//Merge, forms are auto-merged, in that they depend on the neutral .resx file
|
||||
if (!inf.IsForm)
|
||||
{
|
||||
XDocument targetDoc = XDocument.Load(inf.TargetFile);
|
||||
XNode insertTarget = targetDoc.Element("root").LastNode;
|
||||
|
||||
var sourceVals = XDocument.Load(inf.SourceFile).Element("root").Elements("data").ToSafeDictionary(c => c.Attribute("name").Value, inf.SourceFile);
|
||||
var targetVals = targetDoc.Element("root").Elements("data").ToSafeDictionary(c => c.Attribute("name").Value, inf.TargetFile);
|
||||
|
||||
bool updated = false;
|
||||
foreach (var item in sourceVals)
|
||||
if (!targetVals.ContainsKey(item.Key))
|
||||
{
|
||||
updated = true;
|
||||
insertTarget.AddAfterSelf(new XElement(item.Value));
|
||||
}
|
||||
|
||||
if (updated)
|
||||
targetDoc.Save(inf.TargetFile);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inf.IsForm)
|
||||
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(inf.TargetFile))
|
||||
sw.Write(Properties.Resources.Empty_resx);
|
||||
else
|
||||
{
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
{
|
||||
var doc = XDocument.Load(inf.SourceFile);
|
||||
var entries = from x in doc.Element("root").Elements("data")
|
||||
where
|
||||
x.Attribute("type") != null
|
||||
&&
|
||||
x.Attribute("type").Value == "System.Resources.ResXFileRef, System.Windows.Forms"
|
||||
select x;
|
||||
|
||||
foreach(var e in entries)
|
||||
{
|
||||
string name = e.Element("value").Value;
|
||||
int ixpos = name.IndexOf(";");
|
||||
string path = name.Substring(0, ixpos);
|
||||
path = path.Replace ("\\", System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
e.Element("value").Value = path + name.Substring(ixpos);
|
||||
}
|
||||
|
||||
doc.Save(inf.TargetFile);
|
||||
|
||||
} else {
|
||||
System.IO.File.Copy(inf.SourceFile, inf.TargetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var item in from y in
|
||||
(from x in XDocument.Load(inf.SourceFile).Element("root").Elements("data")
|
||||
where
|
||||
x.Attribute("type") != null
|
||||
&&
|
||||
x.Attribute("type").Value == "System.Resources.ResXFileRef, System.Windows.Forms"
|
||||
select x)
|
||||
let relname = y.Element("value").Value.Substring(0, y.Element("value").Value.IndexOf(";"))
|
||||
let sourceRes = System.IO.Path.GetFullPath(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(inf.SourceFile), relname)).Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString())
|
||||
let targetRes = System.IO.Path.GetFullPath(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(inf.TargetFile), relname)).Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString())
|
||||
|
||||
select new { sourceRes, targetRes }
|
||||
)
|
||||
{
|
||||
if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(item.targetRes)))
|
||||
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(item.targetRes));
|
||||
|
||||
if (!System.IO.File.Exists(item.targetRes))
|
||||
{
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux && !System.IO.File.Exists(item.sourceRes))
|
||||
{
|
||||
string basefolder = System.IO.Path.GetDirectoryName(item.sourceRes);
|
||||
string sourcefile = System.IO.Directory.GetFiles (basefolder).FirstOrDefault (x => item.sourceRes.Equals(x, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (sourcefile == null)
|
||||
Console.WriteLine ("Source file not found: " + item.sourceRes);
|
||||
else
|
||||
System.IO.File.Copy (sourcefile, item.targetRes);
|
||||
|
||||
} else {
|
||||
System.IO.File.Copy(item.sourceRes, item.targetRes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string batchfilename = System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, culture), "gui update.bat");
|
||||
System.IO.File.WriteAllText(batchfilename, Properties.Resources.Batchjob_bat, System.Text.Encoding.Default);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void Clean(string cultureReq)
|
||||
{
|
||||
string root = Duplicati.Library.Utility.Utility.AppendDirSeparator(Application.StartupPath);
|
||||
if (!string.IsNullOrEmpty(cultureReq))
|
||||
root = System.IO.Path.Combine(root, cultureReq);
|
||||
|
||||
foreach (string s in Duplicati.Library.Utility.Utility.EnumerateFiles(Application.StartupPath))
|
||||
if (System.IO.Path.GetDirectoryName(s) != Application.StartupPath && (System.IO.Path.GetExtension(s) == ".resources" || System.IO.Path.GetExtension(s) == ".dll"))
|
||||
System.IO.File.Delete(s);
|
||||
else if (System.IO.Path.GetExtension(s) == ".resx")
|
||||
{
|
||||
//Remove the extra files required for resgen.exe
|
||||
string culture = s.Substring(root.Length);
|
||||
culture = culture.Substring(0, culture.IndexOf(System.IO.Path.DirectorySeparatorChar));
|
||||
if (!s.ToLower().EndsWith("." + culture.ToLower() + ".resx") && System.IO.File.Exists(s.Substring(0, s.Length - "resx".Length) + culture + ".resx"))
|
||||
System.IO.File.Delete(s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void Compile(string cultureReq)
|
||||
{
|
||||
XDocument doc = XDocument.Load(System.IO.Path.Combine(Application.StartupPath, "configuration.xml"));
|
||||
|
||||
foreach (XElement conf in doc.Element("root").Elements("configuration"))
|
||||
{
|
||||
string keyfile = conf.Element("keyfile") == null ? null : conf.Element("keyfile").Value;
|
||||
string versionassembly = conf.Element("versionassembly").Value;
|
||||
string outputfolder = conf.Element("outputfolder").Value;
|
||||
string productname = conf.Element("productname").Value;
|
||||
string fxVer = conf.Element("frameworkVersion") == null ? "4.0" : conf.Element("frameworkVersion").Value;
|
||||
|
||||
Console.WriteLine("Compiling for framework: " + fxVer);
|
||||
|
||||
foreach (XElement n in conf.Elements("assembly"))
|
||||
{
|
||||
string assemblyName = n.Attribute("name").Value;
|
||||
string folder = n.Attribute("folder").Value;
|
||||
string @namespace = n.Attribute("namespace") == null ? assemblyName : n.Attribute("namespace").Value;
|
||||
|
||||
foreach (string culture in GetLocaleFolders(cultureReq))
|
||||
{
|
||||
List<string> excludes = (from x in n.Elements("exclude")
|
||||
let fullpath = System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, culture), x.Value)
|
||||
select fullpath).ToList();
|
||||
|
||||
string outfolder = System.IO.Path.GetFullPath(System.IO.Path.Combine(outputfolder, culture));
|
||||
if (!System.IO.Directory.Exists(outfolder))
|
||||
System.IO.Directory.CreateDirectory(outfolder);
|
||||
|
||||
ResXCompiler.CompileResxFiles(System.IO.Path.Combine(System.IO.Path.Combine(Application.StartupPath, culture), folder), excludes, @namespace, System.IO.Path.Combine(outfolder, assemblyName + ".resources.dll"), System.IO.Path.GetFullPath(versionassembly), keyfile, culture, productname, fxVer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("LocalizationTool")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Duplicati Team")]
|
||||
[assembly: AssemblyProduct("LocalizationTool")]
|
||||
[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("6f59216b-26e5-48cc-907b-16a97c7625df")]
|
||||
|
||||
// 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")]
|
||||
@@ -1,105 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.544
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace LocalizationTool.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LocalizationTool.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to @echo off
|
||||
///
|
||||
///echo Processing updates, please wait ...
|
||||
///
|
||||
///set FOLDERNAME=
|
||||
///for /f "tokens=*" %%a in ("%CD%") do set FOLDERNAME=%%~na
|
||||
///
|
||||
///..\LocalizationTool.exe update %FOLDERNAME%
|
||||
///..\LocalizationTool.exe guiupdate %FOLDERNAME%.
|
||||
/// </summary>
|
||||
internal static string Batchjob_bat {
|
||||
get {
|
||||
return ResourceManager.GetString("Batchjob.bat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to <?xml version="1.0" encoding="utf-8"?>
|
||||
///<root>
|
||||
/// <!--
|
||||
/// Microsoft ResX Schema
|
||||
///
|
||||
/// Version 2.0
|
||||
///
|
||||
/// The primary goals of this format is to allow a simple XML format
|
||||
/// that is mostly human readable. The generation and parsing of the
|
||||
/// various data types are done through the TypeConverter classes
|
||||
/// associated with the data types.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ... ado.net/XML headers & schema ...
|
||||
/// <resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
/// <resheader n [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string Empty_resx {
|
||||
get {
|
||||
return ResourceManager.GetString("Empty_resx", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="Batchjob.bat" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Batchjob.bat.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
|
||||
</data>
|
||||
<data name="Empty_resx" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Empty_resx.txt;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,26 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.544
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace LocalizationTool.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles />
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
@@ -1,230 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Configuration;
|
||||
|
||||
namespace LocalizationTool
|
||||
{
|
||||
public static class ResXCompiler
|
||||
{
|
||||
private static string ExecuteAndRead(string command, string arguments)
|
||||
{
|
||||
var psi = new System.Diagnostics.ProcessStartInfo(command, arguments);
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.UseShellExecute = false;
|
||||
|
||||
var pi = System.Diagnostics.Process.Start(psi);
|
||||
pi.WaitForExit(5000);
|
||||
if (pi.HasExited)
|
||||
return pi.StandardOutput.ReadToEnd().Trim();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void CompileResxFiles(string folder, List<string> excludeFolders, string @namespace, string assemblyname, string versionAssembly, string keyfile, string culture, string productname, string fxVer)
|
||||
{
|
||||
string resgenexe;
|
||||
string alexe;
|
||||
if (Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
{
|
||||
resgenexe = ExecuteAndRead("which", "resgen");
|
||||
alexe = ExecuteAndRead("which", "al");
|
||||
}
|
||||
else //Windows
|
||||
{
|
||||
//Order of these paths is also the search order
|
||||
string[] known_sdk_paths = null;
|
||||
if (fxVer == "2.0")
|
||||
{
|
||||
known_sdk_paths = new string[]
|
||||
{
|
||||
Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v2.0.50727\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft.NET\\SDK\\v2.0\\bin\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft.NET\\SDK\\v2.0\\bin\\"),
|
||||
};
|
||||
}
|
||||
else if (fxVer == "3.0")
|
||||
{
|
||||
known_sdk_paths = new string[]
|
||||
{
|
||||
Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v3.0\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v6.0A\\bin\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v6.0A\\bin\\"),
|
||||
|
||||
};
|
||||
}
|
||||
else if (fxVer == "3.5")
|
||||
{
|
||||
known_sdk_paths = new string[]
|
||||
{
|
||||
Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v3.5\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v7.0A\\bin\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v7.0A\\bin\\"),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
known_sdk_paths = new string[]
|
||||
{
|
||||
Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v4.0\\"),
|
||||
Environment.ExpandEnvironmentVariables("%WINDIR%\\Microsoft.Net\\Framework\\v4.0.30319\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\NETFX 4.0 Tools\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES%\\Microsoft SDKs\\Windows\\v7.1\\Bin\\NETFX 4.0 Tools\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v8.0A\\bin\\NETFX 4.0 Tools\\"),
|
||||
Environment.ExpandEnvironmentVariables("%PROGRAMFILES(X86)%\\Microsoft SDKs\\Windows\\v7.1\\Bin\\NETFX 4.0 Tools\\"),
|
||||
};
|
||||
}
|
||||
resgenexe = "resgen.exe";
|
||||
alexe = "al.exe";
|
||||
|
||||
foreach(var p in known_sdk_paths)
|
||||
if (System.IO.File.Exists(System.IO.Path.Combine(p, resgenexe)))
|
||||
{
|
||||
resgenexe = System.IO.Path.Combine(p, resgenexe);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var p in known_sdk_paths)
|
||||
if (System.IO.File.Exists(System.IO.Path.Combine(p, alexe)))
|
||||
{
|
||||
alexe = System.IO.Path.Combine(p, alexe);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (!System.IO.File.Exists(resgenexe))
|
||||
{
|
||||
Console.WriteLine("Unable to locate file: {0}", resgenexe);
|
||||
Console.WriteLine("This can be fixed by installing a microsoft platform SDK, or visual studio (express is fine)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!System.IO.File.Exists(alexe))
|
||||
{
|
||||
Console.WriteLine("Unable to locate file: {0}", alexe);
|
||||
Console.WriteLine("This can be fixed by installing the .Net framework version 2.0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(keyfile) && Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
keyfile = keyfile.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
|
||||
|
||||
List<string> resources = new List<string>();
|
||||
|
||||
folder = Duplicati.Library.Utility.Utility.AppendDirSeparator(folder);
|
||||
|
||||
foreach (string s in Duplicati.Library.Utility.Utility.EnumerateFiles(folder))
|
||||
{
|
||||
if (s.ToLower().EndsWith("." + culture.ToLower() + ".resx"))
|
||||
{
|
||||
if (excludeFolders.Any(xf => s.ToLower().StartsWith(Duplicati.Library.Utility.Utility.AppendDirSeparator(xf).ToLower())))
|
||||
continue;
|
||||
|
||||
string resname = System.IO.Path.ChangeExtension(s, ".resources");
|
||||
|
||||
if (!System.IO.File.Exists(resname) || System.IO.File.GetLastWriteTime(resname) < System.IO.File.GetLastWriteTime(s))
|
||||
{
|
||||
Console.WriteLine("Compiling: " + s);
|
||||
System.Diagnostics.ProcessStartInfo pi = new System.Diagnostics.ProcessStartInfo(resgenexe, "\"" + s + "\"");
|
||||
pi.CreateNoWindow = true;
|
||||
pi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
|
||||
pi.RedirectStandardOutput = true;
|
||||
pi.RedirectStandardError = true;
|
||||
pi.UseShellExecute = false;
|
||||
pi.WorkingDirectory = System.IO.Path.GetDirectoryName(s);
|
||||
|
||||
System.Diagnostics.Process pr = System.Diagnostics.Process.Start(pi);
|
||||
pr.WaitForExit();
|
||||
|
||||
if (pr.ExitCode != 0)
|
||||
{
|
||||
Console.WriteLine("Error");
|
||||
Console.WriteLine(pr.StandardOutput.ReadToEnd());
|
||||
Console.WriteLine(pr.StandardError.ReadToEnd());
|
||||
throw new Exception("Resgen failure: " + s);
|
||||
}
|
||||
}
|
||||
else
|
||||
Console.WriteLine("Not modified: " + s);
|
||||
|
||||
resources.Add(resname);
|
||||
}
|
||||
}
|
||||
|
||||
if (resources.Count == 0)
|
||||
return;
|
||||
|
||||
if (!System.IO.File.Exists(versionAssembly) && Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
versionAssembly = versionAssembly.Replace("\\", System.IO.Path.DirectorySeparatorChar.ToString());
|
||||
|
||||
if (!System.IO.File.Exists(versionAssembly))
|
||||
{
|
||||
Console.WriteLine("Unable to locate file: {0}", versionAssembly);
|
||||
Console.WriteLine("This can be fixed by compiling the application or modifying the file configuration.xml");
|
||||
return;
|
||||
}
|
||||
|
||||
using (Duplicati.Library.Utility.TempFile tf = new Duplicati.Library.Utility.TempFile())
|
||||
{
|
||||
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(tf))
|
||||
{
|
||||
System.Reflection.Assembly asm = System.Reflection.Assembly.ReflectionOnlyLoadFrom(versionAssembly);
|
||||
|
||||
sw.WriteLine("/t:lib");
|
||||
sw.WriteLine("/out:\"" + assemblyname + "\"");
|
||||
sw.WriteLine("/product:\"" + productname + "\"");
|
||||
sw.WriteLine("/title:\"" + productname + "\"");
|
||||
sw.WriteLine("/version:" + asm.GetName().Version.ToString());
|
||||
if (!string.IsNullOrEmpty(keyfile))
|
||||
sw.WriteLine("/keyfile:\"" + keyfile + "\"");
|
||||
sw.WriteLine("/culture:" + culture);
|
||||
|
||||
foreach (string s in resources)
|
||||
{
|
||||
string resname = s.Substring(folder.Length);
|
||||
resname = resname.Replace("\\", ".");
|
||||
resname = resname.Replace(" ", "_");
|
||||
resname = @namespace + "." + resname;
|
||||
|
||||
sw.WriteLine("/embed:\"" + s + "\"," + resname);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Linking ...");
|
||||
System.Diagnostics.ProcessStartInfo pi = new System.Diagnostics.ProcessStartInfo(alexe, "@\"" + tf + "\"");
|
||||
pi.CreateNoWindow = true;
|
||||
pi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
|
||||
|
||||
System.Diagnostics.Process pr = System.Diagnostics.Process.Start(pi);
|
||||
pr.WaitForExit();
|
||||
|
||||
if (pr.ExitCode != 0)
|
||||
throw new Exception("Linker failure");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo Processing updates, please wait ...
|
||||
|
||||
set FOLDERNAME=
|
||||
for /f "tokens=*" %%a in ("%CD%") do set FOLDERNAME=%%~na
|
||||
|
||||
..\LocalizationTool.exe update %FOLDERNAME%
|
||||
..\LocalizationTool.exe guiupdate %FOLDERNAME%
|
||||
@@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,311 +0,0 @@
|
||||
namespace LocalizationTool
|
||||
{
|
||||
partial class UpdateGUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
|
||||
this.treeView1 = new System.Windows.Forms.TreeView();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.textBox1 = new System.Windows.Forms.TextBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.textBox2 = new System.Windows.Forms.TextBox();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
this.label5 = new System.Windows.Forms.Label();
|
||||
this.label6 = new System.Windows.Forms.Label();
|
||||
this.panel3 = new System.Windows.Forms.Panel();
|
||||
this.label7 = new System.Windows.Forms.Label();
|
||||
this.panel4 = new System.Windows.Forms.Panel();
|
||||
this.button1 = new System.Windows.Forms.Button();
|
||||
this.button2 = new System.Windows.Forms.Button();
|
||||
this.button3 = new System.Windows.Forms.Button();
|
||||
this.label8 = new System.Windows.Forms.Label();
|
||||
this.panel1.SuspendLayout();
|
||||
this.panel2.SuspendLayout();
|
||||
this.splitContainer1.Panel1.SuspendLayout();
|
||||
this.splitContainer1.Panel2.SuspendLayout();
|
||||
this.splitContainer1.SuspendLayout();
|
||||
this.panel3.SuspendLayout();
|
||||
this.panel4.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.Controls.Add(this.button3);
|
||||
this.panel1.Controls.Add(this.button2);
|
||||
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.panel1.Location = new System.Drawing.Point(0, 264);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(459, 45);
|
||||
this.panel1.TabIndex = 0;
|
||||
//
|
||||
// panel2
|
||||
//
|
||||
this.panel2.Controls.Add(this.label6);
|
||||
this.panel2.Controls.Add(this.label5);
|
||||
this.panel2.Controls.Add(this.label4);
|
||||
this.panel2.Controls.Add(this.label3);
|
||||
this.panel2.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
this.panel2.Location = new System.Drawing.Point(0, 0);
|
||||
this.panel2.Name = "panel2";
|
||||
this.panel2.Size = new System.Drawing.Size(459, 32);
|
||||
this.panel2.TabIndex = 1;
|
||||
//
|
||||
// splitContainer1
|
||||
//
|
||||
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.splitContainer1.Location = new System.Drawing.Point(0, 32);
|
||||
this.splitContainer1.Name = "splitContainer1";
|
||||
//
|
||||
// splitContainer1.Panel1
|
||||
//
|
||||
this.splitContainer1.Panel1.Controls.Add(this.treeView1);
|
||||
this.splitContainer1.Panel1.Controls.Add(this.panel4);
|
||||
//
|
||||
// splitContainer1.Panel2
|
||||
//
|
||||
this.splitContainer1.Panel2.Controls.Add(this.panel3);
|
||||
this.splitContainer1.Panel2.Controls.Add(this.label7);
|
||||
this.splitContainer1.Size = new System.Drawing.Size(459, 232);
|
||||
this.splitContainer1.SplitterDistance = 227;
|
||||
this.splitContainer1.TabIndex = 2;
|
||||
//
|
||||
// treeView1
|
||||
//
|
||||
this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.treeView1.HideSelection = false;
|
||||
this.treeView1.Location = new System.Drawing.Point(0, 0);
|
||||
this.treeView1.Name = "treeView1";
|
||||
this.treeView1.Size = new System.Drawing.Size(227, 200);
|
||||
this.treeView1.TabIndex = 0;
|
||||
this.treeView1.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView1_AfterSelect);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(16, 8);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(25, 13);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = "Key";
|
||||
//
|
||||
// textBox1
|
||||
//
|
||||
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.textBox1.Location = new System.Drawing.Point(56, 8);
|
||||
this.textBox1.Name = "textBox1";
|
||||
this.textBox1.ReadOnly = true;
|
||||
this.textBox1.Size = new System.Drawing.Size(42, 20);
|
||||
this.textBox1.TabIndex = 1;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Location = new System.Drawing.Point(16, 40);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(34, 13);
|
||||
this.label2.TabIndex = 2;
|
||||
this.label2.Text = "Value";
|
||||
//
|
||||
// textBox2
|
||||
//
|
||||
this.textBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.textBox2.Location = new System.Drawing.Point(16, 56);
|
||||
this.textBox2.Multiline = true;
|
||||
this.textBox2.Name = "textBox2";
|
||||
this.textBox2.Size = new System.Drawing.Size(82, 136);
|
||||
this.textBox2.TabIndex = 3;
|
||||
this.textBox2.TextChanged += new System.EventHandler(this.textBox2_TextChanged);
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(8, 8);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(83, 13);
|
||||
this.label3.TabIndex = 0;
|
||||
this.label3.Text = "Items remaining:";
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Location = new System.Drawing.Point(96, 8);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(37, 13);
|
||||
this.label4.TabIndex = 1;
|
||||
this.label4.Text = "12345";
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.AutoSize = true;
|
||||
this.label5.Location = new System.Drawing.Point(152, 8);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Size = new System.Drawing.Size(36, 13);
|
||||
this.label5.TabIndex = 2;
|
||||
this.label5.Text = "Folder";
|
||||
//
|
||||
// label6
|
||||
//
|
||||
this.label6.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.label6.AutoEllipsis = true;
|
||||
this.label6.Location = new System.Drawing.Point(200, 8);
|
||||
this.label6.Name = "label6";
|
||||
this.label6.Size = new System.Drawing.Size(256, 16);
|
||||
this.label6.TabIndex = 3;
|
||||
this.label6.Text = "label6";
|
||||
//
|
||||
// panel3
|
||||
//
|
||||
this.panel3.Controls.Add(this.textBox2);
|
||||
this.panel3.Controls.Add(this.label2);
|
||||
this.panel3.Controls.Add(this.label1);
|
||||
this.panel3.Controls.Add(this.textBox1);
|
||||
this.panel3.Location = new System.Drawing.Point(8, 0);
|
||||
this.panel3.Name = "panel3";
|
||||
this.panel3.Size = new System.Drawing.Size(104, 208);
|
||||
this.panel3.TabIndex = 4;
|
||||
this.panel3.Visible = false;
|
||||
//
|
||||
// label7
|
||||
//
|
||||
this.label7.Anchor = System.Windows.Forms.AnchorStyles.None;
|
||||
this.label7.Location = new System.Drawing.Point(67, 92);
|
||||
this.label7.Name = "label7";
|
||||
this.label7.Size = new System.Drawing.Size(100, 16);
|
||||
this.label7.TabIndex = 5;
|
||||
this.label7.Text = "Click on an entry";
|
||||
this.label7.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// panel4
|
||||
//
|
||||
this.panel4.Controls.Add(this.label8);
|
||||
this.panel4.Controls.Add(this.button1);
|
||||
this.panel4.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.panel4.Location = new System.Drawing.Point(0, 200);
|
||||
this.panel4.Name = "panel4";
|
||||
this.panel4.Size = new System.Drawing.Size(227, 32);
|
||||
this.panel4.TabIndex = 6;
|
||||
//
|
||||
// button1
|
||||
//
|
||||
this.button1.Location = new System.Drawing.Point(5, 8);
|
||||
this.button1.Name = "button1";
|
||||
this.button1.Size = new System.Drawing.Size(75, 23);
|
||||
this.button1.TabIndex = 6;
|
||||
this.button1.Text = "&Next";
|
||||
this.button1.UseVisualStyleBackColor = true;
|
||||
this.button1.Click += new System.EventHandler(this.button1_Click);
|
||||
//
|
||||
// button2
|
||||
//
|
||||
this.button2.Anchor = System.Windows.Forms.AnchorStyles.Top;
|
||||
this.button2.Location = new System.Drawing.Point(144, 8);
|
||||
this.button2.Name = "button2";
|
||||
this.button2.Size = new System.Drawing.Size(75, 23);
|
||||
this.button2.TabIndex = 0;
|
||||
this.button2.Text = "&OK";
|
||||
this.button2.UseVisualStyleBackColor = true;
|
||||
this.button2.Click += new System.EventHandler(this.button2_Click);
|
||||
//
|
||||
// button3
|
||||
//
|
||||
this.button3.Anchor = System.Windows.Forms.AnchorStyles.Top;
|
||||
this.button3.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.button3.Location = new System.Drawing.Point(240, 8);
|
||||
this.button3.Name = "button3";
|
||||
this.button3.Size = new System.Drawing.Size(75, 23);
|
||||
this.button3.TabIndex = 1;
|
||||
this.button3.Text = "&Cancel";
|
||||
this.button3.UseVisualStyleBackColor = true;
|
||||
this.button3.Click += new System.EventHandler(this.button3_Click);
|
||||
//
|
||||
// label8
|
||||
//
|
||||
this.label8.AutoSize = true;
|
||||
this.label8.Location = new System.Drawing.Point(96, 16);
|
||||
this.label8.Name = "label8";
|
||||
this.label8.Size = new System.Drawing.Size(134, 13);
|
||||
this.label8.TabIndex = 2;
|
||||
this.label8.Text = "Press ALT+N for next entry";
|
||||
//
|
||||
// UpdateGUI
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.button3;
|
||||
this.ClientSize = new System.Drawing.Size(459, 309);
|
||||
this.Controls.Add(this.splitContainer1);
|
||||
this.Controls.Add(this.panel2);
|
||||
this.Controls.Add(this.panel1);
|
||||
this.Name = "UpdateGUI";
|
||||
this.Text = "Localization updater";
|
||||
this.Load += new System.EventHandler(this.UpdateGUI_Load);
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.UpdateGUI_FormClosing);
|
||||
this.panel1.ResumeLayout(false);
|
||||
this.panel2.ResumeLayout(false);
|
||||
this.panel2.PerformLayout();
|
||||
this.splitContainer1.Panel1.ResumeLayout(false);
|
||||
this.splitContainer1.Panel2.ResumeLayout(false);
|
||||
this.splitContainer1.ResumeLayout(false);
|
||||
this.panel3.ResumeLayout(false);
|
||||
this.panel3.PerformLayout();
|
||||
this.panel4.ResumeLayout(false);
|
||||
this.panel4.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Panel panel2;
|
||||
private System.Windows.Forms.Label label3;
|
||||
private System.Windows.Forms.SplitContainer splitContainer1;
|
||||
private System.Windows.Forms.TreeView treeView1;
|
||||
private System.Windows.Forms.TextBox textBox2;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.TextBox textBox1;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Label label6;
|
||||
private System.Windows.Forms.Label label5;
|
||||
private System.Windows.Forms.Label label4;
|
||||
private System.Windows.Forms.Label label7;
|
||||
private System.Windows.Forms.Panel panel3;
|
||||
private System.Windows.Forms.Button button3;
|
||||
private System.Windows.Forms.Button button2;
|
||||
private System.Windows.Forms.Panel panel4;
|
||||
private System.Windows.Forms.Button button1;
|
||||
private System.Windows.Forms.Label label8;
|
||||
}
|
||||
}
|
||||
@@ -1,233 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace LocalizationTool
|
||||
{
|
||||
public partial class UpdateGUI : Form
|
||||
{
|
||||
private string m_reportfile;
|
||||
private int m_totalcount;
|
||||
private Dictionary<string, XElement> m_updates = new Dictionary<string, XElement>();
|
||||
|
||||
public UpdateGUI(string reportfile)
|
||||
{
|
||||
InitializeComponent();
|
||||
m_reportfile = reportfile;
|
||||
}
|
||||
|
||||
private void UpdateGUI_Load(object sender, EventArgs e)
|
||||
{
|
||||
panel3.Dock = DockStyle.Fill;
|
||||
string culturename = System.IO.Path.GetExtension(System.IO.Path.GetFileNameWithoutExtension(m_reportfile)).Substring(1);
|
||||
this.Text = "Localizing culture " + culturename;
|
||||
|
||||
var files = XDocument.Load(m_reportfile).Element("root").Elements("file").Where(n =>
|
||||
(n.Element("missing") != null && n.Element("missing").Elements().Count() > 0)
|
||||
||
|
||||
(n.Element("not-updated") != null && n.Element("not-updated").Elements().Count() > 0)
|
||||
);
|
||||
|
||||
string rootfolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(System.IO.Path.Combine(Application.StartupPath, culturename));
|
||||
|
||||
treeView1.BeginUpdate();
|
||||
treeView1.Nodes.Clear();
|
||||
foreach (XElement x in files)
|
||||
{
|
||||
TreeNodeCollection cl = treeView1.Nodes;
|
||||
string relpath = x.Attribute("filename").Value.Substring(rootfolder.Length);
|
||||
foreach (string s in relpath.Split(System.IO.Path.DirectorySeparatorChar))
|
||||
{
|
||||
bool found = false;
|
||||
foreach (TreeNode n in cl)
|
||||
if (n.Text == s)
|
||||
{
|
||||
cl = n.Nodes;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
TreeNode nn = new TreeNode(s);
|
||||
nn.ForeColor = Color.DarkBlue;
|
||||
cl.Add(nn);
|
||||
cl = nn.Nodes;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement el in x.Element("missing").Elements("item").Union(x.Element("not-updated").Elements("item")))
|
||||
{
|
||||
//These show up occasionally, but mut not be translated
|
||||
if (
|
||||
el.Attribute("name").Value.EndsWith(".Name")
|
||||
||
|
||||
el.Attribute("name").Value.EndsWith(".MappingName")
|
||||
)
|
||||
continue;
|
||||
|
||||
string val = el.Value;
|
||||
if (val.Length > 100)
|
||||
val = val.Substring(0, 97) + "...";
|
||||
TreeNode tn = new TreeNode(el.Attribute("name").Value + ": " + val);
|
||||
tn.ForeColor = Color.DarkRed;
|
||||
tn.Tag = el;
|
||||
cl.Add(tn);
|
||||
m_totalcount++;
|
||||
}
|
||||
}
|
||||
treeView1.EndUpdate();
|
||||
treeView1.ExpandAll();
|
||||
if (treeView1.Nodes.Count > 0)
|
||||
{
|
||||
TreeNode top = treeView1.Nodes[0];
|
||||
while (top.Nodes.Count > 0)
|
||||
top = top.Nodes[0];
|
||||
treeView1.SelectedNode = top;
|
||||
}
|
||||
label4.Text = m_totalcount.ToString();
|
||||
label6.Text = System.IO.Path.Combine(Application.StartupPath, culturename);
|
||||
this.WindowState = FormWindowState.Maximized;
|
||||
}
|
||||
|
||||
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
|
||||
{
|
||||
if (treeView1.SelectedNode == null || treeView1.SelectedNode.Tag as XElement == null)
|
||||
panel3.Visible = false;
|
||||
else
|
||||
{
|
||||
panel3.Visible = true;
|
||||
XElement el = treeView1.SelectedNode.Tag as XElement;
|
||||
panel3.Tag = null;
|
||||
textBox1.Text = el.Attribute("name").Value;
|
||||
textBox2.Text = el.Value.Replace(Environment.NewLine, "\n").Replace("\n", Environment.NewLine);
|
||||
try
|
||||
{
|
||||
textBox2.Focus();
|
||||
textBox2.SelectAll();
|
||||
}
|
||||
catch { }
|
||||
panel3.Tag = el;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void textBox2_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (panel3.Tag as XElement == null)
|
||||
return;
|
||||
|
||||
XElement el = panel3.Tag as XElement;
|
||||
el.Value = textBox2.Text;
|
||||
string dictkey = el.Parent.Parent.Attribute("filename").Value + "@" + el.Attribute("name").Value;
|
||||
m_updates[dictkey] = el;
|
||||
label4.Text = (m_totalcount - m_updates.Count).ToString();
|
||||
|
||||
if (treeView1.SelectedNode != null && treeView1.SelectedNode.Tag == el)
|
||||
treeView1.SelectedNode.ForeColor = Color.DarkGreen;
|
||||
}
|
||||
|
||||
private void button3_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (m_updates.Count > 0)
|
||||
if (MessageBox.Show(this, "Close and loose all changes?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button3) != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
this.DialogResult = DialogResult.Cancel;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void button1_Click(object sender, EventArgs e)
|
||||
{
|
||||
TreeNode t = treeView1.SelectedNode;
|
||||
if (t == null)
|
||||
return;
|
||||
|
||||
List<TreeNode> visited = new List<TreeNode>();
|
||||
|
||||
while (t != null)
|
||||
{
|
||||
if (t.Nodes.Count > 0 && !visited.Contains(t))
|
||||
t = t.Nodes[0];
|
||||
else if (t.NextNode != null)
|
||||
t = t.NextNode;
|
||||
else
|
||||
{
|
||||
if (t.Parent != null && !visited.Contains(t.Parent))
|
||||
visited.Add(t.Parent);
|
||||
t = t.Parent;
|
||||
}
|
||||
|
||||
if (t != null && t.ForeColor == Color.DarkRed)
|
||||
{
|
||||
treeView1.SelectedNode = t;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox.Show(this, "No more items", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
private void button2_Click(object sender, EventArgs e)
|
||||
{
|
||||
foreach (XElement el in m_updates.Values)
|
||||
{
|
||||
XDocument file = XDocument.Load(el.Parent.Parent.Attribute("filename").Value);
|
||||
var insertEl = file.Element("root").Elements("data").Where(c => c.Attribute("name").Value == el.Attribute("name").Value).FirstOrDefault();
|
||||
if (insertEl != null)
|
||||
insertEl.Element("value").Value = el.Value;
|
||||
else
|
||||
file.Element("root").Elements().Last().AddAfterSelf(
|
||||
new XElement("data",
|
||||
new XAttribute("name", el.Attribute("name").Value),
|
||||
new XAttribute(XNamespace.Xml + "space", "preserve"),
|
||||
new XElement("value", el.Value)
|
||||
)
|
||||
);
|
||||
|
||||
file.Save(el.Parent.Parent.Attribute("filename").Value);
|
||||
}
|
||||
|
||||
m_updates.Clear();
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void UpdateGUI_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.FormOwnerClosing)
|
||||
if (m_updates.Count > 0)
|
||||
if (MessageBox.Show(this, "Close and loose all changes?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button3) != DialogResult.Yes)
|
||||
{
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
</configSections>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LocalizationTool2
|
||||
{
|
||||
public class LocalizationEntry
|
||||
{
|
||||
public LocalizationEntry()
|
||||
{
|
||||
SourceLocations = new List<string>();
|
||||
}
|
||||
|
||||
public LocalizationEntry(string text, string file, int position)
|
||||
: this()
|
||||
{
|
||||
SourceString = text;
|
||||
AddSource(file, position);
|
||||
}
|
||||
|
||||
internal void AddSource(string file, int linepos)
|
||||
{
|
||||
SourceLocations.Add(string.Format("{0}:{1}", file, linepos));
|
||||
}
|
||||
|
||||
public string SourceString { get; set; }
|
||||
public List<string> SourceLocations { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,46 +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>{6795DFBA-B494-4D6F-A79C-9118EF1CC109}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>LocalizationTool2</RootNamespace>
|
||||
<AssemblyName>LocalizationTool2</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
</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>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ExternalConsole>true</ExternalConsole>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="LocalizationEntry.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalizationTool2", "LocalizationTool2.csproj", "{6795DFBA-B494-4D6F-A79C-9118EF1CC109}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6795DFBA-B494-4D6F-A79C-9118EF1CC109}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{6795DFBA-B494-4D6F-A79C-9118EF1CC109}.Debug|x86.Build.0 = Debug|x86
|
||||
{6795DFBA-B494-4D6F-A79C-9118EF1CC109}.Release|x86.ActiveCfg = Release|x86
|
||||
{6795DFBA-B494-4D6F-A79C-9118EF1CC109}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
Policies = $0
|
||||
$0.TextStylePolicy = $1
|
||||
$1.inheritsSet = VisualStudio
|
||||
$1.scope = text/plain
|
||||
$1.FileWidth = 120
|
||||
$1.EolMarker = Windows
|
||||
$1.inheritsScope = text/plain
|
||||
$0.CSharpFormattingPolicy = $2
|
||||
$2.NewLinesForBracesInProperties = True
|
||||
$2.NewLinesForBracesInAccessors = True
|
||||
$2.NewLinesForBracesInAnonymousMethods = True
|
||||
$2.NewLinesForBracesInControlBlocks = True
|
||||
$2.NewLinesForBracesInAnonymousTypes = True
|
||||
$2.NewLinesForBracesInObjectCollectionArrayInitializers = True
|
||||
$2.NewLinesForBracesInLambdaExpressionBody = True
|
||||
$2.NewLineForElse = True
|
||||
$2.NewLineForCatch = True
|
||||
$2.NewLineForFinally = True
|
||||
$2.NewLineForMembersInObjectInit = True
|
||||
$2.NewLineForMembersInAnonymousTypes = True
|
||||
$2.NewLineForClausesInQuery = True
|
||||
$2.SpacingAfterMethodDeclarationName = False
|
||||
$2.SpaceAfterMethodCallName = False
|
||||
$2.SpaceBeforeOpenSquareBracket = False
|
||||
$2.inheritsSet = Mono
|
||||
$2.inheritsScope = text/x-csharp
|
||||
$2.scope = text/x-csharp
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LocalizationTool2
|
||||
{
|
||||
class MainClass
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var sourcefolder = Environment.CurrentDirectory;
|
||||
if (args == null || args.Length != 0)
|
||||
sourcefolder = args[0];
|
||||
|
||||
sourcefolder = Path.GetFullPath(sourcefolder.Replace("~", Environment.GetEnvironmentVariable("HOME")));
|
||||
|
||||
var targetfolder = sourcefolder;
|
||||
if (args == null || args.Length > 1)
|
||||
targetfolder = Path.GetFullPath(args[1].Replace("~", Environment.GetEnvironmentVariable("HOME")));
|
||||
|
||||
|
||||
if (!Directory.Exists(sourcefolder))
|
||||
{
|
||||
Console.WriteLine("No such directory: {0}", sourcefolder);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sourcefolder = Path.GetFullPath(sourcefolder);
|
||||
Console.WriteLine("Using directory {0}, scanning ....", sourcefolder);
|
||||
|
||||
|
||||
var searchlist = new Dictionary<string, Regex>();
|
||||
searchlist.Add("html", new Regex("((\\{\\{)|(ng-bind-html\\s*=\\s*\"))\\s*\\'(?<sourcestring>(\\\\\\'|[^\\'])+)\\'\\s*\\|\\s*localize(\\s|\\:|\\})", RegexOptions.Multiline | RegexOptions.IgnoreCase));
|
||||
searchlist.Add("js", new Regex("Localization\\.localize\\(\\s*((\\'(?<sourcestring>(\\\\\\'|[^\\'])+)\\')|(\\\"(?<sourcestring>(\\\\\\\"|[^\\\"])+)\\\"))", RegexOptions.Multiline | RegexOptions.IgnoreCase));
|
||||
searchlist.Add("cs", new Regex("LC.L\\s*\\(((@\\s*\"(?<sourcestring>(\"\"|[^\"])+))|(\"(?<sourcestring>(\\\\\"|[^\"])+)))\"\\s*(\\)|,)", RegexOptions.Multiline | RegexOptions.IgnoreCase));
|
||||
|
||||
var map = new Dictionary<string, LocalizationEntry>();
|
||||
|
||||
if (!sourcefolder.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
|
||||
sourcefolder += Path.DirectorySeparatorChar;
|
||||
|
||||
foreach (var ext in searchlist.Keys)
|
||||
{
|
||||
var re = searchlist[ext];
|
||||
foreach (var f in Directory.GetFiles(sourcefolder, "*." + ext, SearchOption.AllDirectories))
|
||||
{
|
||||
var txt = File.ReadAllText(f);
|
||||
foreach (Match match in re.Matches(txt))
|
||||
{
|
||||
var linepos = txt.Substring(match.Index).Count(x => x == '\n');
|
||||
var str = match.Groups["sourcestring"].Value.Replace("\n", "\\n").Replace("\r", "\\r");
|
||||
LocalizationEntry le;
|
||||
if (!map.TryGetValue(str, out le))
|
||||
map[str] = new LocalizationEntry(str, f.Substring(sourcefolder.Length), linepos);
|
||||
else
|
||||
le.AddSource(Path.GetFileName(f), linepos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//File.WriteAllText(Path.Combine(sourcefolder, "translations.json"), Newtonsoft.Json.JsonConvert.SerializeObject(map.Values.OrderBy(x => x.SourceLocations.FirstOrDefault()).ToArray(), Newtonsoft.Json.Formatting.Indented));
|
||||
//File.WriteAllText(Path.Combine(sourcefolder, "translations-list.json"), Newtonsoft.Json.JsonConvert.SerializeObject(map.Select(x => x.Key).OrderBy(x => x).ToArray(), Newtonsoft.Json.Formatting.Indented));
|
||||
|
||||
File.WriteAllLines(
|
||||
Path.Combine(targetfolder, "localization.po"),
|
||||
map.OrderBy(x => x.Key).Select(x => x.Value).SelectMany(x => new string[] {
|
||||
"#: " + x.SourceLocations.FirstOrDefault(),
|
||||
string.Format("msgid \"{0}\"", (x.SourceString ?? "")),
|
||||
string.Format("msgstr \"{0}\"", (x.SourceString ?? "")),
|
||||
""
|
||||
}),
|
||||
System.Text.Encoding.UTF8
|
||||
);
|
||||
|
||||
Console.WriteLine("Wrote {0} strings to {1}", map.Count, Path.Combine(targetfolder, "localization.po"));
|
||||
|
||||
/*File.WriteAllLines(
|
||||
Path.Combine(sourcefolder, "localization-by-file.po"),
|
||||
map.OrderBy(x => x.Value.SourceLocations.FirstOrDefault()).Select(x => x.Value).SelectMany(x => new string[] {
|
||||
"#: " + x.SourceLocations.FirstOrDefault(),
|
||||
string.Format("msgid \"{0}\"", (x.SourceString ?? "")),
|
||||
string.Format("msgstr \"{0}\"", (x.SourceString ?? "")),
|
||||
""
|
||||
}),
|
||||
System.Text.Encoding.UTF8
|
||||
);*/
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +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("LocalizationTool2")]
|
||||
[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("")]
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -91,6 +91,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.A
|
||||
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.Tools", "Duplicati\Tools\Duplicati.Tools.csproj", "{0797AA22-C5DD-4950-BB60-34765AB8C6DD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -273,6 +275,10 @@ Global
|
||||
{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
|
||||
{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
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
Policies = $0
|
||||
|
||||
@@ -186,44 +186,16 @@
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Duplicati.snk" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<Content Include="..\..\thirdparty\gpg\gpg2.exe">
|
||||
<Link>win-tools\gpg2.exe</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\gpg\gpg.exe">
|
||||
<Link>win-tools\gpg\gpg.exe</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\iconv.dll">
|
||||
<Link>win-tools\iconv.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\libadns-1.dll">
|
||||
<Link>win-tools\libadns-1.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\libassuan-0.dll">
|
||||
<Link>win-tools\libassuan-0.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\libgcrypt-11.dll">
|
||||
<Link>win-tools\libgcrypt-11.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\libgpg-error-0.dll">
|
||||
<Link>win-tools\libgpg-error-0.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\gpg\zlib1.dll">
|
||||
<Link>win-tools\zlib1.dll</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<EmbeddedResource Include="help.txt" />
|
||||
<Content Include="TrayWarning.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -135,12 +135,12 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="WinFormsRunner.cs" />
|
||||
<EmbeddedResource Include="Resources\CloseMenuIcon.png" />
|
||||
<EmbeddedResource Include="Resources\Pause.png" />
|
||||
<EmbeddedResource Include="Resources\Play.png" />
|
||||
<EmbeddedResource Include="Resources\StatusMenuIcon.png" />
|
||||
<Compile Include="RumpsRunner.cs" />
|
||||
<None Include="packages.config" />
|
||||
<EmbeddedResource Include="Resources\context_menu_open.ico" />
|
||||
<EmbeddedResource Include="Resources\context_menu_pause.ico" />
|
||||
<EmbeddedResource Include="Resources\context_menu_play.ico" />
|
||||
<EmbeddedResource Include="Resources\context_menu_quit.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Library\Backend\CloudFiles\Duplicati.Library.Backend.CloudFiles.csproj">
|
||||
@@ -311,6 +311,10 @@
|
||||
<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>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
|
||||
|
||||
@@ -81,10 +81,10 @@ namespace Duplicati.GUI.TrayIcon
|
||||
public const string PauseIcon = "Resources.TrayNormalPause.ico";
|
||||
public const string WorkingIcon = "Resources.TrayWorking.ico";
|
||||
|
||||
public static Bitmap Pause { get { return LoadImage("Resources.Pause.png"); } }
|
||||
public static Bitmap Play { get { return LoadImage("Resources.Play.png"); } }
|
||||
public static Bitmap CloseMenuIcon { get { return LoadImage("Resources.CloseMenuIcon.png"); } }
|
||||
public static Bitmap StatusMenuIcon { get { return LoadImage("Resources.StatusMenuIcon.png"); } }
|
||||
public static Bitmap Pause { get { return LoadImage("Resources.context_menu_pause.ico"); } }
|
||||
public static Bitmap Play { get { return LoadImage("Resources.context_menu_play.ico"); } }
|
||||
public static Bitmap CloseMenuIcon { get { return LoadImage("Resources.context_menu_quit.ico"); } }
|
||||
public static Bitmap StatusMenuIcon { get { return LoadImage("Resources.context_menu_open.ico"); } }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 661 B |
|
Before Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 562 B |
|
Before Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -145,6 +145,15 @@ namespace Duplicati.GUI.TrayIcon.Windows
|
||||
{
|
||||
if (m_onSingleClick != null)
|
||||
m_onSingleClick();
|
||||
else if (m_trayIcon != null && m_trayIcon.ContextMenuStrip != null)
|
||||
{
|
||||
// Show context menu on left-click as we have nothing happening otherwise
|
||||
try
|
||||
{
|
||||
typeof(NotifyIcon).GetMethod("ShowContextMenu", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).Invoke(m_trayIcon, null);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private void m_trayIcon_DoubleClick(object sender, EventArgs e)
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="NGettext, Version=0.6.1.0, Culture=neutral, PublicKeyToken=08d3d1c89dfd2985, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\NGettext.0.6.2\lib\net45\NGettext.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -41,28 +45,15 @@
|
||||
<Compile Include="ILocalizationService.cs" />
|
||||
<Compile Include="MockLocalizationService.cs" />
|
||||
<Compile Include="Short.cs" />
|
||||
<Compile Include="PoLocalizationService.cs" />
|
||||
<Compile Include="MoLocalizationService.cs" />
|
||||
<Compile Include="LocalizationContext.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Folder Include="Localizations\" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\..\..\Localizations\localization-da.po">
|
||||
<Link>Localizations\localization-da.po</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\..\..\Localizations\localization-de.po">
|
||||
<Link>Localizations\localization-de.po</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="..\..\..\Localizations\localization-en.po">
|
||||
<Link>Localizations\localization-en.po</Link>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\..\Localizations\localization.po">
|
||||
<Link>localization.po</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\..\..\Localizations\duplicati\*.mo" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -62,11 +62,6 @@ namespace Duplicati.Library.Localization
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="args">The arguments</param>
|
||||
string Localize(string message, params object[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a lookup table with all strings
|
||||
/// </summary>
|
||||
IDictionary<string, string> AllStrings { get; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Duplicati.Library.Localization.Short;
|
||||
|
||||
namespace Duplicati.Library.Localization
|
||||
{
|
||||
@@ -52,7 +53,8 @@ namespace Duplicati.Library.Localization
|
||||
{
|
||||
if (ci == null)
|
||||
return null;
|
||||
|
||||
|
||||
LC.setCulture(ci);
|
||||
return new LocalizationContext(ci);
|
||||
}
|
||||
|
||||
@@ -112,7 +114,7 @@ namespace Duplicati.Library.Localization
|
||||
|
||||
ILocalizationService service;
|
||||
if (!Services.TryGetValue(ci, out service))
|
||||
service = Services[ci] = new PoLocalizationService(ci);
|
||||
service = Services[ci] = new MoLocalizationService(ci);
|
||||
|
||||
return service;
|
||||
}
|
||||
@@ -134,7 +136,15 @@ namespace Duplicati.Library.Localization
|
||||
/// </summary>
|
||||
public static IEnumerable<string> SupportedCultures
|
||||
{
|
||||
get { return PoLocalizationService.SupportedCultures; }
|
||||
get { return MoLocalizationService.SupportedCultures; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the culture has localization support
|
||||
/// </summary>
|
||||
public static Boolean isCultureSupported(CultureInfo culture)
|
||||
{
|
||||
return MoLocalizationService.isCultureSupported(culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,254 +1,226 @@
|
||||
// Copyright (C) 2016, 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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Duplicati.Library.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for reading embedded PO files
|
||||
/// </summary>
|
||||
public class PoLocalizationService : ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// The environment variable used to locate PO files
|
||||
/// </summary>
|
||||
public const string LOCALIZATIONDIR_ENVNAME = "LOCALIZATION_FOLDER";
|
||||
|
||||
/// <summary>
|
||||
/// A cached copy of all strings
|
||||
/// </summary>
|
||||
private Dictionary<string, string> m_messages = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Path to search for extra .po files in
|
||||
/// </summary>
|
||||
public static string[] SearchPaths =
|
||||
string.IsNullOrWhiteSpace(AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string)
|
||||
? new string[] { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }
|
||||
: (AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string).Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Assembly to look for .po files in
|
||||
/// </summary>
|
||||
public static Assembly SearchAssembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression to match a PO line entry
|
||||
/// </summary>
|
||||
private static readonly Regex PO_MATCHER = new Regex(@"(?<key>\w+(\[(?<index>[0-9]+)\])?)\s+""(?<value>(?:\\.|""""|[^""\\])*)""");
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression to match a locale from a filename
|
||||
/// </summary>
|
||||
private static readonly Regex CI_MATCHER = new Regex(@"localization-(?<culture>" + LocalizationService.CI_MATCHER + @")\.po");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Localization.PoLocalizationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ci">The culture to find.</param>
|
||||
public PoLocalizationService(CultureInfo ci)
|
||||
{
|
||||
var filenames = new string[] {
|
||||
// Load the generic country version first
|
||||
string.Format("localization-{0}.po", ci.Name),
|
||||
// Then the specialized version with overrides
|
||||
string.Format("localization-{0}.po", ci.TwoLetterISOLanguageName)
|
||||
};
|
||||
|
||||
foreach(var fn in filenames)
|
||||
{
|
||||
// Use embedded version first
|
||||
if (SearchAssembly != null)
|
||||
{
|
||||
// Find the localization streams inside the assembly
|
||||
var names =
|
||||
from name in SearchAssembly.GetManifestResourceNames()
|
||||
let m = CI_MATCHER.Match(name)
|
||||
let c = m.Success && string.Equals(m.Value, fn, StringComparison.InvariantCultureIgnoreCase) ? LocalizationService.ParseCulture(m.Groups["culture"].Value) : null
|
||||
where c != null
|
||||
select name;
|
||||
|
||||
foreach (var sn in names)
|
||||
using (var s = SearchAssembly.GetManifestResourceStream(sn))
|
||||
if (s != null)
|
||||
using (var sr = new StreamReader(s, System.Text.Encoding.UTF8, true))
|
||||
ParseStream(sr);
|
||||
}
|
||||
|
||||
// Then override with external
|
||||
foreach(var sp in SearchPaths)
|
||||
if (!string.IsNullOrWhiteSpace(sp) && File.Exists(Path.Combine(sp, fn)))
|
||||
using (var fs = new StreamReader(Path.Combine(sp, fn), System.Text.Encoding.UTF8, true))
|
||||
ParseStream(fs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a PO file by reading each line
|
||||
/// </summary>
|
||||
/// <param name="rd">The reader to extract data from.</param>
|
||||
private void ParseStream(TextReader rd)
|
||||
{
|
||||
string ln;
|
||||
string msgid = null;
|
||||
|
||||
while ((ln = rd.ReadLine()) != null)
|
||||
{
|
||||
var m = PO_MATCHER.Match(ln);
|
||||
if (m.Success)
|
||||
{
|
||||
var key = m.Groups["key"].Value;
|
||||
var value = m.Groups["value"].Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
value = value.Replace("\"\"", "\"").Replace("\\\"", "\"").Replace("\\t", "\t").Replace("\\r", "\r").Replace("\\n", "\n");
|
||||
|
||||
|
||||
if (string.Equals(key, "msgid", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
msgid = value;
|
||||
}
|
||||
else if (string.Equals(key, "msgstr", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
m_messages[msgid] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the actual translation
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to translate.</param>
|
||||
private string Transform(string msg)
|
||||
{
|
||||
string res;
|
||||
if (string.IsNullOrWhiteSpace(msg) || !m_messages.TryGetValue(msg, out res))
|
||||
return msg;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message)
|
||||
{
|
||||
return string.Format(Transform(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="arg0">The first argument</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message, object arg0)
|
||||
{
|
||||
return string.Format(Transform(message), arg0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="arg0">The first argument</param>
|
||||
/// <param name="arg1">The second argument</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message, object arg0, object arg1)
|
||||
{
|
||||
return string.Format(Transform(message), arg0, arg1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="arg0">The first argument</param>
|
||||
/// <param name="arg1">The second argument</param>
|
||||
/// <param name="arg2">The third argument</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message, object arg0, object arg1, object arg2)
|
||||
{
|
||||
return string.Format(Transform(message), arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="args">The arguments</param>
|
||||
public string Localize(string message, params object[] args)
|
||||
{
|
||||
return string.Format(Transform(message), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all strings.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> AllStrings { get { return m_messages; } }
|
||||
|
||||
private static string[] m_supportedcultures = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all the supported cultures
|
||||
/// </summary>
|
||||
public static IEnumerable<string> SupportedCultures
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
if (m_supportedcultures == null)
|
||||
{
|
||||
var lst = new string[0].AsEnumerable();
|
||||
|
||||
if (SearchAssembly != null)
|
||||
lst = lst.Union(SearchAssembly.GetManifestResourceNames());
|
||||
foreach (var sp in SearchPaths)
|
||||
if (Directory.Exists(sp))
|
||||
lst = lst.Union(Directory.GetFiles(sp, "localization-*.po"));
|
||||
|
||||
var allcultures =
|
||||
from name in lst
|
||||
let m = CI_MATCHER.Match(name)
|
||||
let ci = m.Success ? LocalizationService.ParseCulture(m.Groups["culture"].Value) : null
|
||||
where ci != null
|
||||
select ci;
|
||||
|
||||
m_supportedcultures =
|
||||
allcultures
|
||||
.Select(x => x.TwoLetterISOLanguageName).Distinct().OrderBy(x => x)
|
||||
.Union(
|
||||
allcultures.Select(x => x.Name).Distinct().OrderBy(x => x)
|
||||
).ToArray();
|
||||
|
||||
}
|
||||
|
||||
return m_supportedcultures;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (C) 2016, 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;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using NGettext;
|
||||
|
||||
namespace Duplicati.Library.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for reading embedded MO files
|
||||
/// </summary>
|
||||
public class MoLocalizationService : ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// The catalog containing the translations
|
||||
/// </summary>
|
||||
private ICatalog catalog = new Catalog();
|
||||
|
||||
/// <summary>
|
||||
/// The environment variable used to locate MO files
|
||||
/// </summary>
|
||||
public const string LOCALIZATIONDIR_ENVNAME = "LOCALIZATION_FOLDER";
|
||||
|
||||
/// <summary>
|
||||
/// Path to search for extra .mo files in
|
||||
/// </summary>
|
||||
public static string[] SearchPaths =
|
||||
string.IsNullOrWhiteSpace(AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string)
|
||||
? new string[] { Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) }
|
||||
: (AppDomain.CurrentDomain.GetData(LOCALIZATIONDIR_ENVNAME) as string).Split(new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Assembly to look for .mo files in
|
||||
/// </summary>
|
||||
public static Assembly SearchAssembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
/// <summary>
|
||||
/// Regular expression to match a locale from a filename
|
||||
/// </summary>
|
||||
private static readonly Regex CI_MATCHER = new Regex(@"localization-(?<culture>" + LocalizationService.CI_MATCHER + @")\.mo");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Duplicati.Library.Localization.PoLocalizationService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ci">The culture to find.</param>
|
||||
public MoLocalizationService(CultureInfo ci)
|
||||
{
|
||||
var filenames = new string[] {
|
||||
// Load the specialized version first
|
||||
string.Format("localization-{0}.mo", ci.Name),
|
||||
// Then try the generic language version
|
||||
string.Format("localization-{0}.mo", ci.TwoLetterISOLanguageName)
|
||||
};
|
||||
|
||||
foreach(var fn in filenames)
|
||||
{
|
||||
// search first in external files
|
||||
foreach (var sp in SearchPaths)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(sp) && File.Exists(Path.Combine(sp, fn)))
|
||||
{
|
||||
using (var moFileStream = File.OpenRead(Path.Combine(sp, fn)))
|
||||
catalog = new Catalog(moFileStream, ci);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// search in embedded files
|
||||
if (SearchAssembly != null)
|
||||
{
|
||||
// Find the localization streams inside the assembly
|
||||
var names =
|
||||
from name in SearchAssembly.GetManifestResourceNames()
|
||||
let m = CI_MATCHER.Match(name)
|
||||
let c = m.Success && string.Equals(m.Value, fn, StringComparison.InvariantCultureIgnoreCase) ? LocalizationService.ParseCulture(m.Groups["culture"].Value) : null
|
||||
where c != null
|
||||
select name;
|
||||
|
||||
foreach (var sn in names)
|
||||
using (var s = SearchAssembly.GetManifestResourceStream(sn))
|
||||
if (s != null)
|
||||
{
|
||||
catalog = new Catalog(s, ci);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message)
|
||||
{
|
||||
return catalog.GetString(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="arg0">The first argument</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message, object arg0)
|
||||
{
|
||||
return catalog.GetString(message, arg0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="arg0">The first argument</param>
|
||||
/// <param name="arg1">The second argument</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message, object arg0, object arg1)
|
||||
{
|
||||
return catalog.GetString(message, arg0, arg1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="arg0">The first argument</param>
|
||||
/// <param name="arg1">The second argument</param>
|
||||
/// <param name="arg2">The third argument</param>
|
||||
/// <returns>The localized string</returns>
|
||||
public string Localize(string message, object arg0, object arg1, object arg2)
|
||||
{
|
||||
return catalog.GetString(message, arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
/// <param name="message">The string to localize</param>
|
||||
/// <param name="args">The arguments</param>
|
||||
public string Localize(string message, params object[] args)
|
||||
{
|
||||
return catalog.GetString(message, args);
|
||||
}
|
||||
|
||||
private static IEnumerable<CultureInfo> m_supportedcultures = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all the supported cultures
|
||||
/// </summary>
|
||||
private static IEnumerable<CultureInfo> SupportedCultureInfos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_supportedcultures == null)
|
||||
{
|
||||
var lst = new string[0].AsEnumerable();
|
||||
|
||||
if (SearchAssembly != null)
|
||||
lst = lst.Union(SearchAssembly.GetManifestResourceNames());
|
||||
foreach (var sp in SearchPaths)
|
||||
if (Directory.Exists(sp))
|
||||
lst = lst.Union(Directory.GetFiles(sp, "localization-*.mo"));
|
||||
|
||||
var allcultures =
|
||||
from name in lst
|
||||
let m = CI_MATCHER.Match(name)
|
||||
let ci = m.Success ? LocalizationService.ParseCulture(m.Groups["culture"].Value) : null
|
||||
where ci != null
|
||||
select ci;
|
||||
|
||||
m_supportedcultures = allcultures.Concat(new[] { new CultureInfo("en") })
|
||||
.Distinct();
|
||||
}
|
||||
return m_supportedcultures;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all the supported cultures
|
||||
/// </summary>
|
||||
public static IEnumerable<string> SupportedCultures
|
||||
{
|
||||
get
|
||||
{
|
||||
return SupportedCultureInfos
|
||||
.Select(x => x.Name).Distinct()
|
||||
.OrderBy(x => x)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the culture has localization support
|
||||
/// </summary>
|
||||
public static Boolean isCultureSupported(CultureInfo culture)
|
||||
{
|
||||
foreach (var supportedCulture in SupportedCultureInfos)
|
||||
{
|
||||
if (supportedCulture.TwoLetterISOLanguageName == culture.TwoLetterISOLanguageName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,11 +77,6 @@ namespace Duplicati.Library.Localization
|
||||
{
|
||||
return string.Format(message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all strings.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> AllStrings { get { return new Dictionary<string, string>(); } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,15 @@ namespace Duplicati.Library.Localization.Short
|
||||
/// </summary>
|
||||
private static ILocalizationService LS = LocalizationService.Current;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the culture
|
||||
/// </summary>
|
||||
/// <param name="ci">CultureInfo</param>
|
||||
public static void setCulture(System.Globalization.CultureInfo ci)
|
||||
{
|
||||
LS = LocalizationService.Get(ci);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Localizes the string similar to how string.Format works
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NGettext" version="0.6.2" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -82,18 +82,78 @@ namespace Duplicati.Library.Main
|
||||
/// </summary>
|
||||
private bool m_doResetLocale;
|
||||
|
||||
/// <summary>
|
||||
/// The caffeinate process runner
|
||||
/// </summary>
|
||||
private System.Diagnostics.Process m_caffeinate;
|
||||
|
||||
/// <summary>
|
||||
/// This gets called whenever execution of an operation is started or stopped; it currently handles the AllowSleep option
|
||||
/// </summary>
|
||||
/// <param name="isRunning">Flag indicating execution state</param>
|
||||
private void OperationRunning(bool isRunning)
|
||||
{
|
||||
if (m_options != null && !m_options.AllowSleep && !Duplicati.Library.Utility.Utility.IsClientLinux)
|
||||
try
|
||||
if (m_options != null && !m_options.AllowSleep)
|
||||
{
|
||||
if (Duplicati.Library.Utility.Utility.IsClientWindows)
|
||||
{
|
||||
Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS | (isRunning ? Win32.EXECUTION_STATE.ES_SYSTEM_REQUIRED : 0));
|
||||
try
|
||||
{
|
||||
Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS | (isRunning ? Win32.EXECUTION_STATE.ES_SYSTEM_REQUIRED : 0));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log.WriteMessage("Failed to set sleep prevention", Logging.LogMessageType.Warning, ex);
|
||||
}
|
||||
}
|
||||
catch { } //TODO: Report this somehow
|
||||
else if (Duplicati.Library.Utility.Utility.IsClientOSX)
|
||||
{
|
||||
if (isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_caffeinate == null)
|
||||
{
|
||||
// -s prevents sleep on AC, -i prevents sleep generally
|
||||
var psi = new System.Diagnostics.ProcessStartInfo("caffeinate", "-s");
|
||||
psi.RedirectStandardInput = true;
|
||||
psi.UseShellExecute = false;
|
||||
m_caffeinate = System.Diagnostics.Process.Start(psi);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log.WriteMessage("Failed to set sleep prevention", Logging.LogMessageType.Warning, ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_caffeinate != null && !m_caffeinate.HasExited)
|
||||
{
|
||||
// Send CTRL+C
|
||||
m_caffeinate.StandardInput.Write("\x3");
|
||||
m_caffeinate.StandardInput.Flush();
|
||||
m_caffeinate.WaitForExit(500);
|
||||
|
||||
if (!m_caffeinate.HasExited)
|
||||
{
|
||||
m_caffeinate.Kill();
|
||||
m_caffeinate.WaitForExit(500);
|
||||
if (!m_caffeinate.HasExited)
|
||||
throw new Exception("Failed to kill the caffeinate process");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log.WriteMessage("Failed to unset sleep prevention", Logging.LogMessageType.Warning, ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -146,13 +206,13 @@ namespace Duplicati.Library.Main
|
||||
for(int i = 0; i < sources.Count - 1; i++)
|
||||
{
|
||||
for(int j = i + 1; j < sources.Count; j++)
|
||||
if (sources[i].Equals(sources[j], Library.Utility.Utility.IsFSCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase))
|
||||
if (sources[i].Equals(sources[j], Library.Utility.Utility.ClientFilenameStringComparision))
|
||||
{
|
||||
result.AddVerboseMessage("Removing duplicate source: {0}", sources[j]);
|
||||
sources.RemoveAt(j);
|
||||
j--;
|
||||
}
|
||||
else if (sources[i].StartsWith(sources[j], Library.Utility.Utility.IsFSCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase))
|
||||
else if (sources[i].StartsWith(sources[j], Library.Utility.Utility.ClientFilenameStringComparision))
|
||||
{
|
||||
bool includes;
|
||||
bool excludes;
|
||||
@@ -352,7 +412,6 @@ namespace Duplicati.Library.Main
|
||||
using(new Logging.Timer(string.Format("Running {0}", result.MainOperation)))
|
||||
{
|
||||
SetupCommonOptions(result, ref paths, ref filter);
|
||||
OperationRunning(true);
|
||||
|
||||
method(result);
|
||||
|
||||
@@ -360,18 +419,21 @@ namespace Duplicati.Library.Main
|
||||
result.SetDatabase(null);
|
||||
|
||||
OnOperationComplete(result);
|
||||
|
||||
Library.Logging.Log.WriteMessage(Strings.Controller.CompletedOperationMessage(m_options.MainAction), Logging.LogMessageType.Information);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.Log.WriteMessage("Terminated with error: " + ex.Message, Duplicati.Library.Logging.LogMessageType.Error, ex);
|
||||
|
||||
OnOperationComplete(ex);
|
||||
|
||||
try { (result as BasicResults).OperationProgressUpdater.UpdatePhase(OperationPhase.Error); }
|
||||
catch { }
|
||||
|
||||
Library.Logging.Log.WriteMessage(Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message), Logging.LogMessageType.Error, ex);
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -1201,7 +1201,20 @@ namespace Duplicati.Library.Main.Database
|
||||
if (m_connection != null && m_result != null)
|
||||
{
|
||||
m_result.FlushLog();
|
||||
LogMessage("Result", Library.Utility.Utility.PrintSerializeObject(m_result, (StringBuilder)null, x => !typeof(System.Collections.IEnumerable).IsAssignableFrom(x.PropertyType)).ToString(), null, null);
|
||||
LogMessage("Result",
|
||||
Library.Utility.Utility.PrintSerializeObject(
|
||||
m_result,
|
||||
(StringBuilder)null,
|
||||
x =>
|
||||
!typeof(IBackendProgressUpdater).IsAssignableFrom(x.PropertyType) &&
|
||||
!typeof(IMessageSink).IsAssignableFrom(x.PropertyType) &&
|
||||
!typeof(ILogWriter).IsAssignableFrom(x.PropertyType),
|
||||
recurseobjects: true,
|
||||
collectionlimit: 5
|
||||
).ToString(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +248,12 @@ namespace Duplicati.Library.Main.Operation
|
||||
|
||||
public static Snapshots.ISnapshotService GetSnapshot(string[] sources, Options options, ILogWriter log)
|
||||
{
|
||||
if (!Library.Utility.Utility.IsClientWindows && options.RawOptions.ContainsKey("hyperv-backup-vm"))
|
||||
log.AddWarning("hyperv-backup-vm is efective only on Windows OS.", null);
|
||||
|
||||
if (Library.Utility.Utility.IsClientWindows && options.RawOptions.ContainsKey("hyperv-backup-vm") && options.SnapShotStrategy == Options.OptimizationStrategy.Off)
|
||||
throw new Exception("Snapshot strategy cannot be Off when backuping Hyper-V using hyperv-backup-vm"); //VSS is required for Hyper-V backups
|
||||
|
||||
try
|
||||
{
|
||||
if (options.SnapShotStrategy != Options.OptimizationStrategy.Off)
|
||||
@@ -257,6 +263,8 @@ namespace Duplicati.Library.Main.Operation
|
||||
{
|
||||
if (options.SnapShotStrategy == Options.OptimizationStrategy.Required)
|
||||
throw;
|
||||
else if (options.SnapShotStrategy == Options.OptimizationStrategy.On && options.RawOptions.ContainsKey("hyperv-backup-vm"))
|
||||
throw; //VSS is required for Hyper-V backups
|
||||
else if (options.SnapShotStrategy == Options.OptimizationStrategy.On)
|
||||
{
|
||||
log.AddWarning(Strings.Common.SnapshotFailedError(ex.ToString()), ex);
|
||||
|
||||
@@ -244,7 +244,6 @@ namespace Duplicati.Library.Main
|
||||
"vss-use-mapping",
|
||||
"usn-policy",
|
||||
"hyperv-backup-vm",
|
||||
"hyperv-backup-no-merge",
|
||||
"symlink-policy",
|
||||
"hardlink-policy",
|
||||
"exclude-files-attributes",
|
||||
@@ -440,7 +439,6 @@ namespace Duplicati.Library.Main
|
||||
new CommandLineArgument("vss-use-mapping", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VssusemappingShort, Strings.Options.VssusemappingLong, "false"),
|
||||
new CommandLineArgument("usn-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.UsnpolicyShort, Strings.Options.UsnpolicyLong, "off", null, Enum.GetNames(typeof(OptimizationStrategy))),
|
||||
new CommandLineArgument("hyperv-backup-vm", CommandLineArgument.ArgumentType.String, Strings.Options.HypervbackupvmShort, Strings.Options.HypervbackupvmLong),
|
||||
new CommandLineArgument("hyperv-backup-no-merge", CommandLineArgument.ArgumentType.Boolean, Strings.Options.HypervbackupnomergeShort, Strings.Options.HypervbackupnomergeLong),
|
||||
|
||||
new CommandLineArgument("encryption-module", CommandLineArgument.ArgumentType.String, Strings.Options.EncryptionmoduleShort, Strings.Options.EncryptionmoduleLong, "aes"),
|
||||
new CommandLineArgument("compression-module", CommandLineArgument.ArgumentType.String, Strings.Options.CompressionmoduleShort, Strings.Options.CompressionmoduleLong, "zip"),
|
||||
@@ -455,7 +453,7 @@ namespace Duplicati.Library.Main
|
||||
new CommandLineArgument("log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Options.LoglevelShort, Strings.Options.LoglevelLong, "Warning", null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))),
|
||||
|
||||
new CommandLineArgument("list-verify-uploads", CommandLineArgument.ArgumentType.Boolean, Strings.Options.ListverifyuploadsShort, Strings.Options.ListverifyuploadsShort, "false"),
|
||||
new CommandLineArgument("allow-sleep", CommandLineArgument.ArgumentType.Boolean, Strings.Options.AllowsleepShort, Strings.Options.AllowsleepShort, "false"),
|
||||
new CommandLineArgument("allow-sleep", CommandLineArgument.ArgumentType.Boolean, Strings.Options.AllowsleepShort, Strings.Options.AllowsleepLong, "false"),
|
||||
new CommandLineArgument("no-connection-reuse", CommandLineArgument.ArgumentType.Boolean, Strings.Options.NoconnectionreuseShort, Strings.Options.NoconnectionreuseLong, "false"),
|
||||
|
||||
new CommandLineArgument("quota-size", CommandLineArgument.ArgumentType.Size, Strings.Options.QuotasizeShort, Strings.Options.QuotasizeLong),
|
||||
|
||||
@@ -587,7 +587,14 @@ namespace Duplicati.Library.Main
|
||||
/// <returns>A <see cref="System.String"/> that represents the current <see cref="Duplicati.Library.Main.BasicResults"/>.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Library.Utility.Utility.PrintSerializeObject(this).ToString();
|
||||
return Library.Utility.Utility.PrintSerializeObject(
|
||||
this,
|
||||
filter: x =>
|
||||
!typeof(IBackendProgressUpdater).IsAssignableFrom(x.PropertyType) &&
|
||||
!typeof(IMessageSink).IsAssignableFrom(x.PropertyType) &&
|
||||
!typeof(ILogWriter).IsAssignableFrom(x.PropertyType),
|
||||
recurseobjects: true
|
||||
).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Duplicati.Library.Main.Strings
|
||||
public static string UnsupportedSizeValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid size", optionname, value); }
|
||||
public static string UnsupportedTimeValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid time", optionname, value); }
|
||||
public static string StartingOperationMessage(OperationMode operationname) { return LC.L(@"The operation {0} has started", operationname); }
|
||||
public static string CompletedOperationMessage(OperationMode operationname) { return LC.L(@"The operation {0} has completed", operationname); }
|
||||
public static string FailedOperationMessage(OperationMode operationname, string errormessage) { return LC.L(@"The operation {0} has failed with error: {1}", operationname, errormessage); }
|
||||
public static string InvalidPathError(string path, string message) { return LC.L(@"Invalid path: ""{0}"" ({1})", path, message); }
|
||||
public static string FailedForceLocaleError(string exMsg) { return LC.L(@"Failed to apply 'force-locale' setting. Please try to update .NET-Framework. Exception was: ""{0}"" ", exMsg); }
|
||||
}
|
||||
@@ -34,7 +36,8 @@ namespace Duplicati.Library.Main.Strings
|
||||
public static string DisablefiletimecheckShort { get { return LC.L(@"Disable checks based on file time"); } }
|
||||
public static string RestorepathLong { get { return LC.L(@"By default, files will be restored in the source folders, use this option to restore to another folder"); } }
|
||||
public static string RestorepathShort { get { return LC.L(@"Restore to another folder"); } }
|
||||
public static string AllowsleepShort { get { return LC.L(@"Allow system to enter sleep power modes for inactivity during backup/restore operations (Windows only)"); } }
|
||||
public static string AllowsleepShort { get { return LC.L(@"Toggles system sleep mode"); } }
|
||||
public static string AllowsleepLong { get { return LC.L(@"Allow system to enter sleep power modes for inactivity during backup/restore operations (Windows/OSX only)"); } }
|
||||
public static string ThrottledownloadLong { get { return LC.L(@"By setting this value you can limit how much bandwidth Duplicati consumes for downloads. Setting this limit can make the backups take longer, but will make Duplicati less intrusive."); } }
|
||||
public static string ThrottledownloadShort { get { return LC.L(@"Max number of kilobytes to download pr. second"); } }
|
||||
public static string ThrottleuploadLong { get { return LC.L(@"By setting this value you can limit how much bandwidth Duplicati consumes for uploads. Setting this limit can make the backups take longer, but will make Duplicati less intrusive."); } }
|
||||
@@ -209,11 +212,8 @@ namespace Duplicati.Library.Main.Strings
|
||||
public static string ForcelocaleLong { get { return LC.L(@"By default, your system locale and culture settings will be used. In some cases you may prefer to run with another locale, for example to get messages in another language. This option can be used to set the locale. Supply a blank string to choose the ""Invariant Culture""."); } }
|
||||
public static string DisablepipingShort{ get { return LC.L(@"Handle file communication with backend using threaded pipes"); } }
|
||||
public static string DisablepipingLong { get { return LC.L(@"Use this option to disable multithreaded handling of up- and downloads, that can significantly speed up backend operations depending on the hardware you're running on and the transfer rate of your backend."); } }
|
||||
public static string HypervbackupvmShort { get { return LC.L(@"Perform backup of HyperV machines (Windows only)"); } }
|
||||
public static string HypervbackupvmLong { get { return LC.L(@"Use this option to specify the names of machines to include in the backup. Specify multiple machine names with a semicolon separator."); } }
|
||||
public static string HypervbackupnomergeShort { get { return LC.L(@"Do not merge the HyperV VHD (Windows only)"); } }
|
||||
public static string HypervbackupnomergeLong { get { return LC.L(@"Use this option to disable the merge of HyperV VHD's prior to performing the backup."); } }
|
||||
|
||||
public static string HypervbackupvmShort { get { return LC.L(@"Perform backup of Hyper-V machines (Windows only)"); } }
|
||||
public static string HypervbackupvmLong { get { return LC.L(@"Use this option to specify the IDs of machines to include in the backup. Specify multiple machine IDs with a semicolon separator. (You can use this Powershell command to get ID 'Get-VM | ft VMName, ID')"); } }
|
||||
}
|
||||
|
||||
internal static class Common
|
||||
|
||||
@@ -48,7 +48,10 @@ namespace Duplicati.Library.Main.Volumes
|
||||
public VolumeWriterBase(Options options, DateTime timestamp)
|
||||
: base(options)
|
||||
{
|
||||
m_localfile = new Library.Utility.TempFile();
|
||||
if (!string.IsNullOrWhiteSpace(options.AsynchronousUploadFolder))
|
||||
m_localfile = Library.Utility.TempFile.CreateInFolder(options.AsynchronousUploadFolder);
|
||||
else
|
||||
m_localfile = new Library.Utility.TempFile();
|
||||
|
||||
ResetRemoteFilename(options, timestamp);
|
||||
m_compression = DynamicLoader.CompressionLoader.GetModule(options.CompressionModule, m_localfile, options.RawOptions);
|
||||
|
||||
@@ -3,317 +3,188 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Duplicati.Library.Snapshots
|
||||
{
|
||||
public class HyperVGuest
|
||||
{
|
||||
public string Name { get; }
|
||||
public string ID { get; }
|
||||
public List<string> DataPaths { get; }
|
||||
|
||||
public HyperVGuest(string Name, string ID, List<string> DataPaths)
|
||||
{
|
||||
this.Name = Name;
|
||||
this.ID = ID;
|
||||
this.DataPaths = DataPaths;
|
||||
}
|
||||
}
|
||||
|
||||
//Original code: HVBackup Project
|
||||
public class HyperVUtility
|
||||
{
|
||||
private static class WmiClasses
|
||||
{
|
||||
internal const string MsVM_VSMS = "MsVM_VirtualSystemManagementService";
|
||||
internal const string MsVM_VSSD = "MsVM_VirtualSystemSettingData";
|
||||
internal const string MsVM_IMS = "MsVM_ImageManagementService";
|
||||
internal const string MsVM_RASD = "MsVM_ResourceAllocationSettingData";
|
||||
}
|
||||
|
||||
private static class Methods
|
||||
{
|
||||
internal const string DefineVirtualSystem = "DefineVirtualSystem";
|
||||
internal const string ModifyVirtualSystem = "ModifyVirtualSystem";
|
||||
internal const string GetVirtualHardDiskInfo = "GetVirtualHardDiskInfo";
|
||||
internal const string MergeVirtualHardDisk = "MergeVirtualHardDisk";
|
||||
}
|
||||
|
||||
private static class ReturnCode
|
||||
{
|
||||
internal const UInt16 ERROR_SUCCESS = 0;
|
||||
internal const UInt16 ERROR_JOBSTARTED = 4096;
|
||||
}
|
||||
|
||||
private static class JobState
|
||||
{
|
||||
internal const UInt16 Starting = 3;
|
||||
internal const UInt16 Running = 4;
|
||||
internal const UInt16 Completed = 7;
|
||||
}
|
||||
|
||||
private readonly ManagementScope _wmiScope;
|
||||
private readonly string _vmIdField;
|
||||
private readonly string _wmiHost = "localhost"; //We could backup remote Hyper-V Machines in the future
|
||||
private readonly string _wmiHost = "localhost";
|
||||
private readonly bool _wmiv2Namespace;
|
||||
|
||||
public List<string> RequestedVmNames = new List<string>();
|
||||
readonly IDictionary<string, string> _hyperVMachines = new Dictionary<string, string>();
|
||||
|
||||
public HyperVUtility()
|
||||
{
|
||||
//Set the namespace depending off host OS
|
||||
_wmiv2Namespace = Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor >= 2;
|
||||
//Set the scope to use in MWI. V2 for Server 2012 or newer.
|
||||
|
||||
//Set the scope to use in WMI. V2 for Server 2012 or newer.
|
||||
_wmiScope = _wmiv2Namespace
|
||||
? new ManagementScope(string.Format("\\\\{0}\\root\\virtualization\\v2", _wmiHost))
|
||||
: new ManagementScope(string.Format("\\\\{0}\\root\\virtualization", _wmiHost));
|
||||
//Set the VM ID Selector Field for the WMI Query
|
||||
_vmIdField = _wmiv2Namespace ? "VirtualSystemIdentifier" : "SystemName";
|
||||
|
||||
}
|
||||
|
||||
public HyperVUtility(List<string> reqVmNames)
|
||||
{
|
||||
_wmiv2Namespace = Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor >= 2;
|
||||
_wmiScope = _wmiv2Namespace
|
||||
? new ManagementScope(string.Format("\\\\{0}\\root\\virtualization\\v2", _wmiHost))
|
||||
: new ManagementScope(string.Format("\\\\{0}\\root\\virtualization", _wmiHost));
|
||||
_vmIdField = _wmiv2Namespace ? "VirtualSystemIdentifier" : "SystemName";
|
||||
//Store requested VM's
|
||||
RequestedVmNames = reqVmNames;
|
||||
Logging.Log.WriteMessage(string.Format("Using WMI provider {0}", _wmiScope.Path), Logging.LogMessageType.Profiling);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We query the Hyper-V for all requested Virtual Machines
|
||||
/// </summary>
|
||||
/// <returns>List of Hyper-V Machines</returns>
|
||||
public IDictionary<string, string> GetHyperVMachines()
|
||||
{
|
||||
var moCollection = GetWmiObjects(WmiQueryHyperVMachines());
|
||||
foreach (var mObject in moCollection)
|
||||
using (mObject)
|
||||
_hyperVMachines.Add((string) mObject[_vmIdField], (string) mObject["ElementName"]);
|
||||
|
||||
return _hyperVMachines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We build up the wmi-query to retrieve all Virtual Machines
|
||||
/// </summary>
|
||||
/// <returns>WMI query</returns>
|
||||
private string WmiQueryHyperVMachines()
|
||||
public List<HyperVGuest> GetHyperVGuests()
|
||||
{
|
||||
var hyperVMachines = new List<HyperVGuest>();
|
||||
var wmiQuery = _wmiv2Namespace
|
||||
? "SELECT VirtualSystemIdentifier, ElementName FROM Msvm_VirtualSystemSettingData WHERE VirtualSystemType = 'Microsoft:Hyper-V:System:Realized'"
|
||||
: "SELECT SystemName, ElementName FROM Msvm_VirtualSystemSettingData WHERE SettingType = 3";
|
||||
? "SELECT * FROM Msvm_VirtualSystemSettingData WHERE VirtualSystemType = 'Microsoft:Hyper-V:System:Realized'"
|
||||
: "SELECT * FROM Msvm_VirtualSystemSettingData WHERE SettingType = 3";
|
||||
|
||||
//Temporary Stringbuilder to build up the VM selectors
|
||||
var sb = new StringBuilder();
|
||||
using (var moCollection = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(wmiQuery)).Get())
|
||||
foreach (var mObject in moCollection)
|
||||
{
|
||||
var paths = GetAllVmVhdPaths((string)mObject[_vmIdField]);
|
||||
paths.AddRange(GetAllVmConfigPaths((string)mObject[_vmIdField]));
|
||||
|
||||
if (RequestedVmNames == null || !RequestedVmNames.Any()) return null;
|
||||
foreach (var vmName in RequestedVmNames)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append(" OR ");
|
||||
sb.Append(string.Format("ElementName = \'{0}\'", vmName.Replace("'", "''")));
|
||||
}
|
||||
wmiQuery += string.Format(" AND ({0})", sb);
|
||||
hyperVMachines.Add(new HyperVGuest((string)mObject["ElementName"], (string)mObject[_vmIdField], paths));
|
||||
}
|
||||
|
||||
return wmiQuery;
|
||||
return hyperVMachines;
|
||||
}
|
||||
|
||||
#region Management Object helpers
|
||||
/// <summary>
|
||||
/// Executes the WMI Query
|
||||
/// For given Hyper-V guest it enumerate all associated configuration files
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <returns>A collection of results</returns>
|
||||
private ManagementObjectCollection GetWmiObjects(string query)
|
||||
/// <returns>A collection of configuration paths</returns>
|
||||
private List<string> GetAllVmConfigPaths(string vmID)
|
||||
{
|
||||
var moSearcher = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(query));
|
||||
var resultset = moSearcher.Get();
|
||||
return resultset;
|
||||
}
|
||||
var result = new List<string>();
|
||||
string path;
|
||||
var wmiQuery = _wmiv2Namespace
|
||||
? string.Format("select * from Msvm_VirtualSystemSettingData where {0}='{1}'", _vmIdField, vmID)
|
||||
: string.Format("select * from Msvm_VirtualSystemGlobalSettingData where {0}='{1}'", _vmIdField, vmID);
|
||||
|
||||
/// <summary>
|
||||
/// Queries and gets a Management Object
|
||||
/// </summary>
|
||||
/// <param name="className"></param>
|
||||
/// <param name="where"></param>
|
||||
/// <returns></returns>
|
||||
private ManagementObject GetMsVMObject(string className, string where)
|
||||
{
|
||||
var query = where != null
|
||||
? string.Format("select * from {0} where {1}", className, where)
|
||||
: string.Format("select * from {0}", className);
|
||||
|
||||
var resultset = GetWmiObjects(query);
|
||||
|
||||
if (resultset.Count != 1)
|
||||
throw new InvalidOperationException(string.Format("Cannot locate {0} where {1}", className, where));
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var instance in resultset)
|
||||
return instance as ManagementObject;
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Failure retrieving {0} where {1}", className, where), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ManagementObject GetTargetVmObject(string vmElementName)
|
||||
{
|
||||
var query = string.Format("select * from Msvm_ComputerSystem where ElementName = '{0}'", vmElementName);
|
||||
var computers = GetWmiObjects(query);
|
||||
|
||||
foreach (var instance in computers)
|
||||
return (ManagementObject)instance;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
# endregion Management Object helpers
|
||||
|
||||
|
||||
|
||||
#region Recovery Virtual Machines
|
||||
|
||||
/// <summary>
|
||||
/// Please refer to https://blogs.msdn.microsoft.com/sergeim/2008/06/03/prepare-vm-create-vm-programmatically-hyper-v-api-c-version/
|
||||
/// Creation of a Hyper-V Machine
|
||||
/// </summary>
|
||||
/// <param name="vmDisplayName"></param>
|
||||
/// <param name="vmNotes"></param>
|
||||
public void CreateHyperVMachine(string vmDisplayName, string vmNotes)
|
||||
{
|
||||
var sysManService = GetMsVMObject(WmiClasses.MsVM_VSMS, null);
|
||||
|
||||
//Defining a VM with empty settings
|
||||
var hyperVM = sysManService.InvokeMethod(Methods.DefineVirtualSystem,
|
||||
sysManService.GetMethodParameters(Methods.DefineVirtualSystem), null);
|
||||
|
||||
if ((uint) hyperVM["ReturnValue"] != ReturnCode.ERROR_SUCCESS)
|
||||
throw new InvalidOperationException(
|
||||
$"DefineVirtualSystem failed. ReturnValue: {(uint) hyperVM["ReturnValue"]}");
|
||||
|
||||
var hyperVMTemplate = new ManagementObject((string) hyperVM["DefinedSystem"]);
|
||||
|
||||
// this is GUID; will need to locate settings for this VM and edit the settings
|
||||
var hyperVMSettings = GetMsVMObject(WmiClasses.MsVM_VSSD,
|
||||
string.Format("systemname = '{0}'", (string) hyperVMTemplate["name"]));
|
||||
hyperVMSettings["elementname"] = vmDisplayName;
|
||||
hyperVMSettings["notes"] = vmNotes;
|
||||
hyperVMSettings["BIOSGUID"] = new Guid();
|
||||
hyperVMSettings["BIOSNumLock"] = "true";
|
||||
hyperVMSettings["Description"] = "Hyper-V Machine restored from machine xxxx by Duplicati";
|
||||
hyperVMSettings.Put();
|
||||
|
||||
var hyperVMParams = sysManService.GetMethodParameters(Methods.ModifyVirtualSystem);
|
||||
var settingsText = hyperVMSettings.GetText(TextFormat.WmiDtd20);
|
||||
hyperVMParams["ComputerSystem"] = hyperVMTemplate;
|
||||
hyperVMParams["SystemSettingData"] = settingsText;
|
||||
sysManService.InvokeMethod(Methods.ModifyVirtualSystem, hyperVMParams, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Merging VHD
|
||||
public void MergeVhd(List<string> VmNames)
|
||||
{
|
||||
var vhdPaths = VmNames.SelectMany(x => GetAllVmVhdPaths(x)).ToArray();
|
||||
|
||||
var imgMan = GetMsVMObject(WmiClasses.MsVM_IMS, null);
|
||||
var inParams = imgMan.GetMethodParameters(Methods.MergeVirtualHardDisk);
|
||||
inParams["DestinationPath"] = vhdPaths.First();
|
||||
inParams["SourcePath"] = vhdPaths.Last();
|
||||
var outParams = imgMan.InvokeMethod(Methods.MergeVirtualHardDisk, inParams, null);
|
||||
if (outParams != null && (uint)outParams["ReturnValue"] == ReturnCode.ERROR_JOBSTARTED)
|
||||
{
|
||||
JobCompleted(outParams, _wmiScope);
|
||||
/*var result = JobCompleted(outParams, _wmiScope)
|
||||
? $"{inParams["SourcePath"]} was merged successfully."
|
||||
: $"{inParams["SourcePath"]} failed merging.";
|
||||
*/
|
||||
}
|
||||
|
||||
//Remove the snapshots since they are no longer valid.
|
||||
RemoveSnapshotTree();
|
||||
}
|
||||
|
||||
private void RemoveSnapshotTree()
|
||||
{
|
||||
GetTargetVmObject("merge");
|
||||
}
|
||||
|
||||
public static bool JobCompleted(ManagementBaseObject outParams, ManagementScope scope)
|
||||
{
|
||||
bool jobCompleted = true;
|
||||
|
||||
//Retrieve msvc_StorageJob path. This is a full wmi path
|
||||
var JobPath = (string)outParams["Job"];
|
||||
var Job = new ManagementObject(scope, new ManagementPath(JobPath), null);
|
||||
//Try to get storage job information
|
||||
Job.Get();
|
||||
while ((UInt16)Job["JobState"] == JobState.Starting
|
||||
|| (UInt16)Job["JobState"] == JobState.Running)
|
||||
{
|
||||
Logging.Log.WriteMessage(string.Format("HyperV in progress... {0}% completed.", Job["PercentComplete"]), Logging.LogMessageType.Information);
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
Job.Get();
|
||||
}
|
||||
|
||||
//Figure out if job failed
|
||||
var jobState = (UInt16)Job["JobState"];
|
||||
if (jobState != JobState.Completed)
|
||||
{
|
||||
var jobErrorCode = (UInt16)Job["ErrorCode"];
|
||||
Logging.Log.WriteMessage(string.Format("HyperV Error, code: {0}, message: {1}", jobErrorCode, Job["ErrorDescription"]), Logging.LogMessageType.Error);
|
||||
jobCompleted = false;
|
||||
}
|
||||
return jobCompleted;
|
||||
}
|
||||
|
||||
private List<string> GetAllVmVhdPaths(string vmName)
|
||||
{
|
||||
|
||||
var ParentPaths = new List<string>();
|
||||
var vm = GetTargetVmObject(vmName);
|
||||
var imgMan = GetMsVMObject(WmiClasses.MsVM_IMS, null);
|
||||
|
||||
foreach (var sysSettings in vm.GetRelated(WmiClasses.MsVM_VSSD))
|
||||
{
|
||||
var systemObjCollection = ((ManagementObject)sysSettings).GetRelated(WmiClasses.MsVM_RASD);
|
||||
var tempvhd = from ManagementBaseObject systemBaseObj in systemObjCollection
|
||||
where ((UInt16) systemBaseObj["ResourceType"] == 21
|
||||
&& (string) systemBaseObj["ResourceSubType"] == "Microsoft Virtual Hard Disk")
|
||||
select systemBaseObj;
|
||||
ParentPaths.Add(((IEnumerable<object>) tempvhd.First()["Connection"]).First().ToString());
|
||||
}
|
||||
|
||||
var result = new List<string>() {ParentPaths.First()};
|
||||
|
||||
var inParams = imgMan.GetMethodParameters(Methods.GetVirtualHardDiskInfo);
|
||||
foreach (var vhdPath in ParentPaths)
|
||||
{
|
||||
inParams["Path"] = vhdPath;
|
||||
var outParams = imgMan.InvokeMethod(Methods.GetVirtualHardDiskInfo, inParams,
|
||||
null);
|
||||
|
||||
if (outParams != null)
|
||||
using (var mObject1 = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(wmiQuery)).Get().Cast<ManagementObject>().First())
|
||||
if (_wmiv2Namespace)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.LoadXml((string) outParams["Info"]);
|
||||
var node = doc.SelectSingleNode("//PROPERTY[@NAME = 'ParentPath']/VALUE/child::text()");
|
||||
if (node == null)
|
||||
path = Path.Combine((string)mObject1["ConfigurationDataRoot"], (string)mObject1["ConfigurationFile"]);
|
||||
if (File.Exists(path))
|
||||
result.Add(path);
|
||||
|
||||
using (var snaps = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(string.Format(
|
||||
"SELECT * FROM Msvm_VirtualSystemSettingData where VirtualSystemType='Microsoft:Hyper-V:Snapshot:Realized' and {0}='{1}'",
|
||||
_vmIdField, vmID))).Get())
|
||||
{
|
||||
if (result.Contains(vhdPath)) continue;
|
||||
result.Insert(0, vhdPath);
|
||||
foreach (var snap in snaps)
|
||||
{
|
||||
path = Path.Combine((string)snap["ConfigurationDataRoot"], (string)snap["ConfigurationFile"]);
|
||||
if (File.Exists(path))
|
||||
result.Add(path);
|
||||
path = Utility.Utility.AppendDirSeparator(Path.Combine((string)snap["ConfigurationDataRoot"], (string)snap["SuspendDataRoot"]));
|
||||
if (Directory.Exists(path))
|
||||
result.Add(path);
|
||||
}
|
||||
}
|
||||
if (!result.Contains(vhdPath))
|
||||
result.Add(vhdPath);
|
||||
if (result.Contains(vhdPath))
|
||||
result.Insert(result.IndexOf(vhdPath), node.Value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
else
|
||||
{
|
||||
path = Path.Combine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID + ".xml");
|
||||
if (File.Exists(path))
|
||||
result.Add(path);
|
||||
path = Utility.Utility.AppendDirSeparator(Path.Combine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID));
|
||||
if (Directory.Exists(path))
|
||||
result.Add(path);
|
||||
|
||||
var snapsIDs = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(string.Format(
|
||||
"SELECT * FROM Msvm_VirtualSystemSettingData where SettingType=5 and {0}='{1}'",
|
||||
_vmIdField, vmID))).Get().OfType<ManagementObject>().Select(o => (string)o.GetPropertyValue("InstanceID")).ToList();
|
||||
|
||||
foreach (var snapID in snapsIDs)
|
||||
{
|
||||
path = Path.Combine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", "") + ".xml");
|
||||
if (File.Exists(path))
|
||||
result.Add(path);
|
||||
path = Utility.Utility.AppendDirSeparator(Path.Combine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", "")));
|
||||
if (Directory.Exists(path))
|
||||
result.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return result.Distinct(Utility.Utility.ClientFilenameStringComparer).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// For given Hyper-V guest it enumerate all associated VHD files
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <returns>A collection of VHD paths</returns>
|
||||
private List<string> GetAllVmVhdPaths(string vmID)
|
||||
{
|
||||
var result = new List<string>();
|
||||
using (var vm = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(string.Format("select * from Msvm_ComputerSystem where Name = '{0}'", vmID)))
|
||||
.Get().OfType<ManagementObject>().First())
|
||||
{
|
||||
foreach (var sysSettings in vm.GetRelated("MsVM_VirtualSystemSettingData"))
|
||||
using (var systemObjCollection = ((ManagementObject)sysSettings).GetRelated(_wmiv2Namespace ? "MsVM_StorageAllocationSettingData" : "MsVM_ResourceAllocationSettingData"))
|
||||
{
|
||||
List<string> tempvhd;
|
||||
|
||||
if (_wmiv2Namespace)
|
||||
tempvhd = (from ManagementBaseObject systemBaseObj in systemObjCollection
|
||||
where ((UInt16)systemBaseObj["ResourceType"] == 31
|
||||
&& (string)systemBaseObj["ResourceSubType"] == "Microsoft:Hyper-V:Virtual Hard Disk")
|
||||
select ((string[])systemBaseObj["HostResource"])[0]).ToList();
|
||||
else
|
||||
tempvhd = (from ManagementBaseObject systemBaseObj in systemObjCollection
|
||||
where ((UInt16)systemBaseObj["ResourceType"] == 21
|
||||
&& (string)systemBaseObj["ResourceSubType"] == "Microsoft Virtual Hard Disk")
|
||||
select ((string[])systemBaseObj["Connection"])[0]).ToList();
|
||||
|
||||
foreach (var vhd in tempvhd)
|
||||
result.Add(vhd);
|
||||
}
|
||||
}
|
||||
|
||||
using (var imgMan = new ManagementObjectSearcher(_wmiScope, new ObjectQuery("select * from MsVM_ImageManagementService")).Get().OfType<ManagementObject>().First())
|
||||
{
|
||||
var ParentPaths = new List<string>();
|
||||
var inParams = imgMan.GetMethodParameters(_wmiv2Namespace ? "GetVirtualHardDiskSettingData" : "GetVirtualHardDiskInfo");
|
||||
|
||||
foreach (var vhdPath in result)
|
||||
{
|
||||
inParams["Path"] = vhdPath;
|
||||
using (var outParams = imgMan.InvokeMethod(_wmiv2Namespace ? "GetVirtualHardDiskSettingData" : "GetVirtualHardDiskInfo", inParams, null))
|
||||
{
|
||||
if (outParams != null)
|
||||
{
|
||||
var doc = new System.Xml.XmlDocument();
|
||||
doc.LoadXml((string)outParams[_wmiv2Namespace ? "SettingData" : "Info"]);
|
||||
var node = doc.SelectSingleNode("//PROPERTY[@NAME = 'ParentPath']/VALUE/child::text()");
|
||||
|
||||
if (node != null && File.Exists(node.Value))
|
||||
ParentPaths.Add(node.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.AddRange(ParentPaths);
|
||||
}
|
||||
|
||||
return result.Distinct(Utility.Utility.ClientFilenameStringComparer).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Alphaleonis.Win32.Vss;
|
||||
using System.Management;
|
||||
|
||||
namespace Duplicati.Library.Snapshots
|
||||
{
|
||||
@@ -70,10 +71,6 @@ namespace Duplicati.Library.Snapshots
|
||||
/// The Hyper-V VSS Writer Guid
|
||||
/// </summary>
|
||||
private static readonly Guid HyperVWriterGuid = new Guid("66841cd4-6ded-4f4b-8f17-fd23f8ddc3de");
|
||||
/// <summary>
|
||||
/// A list off Hyper-V Machines that we will back up
|
||||
/// </summary>
|
||||
private readonly IDictionary<string, string> m_hyperVMachines = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new backup snapshot, using all the required disks
|
||||
@@ -105,62 +102,42 @@ namespace Duplicati.Library.Snapshots
|
||||
|
||||
m_sourcepaths = sourcepaths.Select(x => Directory.Exists(x) ? Utility.Utility.AppendDirSeparator(x) : x).ToList();
|
||||
|
||||
var requestedHyperVMs = new List<string>();
|
||||
if (options.ContainsKey("hyperv-backup-vm"))
|
||||
requestedHyperVMs = options["hyperv-backup-vm"].Split(';').Where(x => !string.IsNullOrWhiteSpace(x) && x.Trim().Length > 0).ToList();
|
||||
|
||||
//Check if we are backing up HyperV machines
|
||||
if (requestedHyperVMs.Count > 0)
|
||||
{
|
||||
var hyperVUtility = new HyperVUtility(requestedHyperVMs);
|
||||
m_hyperVMachines = hyperVUtility.GetHyperVMachines();
|
||||
|
||||
#region Testing Features
|
||||
if (!Utility.Utility.ParseBoolOption(options, "hyperv-backup-no-merge"))
|
||||
hyperVUtility.MergeVhd(requestedHyperVMs);
|
||||
|
||||
//Option: Create-new-vm-after-restore,
|
||||
//hyperVUtility.CreateHyperVMachine("TestVM", xmlpath);
|
||||
#endregion Testing Features
|
||||
}
|
||||
|
||||
m_backup.StartSnapshotSet();
|
||||
List<string> hypervPaths;
|
||||
|
||||
try
|
||||
{
|
||||
//Gather information on all Vss writers
|
||||
m_backup.GatherWriterMetadata();
|
||||
|
||||
//Update the sourcepaths if we're backing up Hyper-V Machines
|
||||
if (requestedHyperVMs.Count > 0)
|
||||
{
|
||||
//Find sourcepaths in Hyper-V WriterMetaData
|
||||
var m_backup_wmd = m_backup.WriterMetadata.FirstOrDefault(o => o.WriterId.Equals(HyperVWriterGuid));
|
||||
if (m_backup_wmd != null)
|
||||
foreach (var component in m_backup_wmd.Components)
|
||||
{
|
||||
//Cross reference the requested Hyper-V Machines to backup and add the sources
|
||||
if (!m_hyperVMachines.ContainsKey(component.ComponentName))
|
||||
continue;
|
||||
|
||||
foreach (var file in component.Files)
|
||||
{
|
||||
if (file.FileSpecification.Contains("*"))
|
||||
continue;
|
||||
m_sourcepaths.Add(file.Path + file.FileSpecification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hypervPaths = PrepareHyperVBackup(options, m_backup.WriterMetadata.FirstOrDefault(o => o.WriterId.Equals(HyperVWriterGuid)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_backup.FreeWriterMetadata();
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { m_backup.FreeWriterMetadata(); }
|
||||
catch { }
|
||||
|
||||
throw;
|
||||
}
|
||||
if (hypervPaths != null)
|
||||
m_sourcepaths.AddRange(hypervPaths);
|
||||
|
||||
//Sanity check for duplicate files/folders
|
||||
var pathDuplicates = m_sourcepaths.GroupBy(x => x, Utility.Utility.ClientFilenameStringComparer)
|
||||
.Where(g => g.Count() > 1).Select(y => y.Key).ToList();
|
||||
|
||||
foreach(var pathDuplicate in pathDuplicates)
|
||||
Logging.Log.WriteMessage(string.Format("Removing duplicate source: {0}", pathDuplicate), Logging.LogMessageType.Information);
|
||||
|
||||
if(pathDuplicates.Count > 0)
|
||||
m_sourcepaths = m_sourcepaths.Distinct(Utility.Utility.ClientFilenameStringComparer).OrderBy(a => a).ToList();
|
||||
|
||||
//Sanity check for multiple inclusions of the same files/folders
|
||||
var pathIncludedPaths = m_sourcepaths.Where(x => m_sourcepaths.Where(y => y != x).Any(z => x.StartsWith(z, Utility.Utility.ClientFilenameStringComparision))).ToList();
|
||||
|
||||
foreach (var pathIncluded in pathIncludedPaths)
|
||||
Logging.Log.WriteMessage(string.Format("Removing already included source: {0}", pathIncluded), Logging.LogMessageType.Information);
|
||||
|
||||
if (pathIncludedPaths.Count > 0)
|
||||
m_sourcepaths = m_sourcepaths.Except(pathIncludedPaths, Utility.Utility.ClientFilenameStringComparer).ToList();
|
||||
|
||||
m_backup.StartSnapshotSet();
|
||||
|
||||
//Figure out which volumes are in the set
|
||||
m_volumes = new Dictionary<string, Guid>(StringComparer.InvariantCultureIgnoreCase);
|
||||
@@ -216,6 +193,88 @@ namespace Duplicati.Library.Snapshots
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> PrepareHyperVBackup(Dictionary<string, string> options, IVssExamineWriterMetadata writerMetaData)
|
||||
{
|
||||
var resultPaths = new List<string>();
|
||||
var requestedHyperVMs = new List<string>();
|
||||
|
||||
if (options.ContainsKey("hyperv-backup-vm"))
|
||||
requestedHyperVMs = options["hyperv-backup-vm"].Split(';').Where(x => !string.IsNullOrWhiteSpace(x) && x.Trim().Length > 0).ToList();
|
||||
|
||||
if (requestedHyperVMs.Count == 0)
|
||||
return resultPaths;
|
||||
|
||||
Logging.Log.WriteMessage("Starting to gather Hyper-V information.", Logging.LogMessageType.Information);
|
||||
|
||||
var hyperVGuests = new HyperVUtility().GetHyperVGuests();
|
||||
|
||||
Logging.Log.WriteMessage(string.Format("Found {0} virtual machines on Hyper-V.", hyperVGuests.Count), Logging.LogMessageType.Information);
|
||||
|
||||
bool bNotFound = false;
|
||||
var notFoundVM = new List<string>();
|
||||
|
||||
foreach (var requestedHyperVM in requestedHyperVMs)
|
||||
{
|
||||
var foundVMs = hyperVGuests.FindAll(x => (string.Equals(requestedHyperVM, x.ID, StringComparison.CurrentCultureIgnoreCase)));
|
||||
|
||||
if (foundVMs.Count != 1)
|
||||
{
|
||||
bNotFound = true;
|
||||
notFoundVM.Add(requestedHyperVM);
|
||||
Logging.Log.WriteMessage(string.Format("Cannot find virtual machine with ID {0} on Hyper-V.", requestedHyperVM), Logging.LogMessageType.Error);
|
||||
}
|
||||
else
|
||||
Logging.Log.WriteMessage(string.Format("Found virtual machine {0} with ID {1} on Hyper-V.", foundVMs[0].Name, requestedHyperVM), Logging.LogMessageType.Information);
|
||||
}
|
||||
|
||||
if (bNotFound)
|
||||
throw new Exception(string.Format("Cannot find virtual machine with ID {0} on Hyper-V.", string.Join(", ", notFoundVM.ToArray())));
|
||||
|
||||
var productType = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem")
|
||||
.Get().OfType<ManagementObject>()
|
||||
.Select(o => (uint)o.GetPropertyValue("ProductType")).First();
|
||||
|
||||
if (productType != 1) // Hyper-V writer is present only on Server version of Windows
|
||||
{
|
||||
if (writerMetaData == null)
|
||||
throw new Exception("Microsoft Hyper-V VSS Writer not found - cannot backup Hyper-V machines.");
|
||||
|
||||
foreach (var component in writerMetaData.Components)
|
||||
if (requestedHyperVMs.Contains(component.ComponentName, StringComparer.CurrentCultureIgnoreCase))
|
||||
foreach (var file in component.Files)
|
||||
if (file.FileSpecification.Contains("*"))
|
||||
{
|
||||
if (Directory.Exists(Utility.Utility.AppendDirSeparator(file.Path)))
|
||||
{
|
||||
resultPaths.Add(Utility.Utility.AppendDirSeparator(file.Path));
|
||||
Logging.Log.WriteMessage(string.Format("For VM {0} - adding {1}.", component.ComponentName, Utility.Utility.AppendDirSeparator(file.Path)), Logging.LogMessageType.Profiling);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Exists(Path.Combine(file.Path, file.FileSpecification)))
|
||||
{
|
||||
resultPaths.Add(Path.Combine(file.Path, file.FileSpecification));
|
||||
Logging.Log.WriteMessage(string.Format("For VM {0} - adding {1}.", component.ComponentName, Path.Combine(file.Path, file.FileSpecification)), Logging.LogMessageType.Profiling);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Log.WriteMessage("This is client version of Windows. Hyper-V VSS writer is present only on Server version. Backup will continue, but will be crash consistent only in opposite to application consistent in Server version.", Logging.LogMessageType.Warning);
|
||||
|
||||
foreach (var hyperVGuest in hyperVGuests)
|
||||
if (requestedHyperVMs.Contains(hyperVGuest.ID, StringComparer.CurrentCultureIgnoreCase))
|
||||
foreach (var path in hyperVGuest.DataPaths)
|
||||
{
|
||||
resultPaths.Add(path);
|
||||
Logging.Log.WriteMessage(string.Format("For VM {0} - adding {1}.", hyperVGuest.ID, path), Logging.LogMessageType.Profiling);
|
||||
}
|
||||
}
|
||||
|
||||
return resultPaths.Distinct(Utility.Utility.ClientFilenameStringComparer).OrderBy(a => a).ToList();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Returns all files found in the shadow copies
|
||||
@@ -502,4 +561,4 @@ namespace Duplicati.Library.Snapshots
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ namespace Duplicati.Library.Utility
|
||||
return new TempFile(System.IO.Path.Combine(path, GenerateUniqueName()));
|
||||
}
|
||||
|
||||
public static TempFile CreateWritePrefix(string prefix)
|
||||
public static TempFile CreateWithPrefix(string prefix)
|
||||
{
|
||||
return new TempFile(System.IO.Path.Combine(TempFolder.SystemTempPath, prefix + GenerateUniqueName()));
|
||||
}
|
||||
|
||||
@@ -1095,16 +1095,25 @@ namespace Duplicati.Library.Utility
|
||||
/// </summary>
|
||||
/// <returns>The serialized object</returns>
|
||||
/// <param name="item">The object to serialize</param>
|
||||
public static void PrintSerializeObject(object item, System.IO.TextWriter writer, Func<System.Reflection.PropertyInfo, bool> filter = null)
|
||||
/// <param name="writer">The writer to write the results to</param>
|
||||
/// <param name="filter">A filter applied to properties to decide if they are omitted or not</param>
|
||||
/// <param name="recurseobjects">A value indicating if non-primitive values are recursed</param>
|
||||
/// <param name="indentation">The string indentation</param>
|
||||
/// <param name="visited">A lookup table with visited objects, used to avoid inifinite recursion</param>
|
||||
/// <param name="collectionlimit">The maximum number of items to report from an IEnumerable instance</param>
|
||||
public static void PrintSerializeObject(object item, System.IO.TextWriter writer, Func<System.Reflection.PropertyInfo, bool> filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 0, Dictionary<object, object> visited = null)
|
||||
{
|
||||
foreach(var p in item.GetType().GetProperties())
|
||||
visited = visited ?? new Dictionary<object, object>();
|
||||
var indentstring = new string(' ', indentation);
|
||||
|
||||
foreach (var p in item.GetType().GetProperties())
|
||||
{
|
||||
if (filter != null && !filter(p))
|
||||
continue;
|
||||
|
||||
if (p.PropertyType.IsPrimitive || p.PropertyType == typeof(string))
|
||||
if (p.PropertyType.IsPrimitive || p.PropertyType.IsEnum || p.PropertyType == typeof(string) || p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(TimeSpan))
|
||||
{
|
||||
writer.WriteLine("{0}: {1}", p.Name, p.GetValue(item, null));
|
||||
writer.WriteLine("{0}{1}: {2}", indentstring, p.Name, p.GetValue(item, null));
|
||||
}
|
||||
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(p.PropertyType))
|
||||
{
|
||||
@@ -1114,20 +1123,58 @@ namespace Duplicati.Library.Utility
|
||||
var enumerator = enumerable.GetEnumerator();
|
||||
if (enumerator != null)
|
||||
{
|
||||
writer.Write("{0}: [", p.Name);
|
||||
var remain = collectionlimit;
|
||||
|
||||
writer.Write("{0}{1}: [", indentstring, p.Name);
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
writer.WriteLine();
|
||||
|
||||
var extraindent = new string(' ', indentation + 4);
|
||||
|
||||
writer.Write(indentstring);
|
||||
writer.Write(extraindent);
|
||||
writer.Write(enumerator.Current);
|
||||
|
||||
remain--;
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
writer.Write(", ");
|
||||
writer.WriteLine(",");
|
||||
writer.Write(indentstring);
|
||||
writer.Write(extraindent);
|
||||
|
||||
if (remain == 0)
|
||||
{
|
||||
writer.Write("...");
|
||||
break;
|
||||
}
|
||||
writer.Write(enumerator.Current);
|
||||
|
||||
remain--;
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
|
||||
writer.Write(indentstring);
|
||||
}
|
||||
|
||||
writer.WriteLine("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (recurseobjects)
|
||||
{
|
||||
var value = p.GetValue(item, null);
|
||||
if (value == null)
|
||||
writer.WriteLine("{0}{1}: null", indentstring, p.Name);
|
||||
else if (!visited.ContainsKey(value))
|
||||
{
|
||||
writer.WriteLine("{0}{1}:", indentstring, p.Name);
|
||||
visited[value] = null;
|
||||
PrintSerializeObject(value, writer, filter, recurseobjects, indentation + 4, collectionlimit, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.Flush();
|
||||
}
|
||||
@@ -1137,11 +1184,15 @@ namespace Duplicati.Library.Utility
|
||||
/// </summary>
|
||||
/// <returns>The serialized object</returns>
|
||||
/// <param name="item">The object to serialize</param>
|
||||
public static StringBuilder PrintSerializeObject(object item, StringBuilder sb = null, Func<System.Reflection.PropertyInfo, bool> filter = null)
|
||||
/// <param name="filter">A filter applied to properties to decide if they are omitted or not</param>
|
||||
/// <param name="recurseobjects">A value indicating if non-primitive values are recursed</param>
|
||||
/// <param name="indentation">The string indentation</param>
|
||||
/// <param name="collectionlimit">The maximum number of items to report from an IEnumerable instance, set to zero or less for reporting all</param>
|
||||
public static StringBuilder PrintSerializeObject(object item, StringBuilder sb = null, Func<System.Reflection.PropertyInfo, bool> filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 10)
|
||||
{
|
||||
sb = sb ?? new StringBuilder();
|
||||
using(var sw = new System.IO.StringWriter(sb))
|
||||
PrintSerializeObject(item, sw);
|
||||
PrintSerializeObject(item, sw, filter, recurseobjects, indentation, collectionlimit);
|
||||
return sb;
|
||||
}
|
||||
|
||||
@@ -1193,4 +1244,4 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,6 +344,18 @@
|
||||
<Link>licenses\BouncyCastle\licensedata.json</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\angular-gettext\Homepage.txt">
|
||||
<Link>licenses\AngularGettext\Homepage.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\angular-gettext\License.txt">
|
||||
<Link>licenses\AngularGettext\License.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\..\thirdparty\angular-gettext\licensedata.json">
|
||||
<Link>licenses\AngularGettext\licensedata.json</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
@@ -374,5 +386,6 @@
|
||||
<Folder Include="licenses\System.Net.FtpClient\" />
|
||||
<Folder Include="licenses\MimeKit\" />
|
||||
<Folder Include="licenses\BouncyCastle\" />
|
||||
<Folder Include="licenses\AngularGettext\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,32 +1,13 @@
|
||||
We would like to thank all contributors who made Duplicati what it is today.
|
||||
|
||||
Software Development:
|
||||
Livio Bertacco
|
||||
Daniel Halan
|
||||
Birunthan Mohanathas
|
||||
James Colton
|
||||
Nicolas Hatier, Niversoft idées logicielles
|
||||
Tobias Käes
|
||||
Don Lockhart
|
||||
Sebastian Fahrenholz
|
||||
Malte Neumann
|
||||
Mostafa Aghajani
|
||||
Stefan Lück
|
||||
Tim Mylemans
|
||||
Xelio Cheong
|
||||
Michal Naiman
|
||||
We would like to thank all contributors who made Duplicati what it is today.
|
||||
These are the contributors to Duplicati 1.x that got it all started.
|
||||
These are the people who help others to get their backups up and running.
|
||||
These are the testers and translators.
|
||||
These are the people who give us hints how to improve Duplicati further.
|
||||
These are the people who just say "thank you".
|
||||
|
||||
The theme for Duplicati (ngax):
|
||||
Alexander Franzelin
|
||||
|
||||
Translations:
|
||||
Brazilian, pt-BR: Gabriell Hahmann
|
||||
Chinese, zh-CN: Born Kanas
|
||||
Danish, da-DK: Kenneth Skovhede
|
||||
French, fr-FR: Matthieu Weber
|
||||
German, de-DE: Ernst Mair, Mathias Block, DooM ElHado, Leo nowhere
|
||||
Italian, it-IT: Riccardo Stoppani
|
||||
Russian, ru-RU: Philip Torchinsky, Semonix
|
||||
Spanish, es-ES: JosepV
|
||||
You are the reason why this project gets ahead. You do not only contribute to the software but also keep us involved and motivated. Thank you!
|
||||
|
||||
A special thanks to the many users who let us know how they are using Duplicati, ask for new features, test development builds and thus help us improve Duplicati further.
|
||||
Due to their recent contributions to Duplicati 2.0 we would like to thank some people in particular:
|
||||
Stefan Lück (@footstark) for providing great performance optimizations to Duplicati 2.0
|
||||
Tobias Käs (@weltkante) for his C# library that implements LZMA2 and 7z
|
||||
Alexander Franzelin (@AlexFRAN) for the design of the Duplicati UI and the website
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
To remove the heavy load on SVN with the many translated files,
|
||||
the system is now changed to rely on a single CSV file pr. translation.
|
||||
|
||||
To work with it, use the CSV file, either here on from google docs:
|
||||
https://docs.google.com/leaf?id=0B9tKh3OEoqEqOTZmNWI1NWEtOTZkNi00OTk4LTkzMWEtOGI1M2UzMTc3NWYy
|
||||
|
||||
To generate the structure needed to build the translation, run:
|
||||
LocalizationTool.exe import fr-FR report.fr-FR.csv
|
||||
|
||||
This will generate the fr-FR folder, in which you can
|
||||
perform a UI customization (move buttons etc).
|
||||
|
||||
Once done, export the changes back to the CSV file:
|
||||
LocalizationTool.exe export fr-FR
|
||||
|
||||
To build the assemblies run:
|
||||
LocalizationTool.exe build
|
||||
@@ -1,62 +0,0 @@
|
||||
<root>
|
||||
<configuration name="Duplicati">
|
||||
<assembly name="Duplicati" folder="GUI" namespace="Duplicati.GUI">
|
||||
<exclude>Datamodel</exclude>
|
||||
</assembly>
|
||||
<assembly name="Duplicati.Datamodel" folder="GUI\Datamodel" />
|
||||
<assembly name="Duplicati.CommandLine" folder="CommandLine">
|
||||
<exclude>Decrypter</exclude>
|
||||
</assembly>
|
||||
|
||||
<assembly name="Duplicati.CommandLine.Decrypter" folder="CommandLine\Decrypter" />
|
||||
|
||||
<assembly name="Duplicati.Library.Utility" folder="Library\Utility" />
|
||||
<assembly name="Duplicati.Library.Main" folder="Library\Main" />
|
||||
<assembly name="Duplicati.Library.Encryption" folder="Library\Encryption" />
|
||||
<assembly name="Duplicati.Library.SharpExpect" folder="Library\SharpExpect" />
|
||||
<assembly name="Duplicati.Library.Compression" folder="Library\Compression" />
|
||||
<assembly name="Duplicati.Library.Encryption" folder="Library\Encryption" />
|
||||
<assembly name="Duplicati.Library.SharpRSync" folder="Library\SharpRSync" />
|
||||
<assembly name="Duplicati.Library.Shapshots" folder="Library\Snapshots" />
|
||||
|
||||
<assembly name="Duplicati.Library.Interface" folder="Library\Interface" />
|
||||
<assembly name="Duplicati.Library.DynamicLoader" folder="Library\DynamicLoader" />
|
||||
|
||||
<assembly name="Duplicati.Library.Modules.Builtin" folder="Library\Modules\Builtin" />
|
||||
|
||||
<assembly name="Duplicati.Library.Backend.FTP" folder="Library\Backend\FTP" namespace="Duplicati.Library.Backend" />
|
||||
<assembly name="Duplicati.Library.Backend.File" folder="Library\Backend\File" namespace="Duplicati.Library.Backend" />
|
||||
<assembly name="Duplicati.Library.Backend.S3" folder="Library\Backend\S3" namespace="Duplicati.Library.Backend" />
|
||||
<assembly name="Duplicati.Library.Backend.WEBDAV" folder="Library\Backend\WEBDAV" namespace="Duplicati.Library.Backend" />
|
||||
<assembly name="Duplicati.Library.Backend.CloudFiles" folder="Library\Backend\CloudFiles" namespace="Duplicati.Library.Backend" />
|
||||
<assembly name="Duplicati.Library.Backend.SSHv2" folder="Library\Backend\SSHv2" namespace="Duplicati.Library.Backend" />
|
||||
|
||||
<assembly name="Duplicati.Winforms.Controls" folder="Winforms\Controls" namespace="Duplicati.Winforms.Controls" />
|
||||
|
||||
<keyfile>..\GUI\Duplicati.snk</keyfile>
|
||||
<versionassembly>..\GUI\bin\Release\Duplicati.exe</versionassembly>
|
||||
<sourcefolder>..</sourcefolder>
|
||||
<outputfolder>compiled</outputfolder>
|
||||
<productname>Duplicati</productname>
|
||||
</configuration>
|
||||
|
||||
<!-- We need these without a keyfile, because they depend on unsigned dll's -->
|
||||
<configuration name="SSH">
|
||||
<assembly name="Duplicati.Library.Backend.TahoeLAFS" folder="Library\Backend\TahoeLAFS" namespace="Duplicati.Library.Backend" />
|
||||
|
||||
<versionassembly>..\GUI\bin\Release\Duplicati.exe</versionassembly>
|
||||
<sourcefolder>..</sourcefolder>
|
||||
<outputfolder>compiled</outputfolder>
|
||||
<productname>Duplicati</productname>
|
||||
</configuration>
|
||||
|
||||
<configuration name="WizardForm">
|
||||
<assembly name="System.Windows.Forms.Wizard" folder="WizardForm" />
|
||||
|
||||
<keyfile>..\..\WizardForm\WizardForm.snk</keyfile>
|
||||
<versionassembly>..\GUI\bin\Release\System.Windows.Forms.Wizard.dll</versionassembly>
|
||||
<sourcefolder>..\..</sourcefolder>
|
||||
<outputfolder>compiled</outputfolder>
|
||||
<productname>WizardForm</productname>
|
||||
</configuration>
|
||||
</root>
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<ignore>OK</ignore>
|
||||
<ignore>Pause</ignore>
|
||||
<ignore>Start</ignore>
|
||||
<ignore>Stop</ignore>
|
||||
<ignore>Log</ignore>
|
||||
<ignore>Log data</ignore>
|
||||
<ignore>Status</ignore>
|
||||
<ignore>Destination</ignore>
|
||||
<ignore>Pause backup</ignore>
|
||||
<ignore>Stop backup</ignore>
|
||||
<ignore>Duplicati status</ignore>
|
||||
<ignore>s</ignore>
|
||||
<ignore>Filter</ignore>
|
||||
<ignore>gb</ignore>
|
||||
<ignore>gb/s</ignore>
|
||||
<ignore>mb</ignore>
|
||||
<ignore>mb/s</ignore>
|
||||
<ignore>kb</ignore>
|
||||
<ignore>kb/s</ignore>
|
||||
<ignore>...</ignore>
|
||||
<ignore>(...)</ignore>
|
||||
<ignore>Normal</ignore>
|
||||
<ignore>backup</ignore>
|
||||
<ignore>--{0}: {1}</ignore>
|
||||
<ignore>Backup: {0}</ignore>
|
||||
<ignore>Interval</ignore>
|
||||
<ignore>Server</ignore>
|
||||
<ignore>Get start</ignore>
|
||||
<ignore>Put start</ignore>
|
||||
<ignore>List start</ignore>
|
||||
<ignore>AWS Access ID</ignore>
|
||||
<ignore>AWS Secret Key</ignore>
|
||||
<ignore>CloudFiles API Key</ignore>
|
||||
<ignore>{0:N} GB</ignore>
|
||||
<ignore>{0:N} KB</ignore>
|
||||
<ignore>{0:N} MB</ignore>
|
||||
<ignore>CloudFiles</ignore>
|
||||
<ignore>{0}\t{1}</ignore>
|
||||
<ignore>argument</ignore>
|
||||
<ignore>Dialog</ignore>
|
||||
<ignore>{0}</ignore>
|
||||
</root>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<ignore> Inc\t{0}\t{1}\t{2}</ignore>
|
||||
<ignore>(...)</ignore>
|
||||
<ignore>--{0}: {1}</ignore>
|
||||
<ignore>...</ignore>
|
||||
<ignore>1 Minute</ignore>
|
||||
<ignore>AWS Access ID</ignore>
|
||||
<ignore>Argument</ignore>
|
||||
<ignore>CloudFiles (Rackspace)</ignore>
|
||||
<ignore>CloudFiles AuthUrl</ignore>
|
||||
<ignore>Dialog</ignore>
|
||||
<ignore>Duplicati Setup, Version {0}</ignore>
|
||||
<ignore>Duplicati {0} - Status</ignore>
|
||||
<ignore>Filter</ignore>
|
||||
<ignore>Gb</ignore>
|
||||
<ignore>Gb/s</ignore>
|
||||
<ignore>Homepage</ignore>
|
||||
<ignore>MB</ignore>
|
||||
<ignore>MB/s</ignore>
|
||||
<ignore>Name</ignore>
|
||||
<ignore>Normal</ignore>
|
||||
<ignore>OK</ignore>
|
||||
<ignore>Pause</ignore>
|
||||
<ignore>Port (optional)</ignore>
|
||||
<ignore>Server</ignore>
|
||||
<ignore>Status</ignore>
|
||||
<ignore>Stop</ignore>
|
||||
<ignore>bytes</ignore>
|
||||
<ignore>bytes/s</ignore>
|
||||
<ignore>kb</ignore>
|
||||
<ignore>kb/s</ignore>
|
||||
<ignore>s</ignore>
|
||||
<ignore>{0:N} GB</ignore>
|
||||
<ignore>{0:N} MB</ignore>
|
||||
<ignore>{0:N} kB</ignore>
|
||||
<ignore>{0}</ignore>
|
||||
<ignore>{0} bytes</ignore>
|
||||
<ignore>{0}\t{1}</ignore>
|
||||
</root>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<ignore>OK</ignore>
|
||||
<ignore>...</ignore>
|
||||
<ignore>(...)</ignore>
|
||||
<ignore> Inc\t{0}\t{1}\t{2}</ignore>
|
||||
<ignore>--{0}: {1}</ignore>
|
||||
<ignore>1 minute</ignore>
|
||||
<ignore>15 minutes</ignore>
|
||||
<ignore>30 minutes</ignore>
|
||||
<ignore>5 minutes</ignore>
|
||||
<ignore>Action</ignore>
|
||||
<ignore>CloudFiles</ignore>
|
||||
<ignore>Destination</ignore>
|
||||
<ignore>Normal</ignore>
|
||||
<ignore>Options...</ignore>
|
||||
<ignore>Pause</ignore>
|
||||
<ignore>Simple <<<</ignore>
|
||||
<ignore>Source</ignore>
|
||||
<ignore>Stop</ignore>
|
||||
<ignore>argument</ignore>
|
||||
<ignore>s</ignore>
|
||||
<ignore>{0}</ignore>
|
||||
<ignore>{0}\t{1}</ignore>
|
||||
</root>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<ignore>{0}\t{1}</ignore>
|
||||
<ignore>Password</ignore>
|
||||
<ignore>OK</ignore>
|
||||
<ignore>...</ignore>
|
||||
<ignore>s</ignore>
|
||||
<ignore>(...)</ignore>
|
||||
<ignore>bytes</ignore>
|
||||
<ignore>bytes/s</ignore>
|
||||
<ignore>gb</ignore>
|
||||
<ignore>gb/s</ignore>
|
||||
<ignore>kb</ignore>
|
||||
<ignore>kb/s</ignore>
|
||||
<ignore>mb</ignore>
|
||||
<ignore>mb/s</ignore>
|
||||
<ignore>Stop</ignore>
|
||||
<ignore>backup</ignore>
|
||||
<ignore>{0}</ignore>
|
||||
<ignore>--{0}:{1}</ignore>
|
||||
<ignore>Backup: {0}</ignore>
|
||||
<ignore>CloudFiles (Rackspace)</ignore>
|
||||
<ignore>Server</ignore>
|
||||
<ignore>keyfile</ignore>
|
||||
<ignore>{0} bytes</ignore>
|
||||
<ignore>{0:N} GB</ignore>
|
||||
<ignore>{0:N} KB</ignore>
|
||||
<ignore>{0:N} MB</ignore>
|
||||
</root>
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AttKh3OEoqEqdC1EYmdEaFZwM3RfUy1fRFpzQ3VHeGc&single=true&gid=0&output=csv
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AttKh3OEoqEqdHNRWTVqd2pFaWZMQ3RNTXRvclhVZHc&single=true&gid=0&output=csv
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AttKh3OEoqEqdHVnOVlRV01aQ3o0OV9YMVdHNzJVb2c&single=true&gid=0&output=csv
|
||||