Merge branch 'master' into concurrent_processing
# Conflicts: # Duplicati/Library/Main/Strings.cs
|
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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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,21 +82,76 @@ 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)
|
||||
if (m_options != null && !m_options.AllowSleep)
|
||||
{
|
||||
try
|
||||
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 (Exception ex)
|
||||
else if (Duplicati.Library.Utility.Utility.IsClientOSX)
|
||||
{
|
||||
Logging.Log.WriteMessage("Failed to set sleep prevention", Logging.LogMessageType.Warning, ex);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,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;
|
||||
|
||||
@@ -43,6 +43,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)
|
||||
@@ -52,6 +58,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);
|
||||
}
|
||||
|
||||
@@ -254,7 +254,6 @@ namespace Duplicati.Library.Main
|
||||
"vss-use-mapping",
|
||||
"usn-policy",
|
||||
"hyperv-backup-vm",
|
||||
"hyperv-backup-no-merge",
|
||||
"symlink-policy",
|
||||
"hardlink-policy",
|
||||
"exclude-files-attributes",
|
||||
@@ -450,7 +449,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"),
|
||||
@@ -465,7 +463,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),
|
||||
|
||||
@@ -36,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."); } }
|
||||
@@ -217,10 +218,8 @@ namespace Duplicati.Library.Main.Strings
|
||||
public static string ConcurrencyblockhashersLong { get { return LC.L(@"Use this option to set the number of processes that perform hashing of data."); } }
|
||||
public static string ConcurrencycompressorsShort{ get { return LC.L(@"Specify the number of concurrent compression processes"); } }
|
||||
public static string ConcurrencycompressorsLong { get { return LC.L(@"Use this option to set the number of processes that perform compression of output data."); } }
|
||||
public static string 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
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AttKh3OEoqEqdDE2dHhsNU0tNlJLUnpBRzVVRVhjbWc&single=true&gid=5&output=csv
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AttKh3OEoqEqdHFZTXdUcHJ0M0tWc3BSWE1BejhRZUE&single=true&gid=0&output=csv
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AilOWz75w8nHdGdFaEJlR3BGN0t5VlQyWlNhNlhvckE&single=true&gid=0&output=csv
|
||||
@@ -1 +0,0 @@
|
||||
https://docs.google.com/spreadsheet/pub?key=0AttKh3OEoqEqdFRDQ1hZaUhjai1feEVSeTVkNUdCVlE&single=true&gid=0&output=csv
|
||||