diff --git a/Base/Base.vbproj b/Base/Base.vbproj
index 30cb4ceb..170d8af0 100644
--- a/Base/Base.vbproj
+++ b/Base/Base.vbproj
@@ -83,6 +83,13 @@
+
+
+
+
+
+
+
@@ -139,8 +146,6 @@
Logging
-
-
-
+
\ No newline at end of file
diff --git a/Base/FileContainer/DocumentObject.vb b/Base/FileContainer/DocumentObject.vb
new file mode 100644
index 00000000..c9da13f0
--- /dev/null
+++ b/Base/FileContainer/DocumentObject.vb
@@ -0,0 +1,18 @@
+Imports System.Runtime.Serialization
+
+
+Public Class DocumentObject
+
+
+ Public ReadOnly FileName As String
+
+ Public ReadOnly ContainerId As String
+
+ Public ReadOnly DocumentId As Int64
+
+ Public Sub New(ContainerId As String, DocumentId As Int64, FileName As String)
+ Me.ContainerId = ContainerId
+ Me.DocumentId = DocumentId
+ Me.FileName = FileName
+ End Sub
+End Class
diff --git a/Base/FileContainer/FileContainer.vb b/Base/FileContainer/FileContainer.vb
new file mode 100644
index 00000000..54d6ce4a
--- /dev/null
+++ b/Base/FileContainer/FileContainer.vb
@@ -0,0 +1,193 @@
+Imports System.IO
+Imports DigitalData.Modules.Logging
+Imports DigitalData.Modules.Encryption
+Imports ProtoBuf
+
+''' FileContainer
+''' 0.0.0.2
+''' 21.11.2018
+'''
+''' File Container for securely saving files
+'''
+'''
+''' NLog, >= 4.5.8
+'''
+'''
+''' LogConfig, DigitalData.Module.Logging.LogConfig
+''' A LogConfig object
+''' Password, String
+''' The Password to Encrypt
+''' Path, String
+''' The Path to save/load the container
+'''
+'''
+''' dim oContainer = Container.Create(logConfig, "pass", "E:\some.container")
+''' dim oContainer = Container.Load(logConfig, "pass", "E:\some.container")
+'''
+''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
+''' oContainer.Save()
+'''
+''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
+''' oContainer.Contents = oSomeData
+''' oContainer.Save()
+'''
+''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
+''' oContainer.Load()
+''' dim oContents = oContainer.Contents
+'''
+''' dim oContainer = new Container(logConfig, "pass", "E:\some.container")
+''' oContainer.Load()
+''' oContainer.Contents = oSomeOtherData
+''' oContainer.Save()
+''' oContainer.SaveAs("E:\some2.container")
+'''
+Public Class FileContainer
+ Private _crypto As Encryption.Encryption
+ Private _compression As Compression
+ Private _inner As FileContainerInner
+ Private _logger As Logger
+ Private _logConfig As LogConfig
+ Private _path As String
+
+ Public Property Contents As Byte()
+ Get
+ Return _inner.Contents
+ End Get
+ Set(value As Byte())
+ _inner.Contents = value
+ End Set
+ End Property
+ Public ReadOnly Property ContainerId As String
+ Get
+ Return _inner.FileId
+ End Get
+ End Property
+ Public ReadOnly Property CreatedAt As String
+ Get
+ Return _inner.CreatedAt
+ End Get
+ End Property
+ Public ReadOnly Property UpdatedAt As String
+ Get
+ Return _inner.UpdatedAt
+ End Get
+ End Property
+
+ Public Shared Function Create(LogConfig As LogConfig, Password As String) As FileContainer
+ Dim oContainer = New FileContainer(LogConfig, Password)
+ Return oContainer
+ End Function
+
+ Public Shared Function Load(LogConfig As LogConfig, Password As String, Path As String) As FileContainer
+ Dim oContainer = New FileContainer(LogConfig, Password, Path)
+ oContainer.Load()
+ Return oContainer
+ End Function
+
+ Public Sub New(LogConfig As LogConfig, Password As String)
+ _logger = LogConfig.GetLogger()
+ _crypto = New Encryption.Encryption(LogConfig, Password)
+ _compression = New Compression(LogConfig)
+ _inner = New FileContainerInner()
+ End Sub
+
+ Public Sub New(LogConfig As LogConfig, Password As String, Path As String)
+ MyClass.New(LogConfig, Password)
+ _path = Path
+ End Sub
+
+ Public Sub SetFile(Contents As Byte(), FileName As String)
+ _inner.Contents = Contents
+ _inner.UpdatedAt = Date.Now
+ _inner.FileName = FileName
+ End Sub
+
+ Public Function GetFile() As FileContainerInner
+ Return _inner
+ End Function
+
+ Public Sub Save()
+ If IsNothing(_path) Then
+ Throw New ArgumentException("Path not set")
+ End If
+
+ SaveAs(_path)
+ End Sub
+
+ Public Sub SaveAs(Path As String)
+ Try
+ WriteBytesToFile(TransformToBytes(_inner), Path)
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Sub
+
+ Public Sub Load()
+ If IsNothing(_path) Then
+ Throw New ArgumentException("Path not set")
+ End If
+
+ LoadFrom(_path)
+ End Sub
+
+ Public Sub LoadFrom(Path As String)
+ Try
+ _inner = TransformToObject(ReadBytesFromFile(_path))
+ Catch ex As Exception
+ _logger.Error(ex)
+ Throw ex
+ End Try
+ End Sub
+
+ Private Function TransformToBytes([Object] As FileContainerInner) As Byte()
+ Dim oBytes = Serialize([Object])
+ Dim oCompressed = _compression.Compress(oBytes)
+ Dim oEncrypted = _crypto.Encrypt(oCompressed)
+ Return oEncrypted
+ End Function
+
+ Private Function TransformToObject(Bytes As Byte()) As FileContainerInner
+ Dim oDecrypted = _crypto.Decrypt(Bytes)
+ Dim oDecompressed = _compression.Decompress(oDecrypted)
+ Dim oObject = Deserialize(oDecompressed)
+ Return oObject
+ End Function
+
+ Private Function Serialize(InnerData As FileContainerInner) As Byte()
+ Dim oBinaryData As Byte()
+
+ Using oStream As New MemoryStream
+ Serializer.Serialize(oStream, InnerData)
+ oBinaryData = oStream.ToArray()
+ End Using
+
+ Return oBinaryData
+ End Function
+
+ Private Function Deserialize(InnerData As Byte()) As FileContainerInner
+ Dim oObject As FileContainerInner
+
+ Using oStream As New MemoryStream(InnerData)
+ oObject = Serializer.Deserialize(Of FileContainerInner)(oStream)
+ End Using
+
+ Return oObject
+ End Function
+
+ Private Sub WriteBytesToFile(Data As Byte(), FilePath As String)
+ Using oSourceStream As New FileStream(FilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None)
+ oSourceStream.Write(Data, 0, Data.Length)
+ oSourceStream.Flush()
+ End Using
+ End Sub
+
+ Private Function ReadBytesFromFile(FilePath As String) As Byte()
+ Using oFileStream = New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096)
+ Dim oBuffer As Byte() = New Byte(oFileStream.Length - 1) {}
+ oFileStream.Read(oBuffer, 0, oFileStream.Length)
+ oFileStream.Close()
+ Return oBuffer
+ End Using
+ End Function
+End Class
diff --git a/Base/FileContainer/FileContainerInner.vb b/Base/FileContainer/FileContainerInner.vb
new file mode 100644
index 00000000..fc787ce0
--- /dev/null
+++ b/Base/FileContainer/FileContainerInner.vb
@@ -0,0 +1,23 @@
+Imports ProtoBuf
+
+
+
+Public Class FileContainerInner
+
+ Public FileId As String
+
+ Public Contents As Byte()
+
+ Public CreatedAt As DateTime
+
+ Public UpdatedAt As DateTime
+
+ Public FileName As String
+
+ Public Sub New()
+ FileId = Guid.NewGuid().ToString
+ CreatedAt = Date.Now
+ UpdatedAt = Date.Now
+ End Sub
+
+End Class
\ No newline at end of file
diff --git a/Base/FileWatcher/FileWatcher.vb b/Base/FileWatcher/FileWatcher.vb
new file mode 100644
index 00000000..6f82047a
--- /dev/null
+++ b/Base/FileWatcher/FileWatcher.vb
@@ -0,0 +1,132 @@
+Imports System.IO
+Imports DigitalData.Modules.Filesystem
+Imports DigitalData.Modules.Filesystem.FileWatcherFilters
+Imports DigitalData.Modules.Logging
+
+
+Public Class FileWatcher
+ ' Internals
+ Private ReadOnly _Logger As Logger
+ Private ReadOnly _Watchers As List(Of FileSystemWatcher)
+ Private ReadOnly _Files As Dictionary(Of String, FileWatcherProperties)
+ Private ReadOnly _Filters As List(Of BaseFileFilter)
+
+ ' Options
+ Private _Path As String
+
+ ' Public Events
+ Public Event FileSaved(ByVal FullName As String, ByVal IsSpecial As Boolean)
+
+ Public Sub New(LogConfig As LogConfig, Path As String, Optional Filters As List(Of BaseFileFilter) = Nothing)
+ _Logger = LogConfig.GetLogger()
+ _Files = New Dictionary(Of String, FileWatcherProperties)
+ _Watchers = New List(Of FileSystemWatcher)
+ _Filters = IIf(IsNothing(Filters), GetDefaultFilters(), Filters)
+ _Path = Path
+
+ For Each oFilePath In Directory.EnumerateFiles(_Path)
+ Try
+ If IO.File.Exists(oFilePath) Then
+ _Files.Add(oFilePath, New FileWatcherProperties With {
+ .CreatedAt = DateTime.Now,
+ .ChangedAt = Nothing
+ })
+ End If
+ Catch ex As Exception
+ _Logger.Error(ex)
+ _Logger.Warn("File {0} cannot be watched!")
+ End Try
+ Next
+ End Sub
+
+ Public Sub Add(Filter As String)
+ _Watchers.Add(CreateWatcher(Filter))
+ End Sub
+
+ Public Sub Start()
+ For Each oWatcher In _Watchers
+ oWatcher.EnableRaisingEvents = True
+ Next
+ End Sub
+
+ Public Sub [Stop]()
+ For Each oWatcher In _Watchers
+ If Not IsNothing(oWatcher) Then
+ oWatcher.EnableRaisingEvents = False
+ oWatcher.Dispose()
+ End If
+ Next
+ End Sub
+
+ Private Function GetDefaultFilters()
+ Return New List(Of BaseFileFilter) From {
+ New TempFileFilter,
+ New OfficeFileFilter
+ }
+ End Function
+
+ Private Function CreateWatcher(Filter As String)
+ Dim oWatcher = New FileSystemWatcher() With {
+ .Path = _Path,
+ .Filter = Filter,
+ .NotifyFilter = NotifyFilters.LastAccess _
+ Or NotifyFilters.LastWrite _
+ Or NotifyFilters.FileName _
+ Or NotifyFilters.Size _
+ Or NotifyFilters.FileName _
+ Or NotifyFilters.Attributes
+ }
+
+ AddHandler oWatcher.Created, AddressOf HandleFileCreated
+ AddHandler oWatcher.Changed, AddressOf HandleFileChanged
+ AddHandler oWatcher.Deleted, AddressOf HandleFileDeleted
+ AddHandler oWatcher.Renamed, AddressOf HandleFileRenamed
+
+ Return oWatcher
+ End Function
+
+ Private Sub HandleFileCreated(sender As Object, e As FileSystemEventArgs)
+ _Files.Add(e.FullPath, New FileWatcherProperties())
+ _Logger.Debug("[Created] " & e.FullPath)
+ End Sub
+
+ '''
+ ''' This may fire twice for a single save operation,
+ ''' see: https://blogs.msdn.microsoft.com/oldnewthing/20140507-00/?p=1053/
+ '''
+ Private Sub HandleFileChanged(sender As Object, e As FileSystemEventArgs)
+ _Files.Item(e.FullPath).ChangedAt = DateTime.Now
+ _Logger.Debug("[Changed] " & e.FullPath)
+
+ Dim oShouldRaiseSave As Boolean = Not _Filters.Any(Function(oFilter)
+ Return oFilter.ShouldFilter(e)
+ End Function)
+
+ If oShouldRaiseSave Then
+ RaiseEvent FileSaved(e.FullPath, False)
+ End If
+ End Sub
+ Private Sub HandleFileDeleted(sender As Object, e As FileSystemEventArgs)
+ _Files.Remove(e.FullPath)
+ _Logger.Debug("[Removed] " & e.FullPath)
+ End Sub
+
+ Private Sub HandleFileRenamed(sender As Object, e As RenamedEventArgs)
+ Dim oProperties = _Files.Item(e.OldFullPath)
+ _Files.Remove(e.OldFullPath)
+ _Files.Add(e.FullPath, oProperties)
+ ' Soll eine umbenannte datei als NEU gelten?
+
+ Dim oShouldRaiseSave = _Filters.Any(Function(oFilter)
+ Return oFilter.ShouldRaiseSave(e)
+ End Function)
+
+ If oShouldRaiseSave Then
+ RaiseEvent FileSaved(e.OldFullPath, True)
+ End If
+
+ _Logger.Debug("[Renamed] {0} --> {1}", e.OldFullPath, e.FullPath)
+ End Sub
+
+
+End Class
diff --git a/Base/FileWatcher/FileWatcherFilters.vb b/Base/FileWatcher/FileWatcherFilters.vb
new file mode 100644
index 00000000..84f70b62
--- /dev/null
+++ b/Base/FileWatcher/FileWatcherFilters.vb
@@ -0,0 +1,61 @@
+Imports System.IO
+
+'''
+''' Built-in filters for FileWatcher that are useful for correctly detecting changes on Office documents (currently Office 2016)
+'''
+Public Class FileWatcherFilters
+ '''
+ ''' Base Filter that all filters must inherit from
+ ''' Provides two functions that may be overridden and some useful file extension lists
+ '''
+ Public MustInherit Class BaseFileFilter
+ Public TempFiles As New List(Of String) From {".tmp", ""}
+
+ Public Overridable Function ShouldFilter(e As FileSystemEventArgs) As Boolean
+ Return False
+ End Function
+ Public Overridable Function ShouldRaiseSave(e As RenamedEventArgs) As Boolean
+ Return False
+ End Function
+ End Class
+
+ '''
+ ''' Simple Filter that filters changes made on temporary files
+ '''
+ Public Class TempFileFilter
+ Inherits BaseFileFilter
+
+ Public Overrides Function ShouldFilter(e As FileSystemEventArgs) As Boolean
+ Dim oFileInfo As New FileInfo(e.FullPath)
+ Return TempFiles.Contains(oFileInfo.Extension)
+ End Function
+ End Class
+
+ '''
+ ''' Filter to detect changes on Office files
+ '''
+ Public Class OfficeFileFilter
+ Inherits BaseFileFilter
+
+ Public OfficeFiles As New List(Of String) From {".docx", ".pptx", ".xlsx"}
+
+ Public Overrides Function ShouldFilter(e As FileSystemEventArgs) As Boolean
+ Dim oFileInfo As New FileInfo(e.FullPath)
+ Return OfficeFiles.Contains(oFileInfo.Extension) And oFileInfo.Name.StartsWith("~")
+ End Function
+
+ Public Overrides Function ShouldRaiseSave(e As RenamedEventArgs) As Boolean
+ Dim oIsTransform = OfficeFiles.Any(Function(Extension As String)
+ Return e.OldName.EndsWith(Extension)
+ End Function)
+
+ ' Check if it is renamed to a temp file
+ Dim oIsTempFile = TempFiles.Any(Function(Extension)
+ Return e.Name.EndsWith(Extension)
+ End Function)
+
+
+ Return oIsTransform And oIsTempFile
+ End Function
+ End Class
+End Class
diff --git a/Base/FileWatcher/FileWatcherProperties.vb b/Base/FileWatcher/FileWatcherProperties.vb
new file mode 100644
index 00000000..5eadecbe
--- /dev/null
+++ b/Base/FileWatcher/FileWatcherProperties.vb
@@ -0,0 +1,10 @@
+Public Class FileWatcherProperties
+ Public Property CreatedAt As DateTime
+ Public Property ChangedAt As DateTime
+ Public ReadOnly Property HasChanged As Boolean
+ Public Sub New()
+ CreatedAt = DateTime.Now
+ ChangedAt = Nothing
+ HasChanged = False
+ End Sub
+End Class
diff --git a/Base/FilesystemEx.vb b/Base/FilesystemEx.vb
new file mode 100644
index 00000000..a746d93f
--- /dev/null
+++ b/Base/FilesystemEx.vb
@@ -0,0 +1,495 @@
+Imports DigitalData.Modules.Logging
+Imports System.IO
+Imports System.Security.Cryptography
+Imports System.Text
+Imports System.Text.RegularExpressions
+
+Public Class FilesystemEx
+ Private ReadOnly _Logger As Logger
+ Private ReadOnly _LogConfig As LogConfig
+
+ Private ReadOnly _invalidFilenameChars As String
+ Private ReadOnly _invalidPathChars As String
+
+ Private Const REGEX_CLEAN_FILENAME As String = "[\\/:""<>|\b\0\r\n\t]"
+ Private Const REGEX_CLEAN_PATH As String = "[""<>|\b\0\r\n\t]"
+
+ ' The limit enforced by windows for filenpaths is 260,
+ ' so we use a slightly smaller number to have some Error margin.
+ '
+ ' Source: https://docs.microsoft.com/de-de/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#maximum-path-length-limitation
+ Private Const MAX_FILE_PATH_LENGTH = 250
+
+ ' This prevents an infinite loop when no file can be created in a location
+ Private Const MAX_FILE_VERSION = 100
+ Private Const VERSION_SEPARATOR As Char = "~"c
+
+ Private Const FILE_NAME_ACCESS_TEST = "accessTest.txt"
+
+ Public Sub New(LogConfig As LogConfig)
+ _LogConfig = LogConfig
+ _Logger = LogConfig.GetLogger()
+
+ _invalidFilenameChars = String.Join("", Path.GetInvalidFileNameChars())
+ _invalidPathChars = String.Join("", Path.GetInvalidPathChars())
+ End Sub
+
+ Public Function GetCleanFilename(FileName As String) As String
+ _Logger.Debug("Filename before cleaning: [{0}]", FileName)
+
+ Dim oCleanName As String = FileName
+ oCleanName = Regex.Replace(oCleanName, _invalidFilenameChars, String.Empty)
+ oCleanName = Regex.Replace(oCleanName, REGEX_CLEAN_FILENAME, String.Empty, RegexOptions.Singleline)
+ oCleanName = Regex.Replace(oCleanName, "\s{2,}", " ")
+ oCleanName = Regex.Replace(oCleanName, "\.{2,}", ".")
+
+ _Logger.Debug("Filename after cleaning: [{0}]", oCleanName)
+
+ Return oCleanName
+ End Function
+
+ Public Function GetCleanPath(FilePath As String) As String
+ _Logger.Debug("Path before cleaning: [{0}]", FilePath)
+
+ Dim oCleanName As String = FilePath
+ oCleanName = Regex.Replace(oCleanName, _invalidPathChars, String.Empty)
+ oCleanName = Regex.Replace(oCleanName, REGEX_CLEAN_PATH, String.Empty, RegexOptions.Singleline)
+
+ _Logger.Debug("Path after cleaning: [{0}]", oCleanName)
+
+ Return oCleanName
+ End Function
+
+ '''
+ ''' Reads the file at `FilePath` and computes a SHA256 Hash from its contents
+ '''
+ '''
+ '''
+ Public Function GetChecksum(FilePath As String) As String
+ Try
+ Using oFileStream = IO.File.OpenRead(FilePath)
+ Using oStream As New BufferedStream(oFileStream, 1200000)
+ Dim oChecksum() As Byte = SHA256.Create.ComputeHash(oStream)
+ Return FormatHash(oChecksum)
+ End Using
+ End Using
+ Catch ex As Exception
+ _Logger.Error(ex)
+ Return Nothing
+ End Try
+ End Function
+
+ Public Function GetChecksumFromString(pStringToCheck As String) As String
+ Dim oBytes() As Byte = Encoding.UTF8.GetBytes(pStringToCheck)
+ Dim oChecksum() As Byte = SHA256.Create.ComputeHash(oBytes)
+ Return FormatHash(oChecksum)
+ End Function
+
+ Public Function GetHash(FilePath As String) As String
+ Return GetChecksum(FilePath)
+ End Function
+
+ Public Function GetHashFromString(pStringToCheck As String) As String
+ Return GetChecksumFromString(pStringToCheck)
+ End Function
+
+ Private Function FormatHash(pChecksum)
+ Return BitConverter.
+ ToString(pChecksum).
+ Replace("-", String.Empty)
+ End Function
+
+ '''
+ ''' Adds file version string to given filename `Destination` if that file already exists.
+ '''
+ ''' Filepath to check
+ ''' Versioned string
+ Public Function GetVersionedFilename(pFilePath As String) As String
+ Return GetVersionedFilenameWithFilecheck(pFilePath, Function(pPath As String) IO.File.Exists(pPath))
+ End Function
+
+ '''
+ ''' Adds file version string to given filename `Destination` if that file already exists.
+ '''
+ ''' Filepath to check
+ ''' Custom action to check for file existence
+ ''' Versioned string
+ Public Function GetVersionedFilenameWithFilecheck(pFilePath As String, pFileExistsAction As Func(Of String, Boolean)) As String
+ Try
+ Dim oFileName As String = pFilePath
+ Dim oFinalFileName = oFileName
+
+ Dim oDestinationDir = Path.GetDirectoryName(oFileName)
+ Dim oExtension = Path.GetExtension(oFileName)
+
+ Dim oFileNameWithoutExtension = Path.GetFileNameWithoutExtension(oFileName)
+ Dim oSplitResult = GetVersionedString(oFileNameWithoutExtension)
+
+ oFileNameWithoutExtension = oSplitResult.Item1
+ Dim oFileVersion As Integer = oSplitResult.Item2
+
+ ' Shorten the filename (only filename, without extension or version)
+ ' by cutting the length in half. This should work no matter how long the path and/or filename are.
+ ' The initial check operates on the full path to catch all scenarios.
+ If pFilePath.Length > MAX_FILE_PATH_LENGTH Then
+ _Logger.Info("Filename is too long. Filename will be cut to prevent further errors.")
+ _Logger.Info("Original Filename is: {0}", oFileNameWithoutExtension)
+ Dim oNewLength As Integer = CInt(Math.Floor(oFileNameWithoutExtension.Length / 2.0))
+ Dim oNewFileNameWithoutExtension = oFileNameWithoutExtension.Substring(0, oNewLength)
+ _Logger.Info("New Filename will be: {0}", oNewFileNameWithoutExtension)
+
+ oFileNameWithoutExtension = oNewFileNameWithoutExtension
+ End If
+
+ ' while file exists, increment version.
+ ' version cannot go above MAX_FILE_VERSION, to prevent infinite loop
+ Do
+ oFinalFileName = Path.Combine(oDestinationDir, GetFilenameWithVersion(oFileNameWithoutExtension, oFileVersion, oExtension))
+ _Logger.Debug("Intermediate Filename is {0}", oFinalFileName)
+ _Logger.Debug("File version: {0}", oFileVersion)
+ oFileVersion += 1
+ Loop While pFileExistsAction(oFinalFileName) = True And oFileVersion < MAX_FILE_VERSION
+
+ If oFileVersion >= MAX_FILE_VERSION Then
+ Throw New OverflowException($"Tried '{MAX_FILE_VERSION}' times to version filename before giving up. Sorry.")
+ End If
+
+ _Logger.Debug("Final Filename is {0}", oFinalFileName)
+
+ Return oFinalFileName
+ Catch ex As Exception
+ _Logger.Warn("Filename {0} could not be versioned. Original filename will be returned!", pFilePath)
+ _Logger.Error(ex)
+ Return pFilePath
+ End Try
+ End Function
+
+ '''
+ ''' Split String at version separator to:
+ ''' check if string is already versioned,
+ ''' get the string version of an already versioned string
+ '''
+ '''
+ ''' Examples:
+ ''' test1.pdf --> test1 --> ['test1'] --> no fileversion
+ ''' test1~2.pdf --> test1~2 --> ['test1', '2'] --> version 2
+ ''' test1~12345~2.pdf --> test1~12345~2 --> ['test1', '12345', '2'] --> still version 2
+ ''' somestring~3 --> somestring~3 --> ['somestring', '3'] --> version 3
+ '''
+ ''' The string to versioned
+ ''' The character to split at
+ ''' Tuple of string and version
+ Public Function GetVersionedString(pString As String) As Tuple(Of String, Integer)
+ Dim oSplitString = pString.Split(VERSION_SEPARATOR).ToList()
+ Dim oStringVersion As Integer
+
+ ' if string is already versioned, extract string version
+ ' else just use the string and set version to 1
+ If oSplitString.Count > 1 Then
+ Dim oVersion As Integer = 1
+ Try
+ oVersion = Integer.Parse(oSplitString.Last())
+ pString = String.Join("", oSplitString.Take(oSplitString.Count - 1))
+ Catch ex As Exception
+ ' pString does NOT change
+ pString = pString
+ Finally
+ oStringVersion = oVersion
+ End Try
+ Else
+ oStringVersion = 1
+ End If
+
+ _Logger.Debug("Versioned: String [{0}], Version [{1}]", pString, oStringVersion)
+
+ Return New Tuple(Of String, Integer)(pString, oStringVersion)
+ End Function
+
+ Public Function GetAppDataPath(CompanyName As String, ProductName As String)
+ Dim oLocalAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
+ Return Path.Combine(oLocalAppData, CompanyName, ProductName)
+ End Function
+
+ Private Function GetFilenameWithVersion(FileNameWithoutExtension As String, FileVersion As Integer, Extension As String) As String
+ If FileVersion <= 1 Then
+ Return $"{FileNameWithoutExtension}{Extension}"
+ Else
+ Return $"{FileNameWithoutExtension}{VERSION_SEPARATOR}{FileVersion}{Extension}"
+ End If
+ End Function
+
+ '''
+ ''' Removes files in a directory filtered by filename, extension and last write date
+ '''
+ ''' The directory in which files will be deleted
+ ''' Only delete files which are older than x days. Must be between 0 and 1000 days.
+ ''' A filename filter which will be checked
+ ''' A file extension which will be checked
+ ''' Should the function continue with deleting when a file could not be deleted?
+ ''' True if all files were deleted or if no files were deleted, otherwise false
+ Public Function RemoveFiles(Path As String, FileKeepTime As Integer, FileBaseName As String, Optional FileExtension As String = "log", Optional ContinueOnError As Boolean = True) As Boolean
+ If Not TestPathIsDirectory(Path) Then
+ Throw New ArgumentException($"Path {Path} is not a directory!")
+ End If
+
+ If Not Directory.Exists(Path) Then
+ Throw New DirectoryNotFoundException($"Path {Path} does not exist!")
+ End If
+
+ If FileKeepTime < 0 Or FileKeepTime > 1000 Then
+ Throw New ArgumentOutOfRangeException("FileKeepTime must be an integer between 0 and 1000!")
+ End If
+
+ Dim oUnableToDeleteCounter = 0
+ Dim oDirectory As New DirectoryInfo(Path)
+ Dim oDateLimit As DateTime = DateTime.Now.AddDays(FileKeepTime)
+ Dim oFiles As List(Of FileInfo) = oDirectory.
+ EnumerateFiles($"*{FileBaseName}*").
+ Where(Function(oFileInfo As FileInfo)
+ Return oFileInfo.Extension = FileExtension And oFileInfo.LastWriteTime < oDateLimit
+ End Function).
+ ToList()
+
+ If oFiles.Count = 0 Then
+ _Logger.Debug("No files found that match the criterias.")
+ Return True
+ End If
+
+ _Logger.Debug("Deleting old files (Found {0}).", oFiles.Count)
+
+ For Each oFile As FileInfo In oFiles
+ Try
+ oFile.Delete()
+ Catch ex As Exception
+ If ContinueOnError = False Then
+ _Logger.Warn("Deleting files was aborted at file {0}.", oFile.FullName)
+ Return False
+ End If
+ oUnableToDeleteCounter = oUnableToDeleteCounter + 1
+ _Logger.Warn("File {0} could not be deleted!")
+ End Try
+ Next
+
+ If oUnableToDeleteCounter > 0 Then
+ _Logger.Debug("Old files partially removed. {0} files could not be removed.", oUnableToDeleteCounter)
+ Else
+ _Logger.Debug("Old files removed.")
+ End If
+
+ Return True
+ End Function
+
+
+ Public Sub MoveTo(FilePath As String, Directory As String)
+ Dim oFileInfo As New FileInfo(FilePath)
+ IO.File.Move(FilePath, Path.Combine(Directory, oFileInfo.Name))
+ End Sub
+
+
+ Public Sub MoveTo(FilePath As String, NewFileName As String, Directory As String)
+ IO.File.Move(FilePath, Path.Combine(Directory, NewFileName))
+ End Sub
+
+ '''
+ ''' Copied from https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories
+ '''
+ '''
+ '''
+ '''
+ Public Sub CopyDirectory(ByVal SourceDirName As String, ByVal DestDirName As String, ByVal CopySubDirs As Boolean)
+ Dim oDirectory As New DirectoryInfo(SourceDirName)
+
+ If Not oDirectory.Exists Then
+ Throw New DirectoryNotFoundException("Source directory does not exist or could not be found: " & SourceDirName)
+ End If
+
+ Dim oDirectories As DirectoryInfo() = oDirectory.GetDirectories()
+ Directory.CreateDirectory(DestDirName)
+ Dim oFiles As FileInfo() = oDirectory.GetFiles()
+
+ For Each oFile As FileInfo In oFiles
+ Dim tempPath As String = Path.Combine(DestDirName, oFile.Name)
+ oFile.CopyTo(tempPath, False)
+ Next
+
+ If CopySubDirs Then
+ For Each oSubDirectory As DirectoryInfo In oDirectories
+ Dim oTempPath As String = Path.Combine(DestDirName, oSubDirectory.Name)
+ CopyDirectory(oSubDirectory.FullName, oTempPath, CopySubDirs)
+ Next
+ End If
+ End Sub
+
+ '''
+ ''' Tries to create a directory and returns its path.
+ ''' Returns a temp path if `DirectoryPath` can not be created or written to.
+ '''
+ ''' The directory to create
+ ''' Should a write access test be performed?
+ ''' The used path
+ Public Function CreateDirectory(DirectoryPath As String, Optional TestWriteAccess As Boolean = True) As String
+ Dim oFinalPath As String
+ If Directory.Exists(DirectoryPath) Then
+ _Logger.Debug("Directory {0} already exists. Skipping.", DirectoryPath)
+ oFinalPath = DirectoryPath
+ Else
+ Try
+ Directory.CreateDirectory(DirectoryPath)
+ oFinalPath = DirectoryPath
+ Catch ex As Exception
+ _Logger.Error(ex)
+ _Logger.Warn("Directory {0} could not be created. Temp path will be used instead.", DirectoryPath)
+ oFinalPath = Path.GetTempPath()
+ End Try
+ End If
+
+ If TestWriteAccess AndAlso Not TestPathIsWritable(DirectoryPath) Then
+ _Logger.Warn("Directory {0} is not writable. Temp path will be used instead.", DirectoryPath)
+ oFinalPath = Path.GetTempPath()
+ Else
+ oFinalPath = DirectoryPath
+ End If
+
+ _Logger.Debug("Using path {0}", oFinalPath)
+
+ Return oFinalPath
+ End Function
+
+ Public Function TestPathIsWritable(DirectoryPath As String) As Boolean
+ Try
+ Dim fileAccessPath = Path.Combine(DirectoryPath, FILE_NAME_ACCESS_TEST)
+ Using fs As FileStream = IO.File.Create(fileAccessPath)
+ fs.WriteByte(0)
+ End Using
+
+ IO.File.Delete(fileAccessPath)
+ Return True
+ Catch ex As Exception
+ Return False
+ End Try
+ End Function
+
+ '''
+ ''' Checks if a file is locked, ie. in use by another process.
+ '''
+ '''
+ ''' https://docs.microsoft.com/en-us/dotnet/standard/io/handling-io-errors
+ ''' https://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in-use
+ '''
+ Public Function TestFileIsLocked(pFilePath As String) As Boolean
+ Try
+ Using stream As FileStream = IO.File.Open(pFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)
+ stream.Close()
+ End Using
+ Catch ex As Exception When ((ex.HResult And &HFFFF) = 32)
+ Return True
+ Catch ex As Exception
+ Return True
+ End Try
+
+ Return False
+ End Function
+
+ Public Function TestPathIsDirectory(Path As String) As Boolean
+ If Not Directory.Exists(Path) Then
+ Return False
+ End If
+
+ Dim oIsDirectory As Boolean = (System.IO.File.GetAttributes(Path) And FileAttributes.Directory) = FileAttributes.Directory
+ Return oIsDirectory
+ End Function
+
+ '''
+ ''' Checks the size of the supplied file.
+ '''
+ '''
+ '''
+ '''
+ Public Function TestFileSizeIsLessThanMaxFileSize(pFilePath As String, pMaxFileSizeInMegabytes As Integer) As Boolean
+ Dim oFileInfo As New FileInfo(pFilePath)
+
+ _Logger.Info("Checking Filesize of {0}", oFileInfo.Name)
+ _Logger.Debug("Filesize threshold is {0} MB.", pMaxFileSizeInMegabytes)
+
+ If pMaxFileSizeInMegabytes <= 0 Then
+ _Logger.Debug("Filesize is not configured. Skipping check.")
+ Return True
+ End If
+
+ Dim oMaxSize = pMaxFileSizeInMegabytes * 1024 * 1024
+
+ If oMaxSize > 0 And oFileInfo.Length > oMaxSize Then
+ _Logger.Debug("Filesize is bigger than threshold.")
+ Return False
+ Else
+ _Logger.Debug("Filesize is smaller than threshold. All fine.")
+ Return True
+ End If
+ End Function
+
+ Public Function GetDateDirectory(pBaseDirectory As String, pDate As Date) As String
+ Dim oDateDirectory = GetDateString(pDate)
+ Dim oFinalDirectory As String = IO.Path.Combine(pBaseDirectory, oDateDirectory)
+ Return oFinalDirectory
+ End Function
+
+ Public Function GetDateDirectory(pBaseDirectory As String) As String
+ Return GetDateDirectory(pBaseDirectory, Now)
+ End Function
+
+ Public Function CreateDateDirectory(pBaseDirectory As String, pDate As Date) As String
+ Dim oDateDirectory = GetDateString(pDate)
+ Dim oFinalDirectory As String = IO.Path.Combine(pBaseDirectory, oDateDirectory)
+
+ If IO.Directory.Exists(oFinalDirectory) = False Then
+ _Logger.Debug("Path does not exist, creating: [{0}]", oFinalDirectory)
+ Try
+ Directory.CreateDirectory(oFinalDirectory)
+ _Logger.Debug("Created folder [{0}]", oFinalDirectory)
+ Catch ex As Exception
+ _Logger.Warn("Final path [{0}] could not be created!", oFinalDirectory)
+ _Logger.Error(ex)
+ End Try
+ End If
+
+ Return oFinalDirectory
+ End Function
+
+ Public Function CreateDateDirectory(pBaseDirectory As String) As String
+ Return CreateDateDirectory(pBaseDirectory, Now)
+ End Function
+
+ Public Function GetDateString() As String
+ Return $"{Now:yyyy\\MM\\dd}"
+ End Function
+
+ Public Function GetDateString(pDate As Date) As String
+ Return $"{pDate:yyyy\\MM\\dd}"
+ End Function
+
+ Public Function GetDateTimeString() As String
+ Return $"{Now:yyyy-MM-dd_hh-mm-ffff}"
+ End Function
+
+ Public Function GetDateTimeString(pDate As Date) As String
+ Return $"{pDate:yyyy-MM-dd_hh-mm-ffff}"
+ End Function
+
+ Public Function GetFilenameWithSuffix(pFilePath As String, pSuffix As String)
+ Dim oFileInfo = New IO.FileInfo(pFilePath)
+ Return GetFilenameWithSuffix(IO.Path.GetFileNameWithoutExtension(pFilePath), pSuffix, oFileInfo.Extension.Substring(1))
+ End Function
+
+ Public Function GetFilenameWithSuffix(pBaseString As String, pSuffix As String, pExtension As String)
+ Return $"{pBaseString}-{pSuffix}.{pExtension}"
+ End Function
+
+ Public Function GetFilenameWithPrefix(pFilePath As String, pPrefix As String)
+ Dim oFileInfo = New IO.FileInfo(pFilePath)
+ Return GetFilenameWithSuffix(IO.Path.GetFileNameWithoutExtension(pFilePath), pPrefix, oFileInfo.Extension.Substring(1))
+ End Function
+
+ Public Function GetFilenameWithPrefix(pBaseString As String, pPrefix As String, pExtension As String)
+ Return $"{pPrefix}-{pBaseString}.{pExtension}"
+ End Function
+End Class