Modules/Filesystem/FileContainer.vb
2018-11-16 11:51:24 +01:00

285 lines
9.7 KiB
VB.net

Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Imports DigitalData.Modules.Logging
''' <module>FileContainer</module>
''' <version>0.0.0.1</version>
''' <date>16.11.2018</date>
''' <summary>
''' File Container for securely saving files
''' </summary>
''' <dependencies>
''' NLog, >= 4.5.8
''' </dependencies>
''' <params>
''' LogConfig, DigitalData.Module.Logging.LogConfig
''' A LogConfig object
''' </params>
''' <props>
''' </props>
''' <example>
''' </example>
''' <remarks>
''' </remarks>
Public Class FileContainer
Private _files As List(Of FileEntry)
Private _crypto As Encryption
Private _compression As Compression
Private _formatter As BinaryFormatter
Private _containerId As Guid
Private _inner As FileContainerInner
Private _logger As Logger
Private _logConfig As LogConfig
<Serializable>
Public Class FileEntry
Public FileId As String
Public Contents As Byte()
Public CreatedAt As DateTime
Public UpdatedAt As DateTime
End Class
<Serializable>
Public Class FileContainerInner
Public Files As List(Of FileEntry)
Public ContainerID As Guid
Public CreatedAt As DateTime
Public UpdatedAt As DateTime
End Class
Public ReadOnly Property ContainerId As String
Get
If _inner Is Nothing Then
Return Nothing
End If
Return _inner.ContainerID.ToString
End Get
End Property
''' <summary>
''' Gibt eine Auflistung der Dateien in Container zurück.
''' </summary>
''' <returns>Eine Liste von Objekten der Klasse FileContainer.FileEntry</returns>
Public ReadOnly Property Files As List(Of FileEntry)
Get
If _inner Is Nothing Then
Return Nothing
End If
Return _inner.Files
End Get
End Property
''' <summary>
''' Erstellt eine Representation eines Datei Containers, der mit dem angegebenen Passwort geschützt ist.
''' Ist für das speichern von neuen Containern und für das laden vorhandener Container nötig.
''' </summary>
''' <param name="Password">Das Passwort, mit dem der Container ver- und entschlüsselt wird</param>
''' <example>
''' Dim password = "meinpasswort"
''' Dim container = new FileContainer(password)
''' </example>
Public Sub New(LogConfig As LogConfig, Password As String)
_logger = LogConfig.GetLogger()
_crypto = New Encryption(LogConfig, Password)
_compression = New Compression(LogConfig)
_formatter = New BinaryFormatter
_inner = New FileContainerInner() With {
.Files = New List(Of FileEntry),
.ContainerID = Guid.NewGuid(),
.CreatedAt = New DateTime()
}
End Sub
''' <summary>
''' Speichert einen Datei Container am angegebenen Pfad.
''' Davor werden die Dateien in Files komprimiert und dann verschlüsselt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, unter dem der Container abgelegt wird.</param>
''' <see cref="Files"/>
''' <example>
''' container.AddFile("datei1.txt")
''' container.AddFile("datei2.pdf")
''' container.Save("container.enc")
''' </example>
Public Sub Save(Path As String)
Try
' 1. Serialize
Dim oFileContainerInner As Byte() = Serialize(_inner)
' 2. Compress
Dim oFileContainerCompressed As Byte() = _compression.Compress(oFileContainerInner)
' 3. Encrypt
Dim oFileContainerEncrypted As Byte() = _crypto.Encrypt(oFileContainerCompressed)
BytesToFile(oFileContainerEncrypted, Path)
Catch ex As Exception
_logger.Error(ex)
Throw ex
End Try
End Sub
''' <summary>
''' Speichert einen Datei Container am angegebenen Pfad.
''' Davor werden die Dateien in Files komprimiert und dann verschlüsselt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, unter dem der Container abgelegt wird.</param>
''' <example>
''' Public Async Sub foobar()
''' ...
''' container.AddFile("datei1.txt")
''' container.AddFile("datei2.pdf")
''' await container.SaveAsync("container.enc")
''' ...
''' End Sub
''' </example>
Public Async Function SaveAsync(Path As String) As Task
Try
' 1. Serialize
Dim oFileContainerInner As Byte() = Serialize(_inner)
' 2. Compress
Dim oFileContainerCompressed As Byte() = Await _compression.CompressAsync(oFileContainerInner)
'Dim bFilesCompressed = bFiles
' 3. Encrypt
Dim oFileContainerEncrypted As Byte() = Await _crypto.EncryptAsync(oFileContainerCompressed)
BytesToFileAsync(oFileContainerEncrypted, Path)
Catch ex As Exception
_logger.Error(ex)
Throw ex
End Try
End Function
''' <summary>
''' Lädt einen Datei Container aus dem angegebenen Pfad.
''' Anschließend werden die enthaltenen Dateien entschlüsselt, dekomprimiert und in Files abgelegt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, von dem der Container geladen wird.</param>
''' <see cref="Files"/>
''' <example>
''' container.Load("container.enc")
''' Dim files As List(Of FileContainer.FileEntry) = container.Files
''' </example>
Public Sub Load(Path As String)
Try
Dim oContainerInnerEncrypted As Byte() = FileToBytes(Path)
' 1. Decrypt
Dim oFileContainerCompressed As Byte() = _crypto.Decrypt(oContainerInnerEncrypted)
' 2. Decompress
Dim oFileContainerInner As Byte() = _compression.Decompress(oFileContainerCompressed)
' 3. Deserialize
_inner = Deserialize(oFileContainerInner)
Catch ex As Exception
_logger.Error(ex)
Throw ex
End Try
End Sub
''' <summary>
''' Lädt einen Datei Container aus dem angegebenen Pfad.
''' Anschließend werden die enthaltenen Dateien entschlüsselt, dekomprimiert und in Files abgelegt.
''' </summary>
''' <param name="Path">Der Pfad mit Dateiname, von dem der Container geladen wird.</param>
''' <example>
''' Public Async Sub foobar()
''' ...
''' await container.LoadAsync("container.enc")
''' Dim files As List(Of FileContainer.FileEntry) = container.Files
''' ...
''' End Sub
''' </example>
Public Async Function LoadAsync(Path As String) As Task
Try
Dim oContainerInnerEncrypted As Byte() = Await FileToBytesAsync(Path)
' 1. Decrypt
Dim oFileContainerCompressed As Byte() = Await _crypto.DecryptAsync(oContainerInnerEncrypted)
' 2. Decompress
Dim oFileContainerInner As Byte() = Await _compression.DecompressAsync(oFileContainerCompressed)
' 3. Deserialize
_inner = Deserialize(oFileContainerInner)
Catch ex As Exception
_logger.Error(ex)
Throw ex
End Try
End Function
''' <summary>
''' Fügt einem Datei Container eine neue Datei hinzu
''' </summary>
''' <param name="FilePath">Der Pfad zur Datei, die im Container gespeichert werden soll</param>
''' <exception cref="FileNotFoundException" />
Public Sub AddFile(FilePath As String)
Try
Dim oFileInfo As New FileInfo(FilePath)
If oFileInfo.Exists = False Then
Throw New FileNotFoundException($"{FilePath} is not a valid path.")
End If
AddFile(IO.File.ReadAllBytes(FilePath))
Catch ex As Exception
_logger.Error(ex)
Throw ex
End Try
End Sub
Public Sub AddFile(FileContents As Byte())
Try
Dim oFileEntry As New FileEntry With {
.Contents = FileContents,
.FileId = Guid.NewGuid().ToString()
}
_inner.Files.Add(oFileEntry)
Catch ex As Exception
_logger.Error(ex)
Throw ex
End Try
End Sub
Public Function GetFile(FileId As String) As FileEntry
Return _inner.Files.Where(Function(f) f.FileId = FileId).FirstOrDefault()
End Function
Private Function Serialize(InnerData As FileContainerInner) As Byte()
Using oStream As New MemoryStream
_formatter.Serialize(oStream, InnerData)
Return oStream.ToArray()
End Using
End Function
Private Function Deserialize(InnerData As Byte()) As FileContainerInner
Using oStream As New MemoryStream(InnerData)
Dim oInner = TryCast(_formatter.Deserialize(oStream), FileContainerInner)
Return oInner
End Using
End Function
Private Sub BytesToFile(Data As Byte(), FilePath As String)
IO.File.WriteAllBytes(FilePath, Data)
End Sub
Private Function FileToBytes(FilePath As String) As Byte()
Return IO.File.ReadAllBytes(FilePath)
End Function
Private Async Function FileToBytesAsync(FilePath As String) As Task(Of Byte())
Using oFileStream = New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, True)
Dim oBuffer As Byte() = New Byte(oFileStream.Length - 1) {}
Await oFileStream.ReadAsync(oBuffer, 0, oFileStream.Length)
oFileStream.Close()
Return oBuffer
End Using
End Function
Private Async Sub BytesToFileAsync(Data As Byte(), FilePath As String)
Using oSourceStream As New FileStream(FilePath, FileMode.Append, FileAccess.Write, FileShare.None, bufferSize:=4096, useAsync:=True)
Await oSourceStream.WriteAsync(Data, 0, Data.Length)
End Using
End Sub
End Class