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; + } + } +}