diff --git a/BeWoFileExporter/.vs/BeWoFileExporter/v16/.suo b/BeWoFileExporter/.vs/BeWoFileExporter/v16/.suo
new file mode 100644
index 0000000..c35473d
Binary files /dev/null and b/BeWoFileExporter/.vs/BeWoFileExporter/v16/.suo differ
diff --git a/BeWoFileExporter/App.config b/BeWoFileExporter/App.config
new file mode 100644
index 0000000..96b39ad
--- /dev/null
+++ b/BeWoFileExporter/App.config
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BeWoFileExporter/BeWoFileExporter.csproj b/BeWoFileExporter/BeWoFileExporter.csproj
new file mode 100644
index 0000000..0f82070
--- /dev/null
+++ b/BeWoFileExporter/BeWoFileExporter.csproj
@@ -0,0 +1,59 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {7352EC50-5812-44CE-B385-F52A3D907945}
+ Exe
+ BeWoFileExporter
+ BeWoFileExporter
+ v4.8
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ C:\Users\lyndo\BeWo\Lib\MySql.Data.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BeWoFileExporter/BeWoFileExporter.sln b/BeWoFileExporter/BeWoFileExporter.sln
new file mode 100644
index 0000000..c85af19
--- /dev/null
+++ b/BeWoFileExporter/BeWoFileExporter.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31019.35
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeWoFileExporter", "BeWoFileExporter.csproj", "{7352EC50-5812-44CE-B385-F52A3D907945}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7352EC50-5812-44CE-B385-F52A3D907945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7352EC50-5812-44CE-B385-F52A3D907945}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7352EC50-5812-44CE-B385-F52A3D907945}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7352EC50-5812-44CE-B385-F52A3D907945}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {EAD7FDD1-60AD-478D-8028-2832C296B010}
+ EndGlobalSection
+EndGlobal
diff --git a/BeWoFileExporter/Connection.cs b/BeWoFileExporter/Connection.cs
new file mode 100644
index 0000000..c31ed1a
--- /dev/null
+++ b/BeWoFileExporter/Connection.cs
@@ -0,0 +1,26 @@
+namespace BeWoFileExporter
+{
+ public class Connection
+ {
+ public string Server { get; set; }
+
+ public string Uid { get; set; }
+
+ public string Database { get; set; }
+
+ public string Password { get; set; }
+
+ public Connection(string server, string uid, string database, string password)
+ {
+ Server = server;
+ Uid = uid;
+ Database = database;
+ Password = password;
+ }
+
+ public string ToMySqlConnectionString()
+ {
+ return $"SERVER={Server};UID={Uid};PASSWORD={Password};DATABASE={Database};";
+ }
+ }
+}
diff --git a/BeWoFileExporter/FileAttachment.cs b/BeWoFileExporter/FileAttachment.cs
new file mode 100644
index 0000000..088331c
--- /dev/null
+++ b/BeWoFileExporter/FileAttachment.cs
@@ -0,0 +1,16 @@
+namespace BeWoFileExporter
+{
+ public class FileAttachment
+ {
+ public long Oid { get; set; }
+ public string Tenant { get; set; }
+ public byte[] Data { get; set; }
+
+ public FileAttachment(long oid, string tenant, byte[] data)
+ {
+ Oid = oid;
+ Tenant = tenant;
+ Data = data;
+ }
+ }
+}
diff --git a/BeWoFileExporter/Program.cs b/BeWoFileExporter/Program.cs
new file mode 100644
index 0000000..c5ab656
--- /dev/null
+++ b/BeWoFileExporter/Program.cs
@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Text;
+using MySql.Data.MySqlClient;
+
+namespace BeWoFileExporter
+{
+ internal class Program
+ {
+ private static MySqlConnection _MySqlConnection;
+
+ private static string BwpRootFileDirectory { get; set; }
+
+ ///
+ /// Sucht alle Datenbanken nach Blobs in der Tabelle FileAttachment ab, exportiert diese in das BWP-File-Verzeichnis und löscht den Blob aus der Datenbank.
+ ///
+ /// Enthält Kundennummern. Wenn keine Übergeben wird, werden alle Datenbanken auf dem Server durchgegangen.
+ /// Hat die Position 0 den Wert 'm', wird nach der Kundennummer gefragt.
+ private static void Main(string[] args)
+ {
+ try
+ {
+ var selectedTenants = new List();
+
+ BwpRootFileDirectory = Utils.GetFileRootDirectoryPath();
+
+ var configPath = Utils.GetMultitenancyDirectoryPath();
+
+//#if DEBUG
+// BwpRootFileDirectory = @"C:\BWP-Files";
+// configPath = @"C:\Users\lyndo\BeWo\Host\Multitenancy";
+//#endif
+
+ // Prüfen, ob die Kundennummer abgefragt werden soll:
+ if(args.Length > 0 && args[0] == "m")
+ {
+ string tenants = null;
+
+ while(tenants == null)
+ {
+ Console.Write("Kundennummer (mehrere durch Komma trennen): ");
+ tenants = Console.ReadLine();
+
+ if(tenants != null)
+ {
+ var tenantList = tenants.Split(',');
+ selectedTenants = tenantList.ToList().Select(s => s.Trim()).ToList();
+ }
+ }
+ }
+ else
+ {
+ if(args.Length > 0)
+ {
+ // Kann durch Kommata getrennt sein
+ var tenants = args[0];
+ if(tenants != null)
+ {
+ var tenantList = tenants.Split(',');
+ selectedTenants = tenantList.ToList();
+ }
+ }
+ }
+
+ var configFileNames = selectedTenants.Select(tenant => $"{tenant}.config").ToList();
+
+ var configFiles = Directory.EnumerateFiles(configPath).Where(filePath =>
+ {
+ var fileName = Path.GetFileName(filePath);
+
+ var isSelected = !configFileNames.Any();
+ if(configFileNames.Count > 0)
+ {
+ isSelected = configFileNames.Contains(fileName);
+ }
+
+ return filePath.EndsWith(".config") && isSelected;
+ }).ToList();
+
+ var connections = GetConnectionObjects(configFiles).ToList();
+
+ var datenbankplural = connections.Count != 1 ? "en" : string.Empty;
+
+ Console.WriteLine($"Exportiere Dateien aus {connections.Count} Datenbank{datenbankplural}\n");
+
+ foreach(var connection in connections)
+ {
+ _MySqlConnection = new MySqlConnection(connection.ToMySqlConnectionString());
+
+ ExtractFiles(connection);
+ }
+ }
+ catch(Exception exception)
+ {
+ Console.WriteLine(exception);
+ }
+ finally
+ {
+ if(_MySqlConnection != null)
+ {
+ CloseConnection();
+ }
+
+ Console.Read();
+ }
+ }
+
+ private static bool OpenConnection()
+ {
+ try
+ {
+ _MySqlConnection.Open();
+ return true;
+ }
+ catch(MySqlException exception)
+ {
+ Console.WriteLine($"Fehler: {exception}\n");
+
+ return false;
+ }
+ }
+
+ private static IEnumerable GetConnectionObjects(IEnumerable configFiles)
+ {
+ var connections = new List();
+
+ foreach(var configFile in configFiles)
+ {
+ var connectionString = Utils.GetConnectionStringFromConfigFile(configFile)?.Trim();
+
+ if(connectionString != null &&
+ connectionString.Contains("Server=") &&
+ connectionString.Contains("Password=") &&
+ connectionString.Contains("User ID=") &&
+ connectionString.Contains("Initial Catalog="))
+ {
+ var server = Utils.GetConnectionStringValue("Server", connectionString);
+ var password = Utils.GetConnectionStringValue("Password", connectionString);
+ var uid = Utils.GetConnectionStringValue("User ID", connectionString);
+ var database = Utils.GetConnectionStringValue("Initial Catalog", connectionString);
+
+ connections.Add(new Connection(server, uid, database, password));
+ }
+ }
+
+ return connections;
+ }
+
+ private static void ExtractFiles(Connection connection)
+ {
+ _MySqlConnection = new MySqlConnection(connection.ToMySqlConnectionString());
+
+ try
+ {
+ if(OpenConnection())
+ {
+ LoadFileAttachmentData(connection.Database);
+ }
+ }
+ catch(Exception exception)
+ {
+ Console.WriteLine(exception+ "\n");
+ }
+ finally
+ {
+ CloseConnection();
+ }
+ }
+
+ private static void LoadFileAttachmentData(string connectionDatabase)
+ {
+ const string queryString = "SELECT Oid, Data FROM fileattachment WHERE Data IS NOT NULL;";
+
+ if(_MySqlConnection.State == ConnectionState.Open)
+ {
+ var mySqlCommand = new MySqlCommand(queryString, _MySqlConnection);
+
+ var reader = mySqlCommand.ExecuteReader();
+
+ var fileAttachments = new List();
+
+ while(reader.Read())
+ {
+ var data = reader["Data"];
+
+ var oid = reader["Oid"];
+
+ if(data == null || oid == null)
+ {
+ continue;
+ }
+
+ if(data is byte[] byteArray && long.TryParse(oid.ToString(), out var fileAttachmentOid))
+ {
+ var tenant = connectionDatabase;
+
+ fileAttachments.Add(new FileAttachment(fileAttachmentOid, tenant, byteArray));
+ }
+ }
+
+ reader.Close();
+
+ var fileCount = fileAttachments.Count;
+
+ var word = fileCount > 1 ?
+ $"{fileCount} Dateien werden exportiert" : fileCount == 0 ?
+ "Keine Dateien exportiert" :
+ "Eine Datei wird exportiert";
+
+ Console.WriteLine($"{connectionDatabase}: {word}\n");
+
+ SaveDataToDirectory(fileAttachments, connectionDatabase);
+ }
+ }
+
+ private static void SaveDataToDirectory(IReadOnlyCollection fileAttachments, string connectionDatabase)
+ {
+ if(_MySqlConnection.State == ConnectionState.Open && fileAttachments?.Count > 0)
+ {
+ Utils.CheckForFileRootDirectory(connectionDatabase, BwpRootFileDirectory);
+
+ var updateStringBuilder = new StringBuilder("UPDATE fileattachment SET Data = NULL WHERE Oid IN (");
+
+ var oidString = string.Empty;
+
+ foreach(var fileAttachment in fileAttachments)
+ {
+ File.WriteAllBytes(Utils.GetFilePath(connectionDatabase, fileAttachment.Oid.ToString()), fileAttachment.Data);
+ oidString += fileAttachment.Oid + ",";
+ }
+
+ updateStringBuilder.Append(oidString.TrimEnd(','));
+
+ updateStringBuilder.Append(");");
+
+ var updateCommand = new MySqlCommand(updateStringBuilder.ToString()) { Connection = _MySqlConnection };
+
+ updateCommand.ExecuteNonQuery();
+ }
+ }
+
+ private static void CloseConnection()
+ {
+ try
+ {
+ _MySqlConnection.Close();
+ }
+ catch(MySqlException exception)
+ {
+ Console.WriteLine(exception+ "\n");
+ }
+ }
+ }
+}
diff --git a/BeWoFileExporter/Properties/AssemblyInfo.cs b/BeWoFileExporter/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..97a6c2e
--- /dev/null
+++ b/BeWoFileExporter/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+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("BeWoFileExporter")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("BeWoFileExporter")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[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("7352ec50-5812-44ce-b385-f52a3d907945")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/BeWoFileExporter/Utils.cs b/BeWoFileExporter/Utils.cs
new file mode 100644
index 0000000..afc0af8
--- /dev/null
+++ b/BeWoFileExporter/Utils.cs
@@ -0,0 +1,103 @@
+using System.Configuration;
+using System.IO;
+using System.Xml;
+
+namespace BeWoFileExporter
+{
+ public static class Utils
+ {
+ public static string GetFileRootDirectoryPath()
+ {
+ var fileRootDirectoryPath = new AppSettingsReader().GetValue("FileRootDirectory", typeof(string)).ToString();
+
+ return fileRootDirectoryPath;
+ }
+
+ public static string GetMultitenancyDirectoryPath()
+ {
+ var multitenancyDirectoryPath = new AppSettingsReader().GetValue("MultitenancyDirectory", typeof(string)).ToString();
+
+ if(!Directory.Exists(multitenancyDirectoryPath))
+ {
+ Directory.CreateDirectory(multitenancyDirectoryPath);
+ }
+
+ return multitenancyDirectoryPath;
+ }
+
+ public static void CheckForFileRootDirectory(string tenant, string fileRootDirectoryPath)
+ {
+ var tenantPath = Path.Combine(GetFileRootDirectoryPath(), tenant);
+
+ if(!Directory.Exists(fileRootDirectoryPath))
+ {
+ Directory.CreateDirectory(fileRootDirectoryPath);
+ }
+
+ if(!Directory.Exists(tenantPath))
+ {
+ Directory.CreateDirectory(tenantPath);
+ }
+ }
+
+ public static string GetFilePath(string tenant, string oid)
+ {
+ var rootDirectory = GetFileRootDirectoryPath();
+
+ return Path.Combine(rootDirectory, Path.Combine(tenant, oid));
+ }
+
+ public static string GetConnectionStringValue(string variableName, string connectionString)
+ {
+ if(connectionString != null && connectionString.Contains(";"))
+ {
+ var split = connectionString.Split(';');
+
+ foreach(var keyValuePairString in split)
+ {
+ var keyToValue = keyValuePairString.Split('=');
+
+ if(keyToValue.Length == 2 && keyToValue[0].Trim() == variableName)
+ {
+ return keyToValue[1];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public static string GetConnectionStringFromConfigFile(string pathToConfigFile)
+ {
+ var xmlDocument = new XmlDocument();
+ xmlDocument.Load(pathToConfigFile);
+
+ var configuration = xmlDocument.ChildNodes.Item(1);
+
+ if(configuration?.Name == "configuration")
+ {
+ var hibernateConfiguration = configuration.ChildNodes.Item(1);
+
+ if(hibernateConfiguration?.Name == "hibernate-configuration")
+ {
+ var sessionFactory = hibernateConfiguration.ChildNodes.Item(0);
+
+ if(sessionFactory?.Name == "session-factory")
+ {
+ var properties = sessionFactory.ChildNodes;
+
+ foreach(XmlNode property in properties)
+ {
+ if(property?.Attributes?["name"]?.Value == "connection.connection_string")
+ {
+ return property.InnerText;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+}