Imports System
Imports System.Configuration
Imports System.Diagnostics
Imports System.Globalization
Imports System.IO
Imports System.Security.Permissions
Imports System.Text.RegularExpressions

<HostProtection(Synchronization:=True)>
Public Class RollingXmlWriterTraceListener
    Inherits XmlWriterTraceListener

    Private Shared ReadOnly LogFileNumberCaptureName As String = "LogFileNumber"
    Private attributesLoaded As Boolean = False
    Private logfileSuffixExpression As Regex = New Regex("_(?<" & LogFileNumberCaptureName & ">\d*)\.", RegexOptions.Compiled)
    Private currentFileSuffixNumber As Integer = 0
    Private _maxTraceFileSize As Long = 128 * 1024 * 1024
    Private basicTraceFileName As String = String.Empty

    Public Sub New(ByVal filename As String)
        MyBase.New(filename)
        Me.basicTraceFileName = filename
        Me.currentFileSuffixNumber = Me.GetTraceFileNumber()
        Me.StartNewTraceFile()
    End Sub

    Public Sub New(ByVal filename As String, ByVal name As String)
        MyBase.New(filename, name)
        Me.basicTraceFileName = filename
        Me.StartNewTraceFile()
    End Sub

    Public ReadOnly Property CurrentTraceFileName As String
        Get
            Return Path.Combine(Path.GetDirectoryName(Me.basicTraceFileName), Path.GetFileNameWithoutExtension(Me.basicTraceFileName) & "_" & Me.currentFileSuffixNumber.ToString().PadLeft(4, "0"c) & Path.GetExtension(Me.basicTraceFileName))
        End Get
    End Property

    Public Property MaxTraceFileSize As Long
        Get

            If Not Me.attributesLoaded Then
                Me.LoadAttributes()
            End If

            Return Me._maxTraceFileSize
        End Get
        Set(ByVal value As Long)

            If Not Me.attributesLoaded Then
                Me.LoadAttributes()
            End If

            Me._maxTraceFileSize = value
        End Set
    End Property

    Protected ReadOnly Property IsRollingConditionReached As Boolean
        Get
            Dim streamWriter As StreamWriter = CType(Me.Writer, StreamWriter)
            Dim fileStream As FileStream = CType(streamWriter.BaseStream, FileStream)
            Dim traceFileName As String = fileStream.Name
            Dim traceFileInfo As FileInfo = New FileInfo(traceFileName)

            If traceFileInfo.Length > Me._maxTraceFileSize Then
                Return True
            Else
                Return False
            End If
        End Get
    End Property

    Public Overrides Sub Fail(ByVal message As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.Fail(message)
    End Sub

    Public Overrides Sub Fail(ByVal message As String, ByVal detailMessage As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.Fail(message, detailMessage)
    End Sub

    Public Overrides Sub TraceData(ByVal eventCache As TraceEventCache, ByVal source As String, ByVal eventType As TraceEventType, ByVal id As Integer, ByVal data As Object)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.TraceData(eventCache, source, eventType, id, data)
    End Sub

    Public Overrides Sub TraceData(ByVal eventCache As TraceEventCache, ByVal source As String, ByVal eventType As TraceEventType, ByVal id As Integer, ParamArray data As Object())
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.TraceData(eventCache, source, eventType, id, data)
    End Sub

    Public Overrides Sub TraceEvent(ByVal eventCache As TraceEventCache, ByVal source As String, ByVal eventType As TraceEventType, ByVal id As Integer)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.TraceEvent(eventCache, source, eventType, id)
    End Sub

    Public Overrides Sub TraceEvent(ByVal eventCache As TraceEventCache, ByVal source As String, ByVal eventType As TraceEventType, ByVal id As Integer, ByVal message As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.TraceEvent(eventCache, source, eventType, id, message)
    End Sub

    Public Overrides Sub TraceEvent(ByVal eventCache As TraceEventCache, ByVal source As String, ByVal eventType As TraceEventType, ByVal id As Integer, ByVal format As String, ParamArray args As Object())
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.TraceEvent(eventCache, source, eventType, id, format, args)
    End Sub

    Public Overrides Sub TraceTransfer(ByVal eventCache As TraceEventCache, ByVal source As String, ByVal id As Integer, ByVal message As String, ByVal relatedActivityId As Guid)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.TraceTransfer(eventCache, source, id, message, relatedActivityId)
    End Sub

    Public Overrides Sub Write(ByVal o As Object)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.Write(o)
    End Sub

    Public Overrides Sub Write(ByVal o As Object, ByVal category As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.Write(o, category)
    End Sub

    Public Overrides Sub Write(ByVal message As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.Write(message)
    End Sub

    Public Overrides Sub Write(ByVal message As String, ByVal category As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.Write(message, category)
    End Sub

    Public Overrides Sub WriteLine(ByVal o As Object)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.WriteLine(o)
    End Sub

    Public Overrides Sub WriteLine(ByVal o As Object, ByVal category As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.WriteLine(o, category)
    End Sub

    Public Overrides Sub WriteLine(ByVal message As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.WriteLine(message)
    End Sub

    Public Overrides Sub WriteLine(ByVal message As String, ByVal category As String)
        If Me.IsRollingConditionReached Then
            Me.StartNewTraceFile()
        End If

        MyBase.WriteLine(message, category)
    End Sub

    Protected Overrides Function GetSupportedAttributes() As String()
        Return New String(0) {"MaxTraceFileSize"}
    End Function

    Private Sub StartNewTraceFile()
        Dim streamWriter As StreamWriter = CType(Me.Writer, StreamWriter)
        Dim fileStream As FileStream = CType(streamWriter.BaseStream, FileStream)
        fileStream.Close()
        Me.currentFileSuffixNumber += 1
        Me.Writer = New StreamWriter(New FileStream(Me.CurrentTraceFileName, FileMode.Create))
    End Sub

    Private Function GetTraceFileNumber() As Integer
        Dim directoryName As String = Path.GetDirectoryName(Me.basicTraceFileName)
        Dim basicTraceFileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(Me.basicTraceFileName)
        Dim basicTraceFileNameExtension As String = Path.GetExtension(Me.basicTraceFileName)
        Dim existingLogFiles As String() = Directory.GetFiles(directoryName, basicTraceFileNameWithoutExtension & "*")
        Dim highestNumber As Integer = -1

        For Each existingLogFile As String In existingLogFiles
            Dim match As Match = Me.logfileSuffixExpression.Match(existingLogFile)

            If match IsNot Nothing Then
                Dim tempInt As Integer

                If match.Groups.Count >= 1 AndAlso Integer.TryParse(match.Groups(LogFileNumberCaptureName).Value, tempInt) AndAlso tempInt >= highestNumber Then
                    highestNumber = tempInt
                End If
            End If
        Next

        Return highestNumber
    End Function

    Private Sub LoadAttributes()
        If Attributes.ContainsKey("MaxTraceFileSize") AndAlso Not String.IsNullOrEmpty(Attributes("MaxTraceFileSize")) Then
            Dim tempLong As Long = 0
            Dim attributeValue As String = Attributes("MaxTraceFileSize")

            If Long.TryParse(attributeValue, tempLong) Then
                Me._maxTraceFileSize = Long.Parse(Attributes("MaxTraceFileSize"), NumberFormatInfo.InvariantInfo)
            Else
                Throw New ConfigurationErrorsException(String.Format("Trace listener {0} has an unparseable configuration attribute ""MaxTraceFileSize"". The value ""{1}"" cannot be parsed to a long value.", Me.Name, attributeValue))
            End If
        End If

        Me.attributesLoaded = True
    End Sub
End Class
