modules/AuditLog.psm1

using namespace System.Management.Automation
using module '.\Enums.psm1'
using module '.\Helper\StringHelper.psm1'
using module '.\Helper\DateTimeHelper.psm1'
using module '.\SessionConfig.psm1'
using module '.\Config.psm1'
using module '.\Downloader\DownloaderBase.psm1'
using module '.\Downloader\DownloaderFactory.psm1'
using module '.\FeedProcessor\ProcessorBase.psm1'
using module '.\FeedProcessor\ProcessorFactory.psm1'
using module '.\FileProcessor\FileProcessor.psm1'

class AuditLogFile{
    #region Private Properties

    hidden [string] $logFileName
    hidden [string] $fileExtension

    hidden [PSCustomObject[]] $currentFile
    hidden [int] $filePos
    hidden [int] $fileLength

    hidden [SOURCE[]] $sources
    hidden [bool] $async
    hidden [bool] $convert
    hidden [bool] $saveConverted

    #endregion

    #region Public Properties

    [SessionConfig] $config
    [hashtable]$fileContent = @{}

    [hashtable] $jobs
    [datetime] $startedAt
    [datetime] $finishedAt

    #endregion

    #region Constructor

    AuditLogFile([SessionConfig] $config, [bool] $convert, [bool] $saveConverted){
        $this.config = $config
        $this.jobs = @{}

        $this.convert = $convert
        $this.saveConverted = $saveConverted

        [ProcessorFactory]::Init([Config]::Load().processors)
    }

    #endregion

    #region Processing Methods

    hidden [void] MountSingleFile([SOURCE] $source, [bool] $runAsJob){
        $start = Get-Date
        $this.startedAt = $start

        foreach ($ext in @('xml', [DownloaderFactory]::current.fileExtension)) {
            $fileName = $this.config.getFileName($source, $ext)

            if (Test-Path $fileName) {

                $size = [StringHelper]::FormatFileSize((Get-Item $fileName).Length)
                if (-not $runAsJob) { Write-Host ('Mounting ''{0}'' ({1})' -f @($source, $size)) -NoNewline}

                if ($runAsJob) {
                    $env:WhereAmI = $PSScriptRoot

                    $job = Start-Job -Name $source -ScriptBlock { param($file, $convert, $timezone)
                        Import-File -file $file -convert $convert -timezone $timezone} `
                        -ArgumentList ($fileName, $this.convert, $this.config.timezone) -InitializationScript `
                        { Import-Module "$env:WhereAmI\FileProcessor\FileProcessor.psd1" }

                    Remove-Item env:\WhereAmI

                    $this.jobs.Add($source, $job.id)
                    $this.fileContent.Add($source, $null)
                }
                else {
                    $content = (Import-File -file $fileName -convert $this.convert -timezone $this.config.timezone)
                    $this.fileContent.Add($source, $content)
                }
            }
        }

        $this.finishedAt = Get-Date
        $diff =$this.finishedAt - $start
        if (-not $runAsJob) { Write-Host (" :: OK ({0})" -f ([DateTimeHelper]::FormatMilliseconds($diff.TotalMilliseconds))) }
    }

    [void] MountAllFiles([bool] $runAsJob){
        $this.startedAt = Get-Date
        $this.async = $runAsJob

        foreach ($src in $this.config.analyzedSources) {
            $this.MountSingleFile($src, $runAsJob)
        }

        $this.finishedAt = Get-Date
    }

    #endregion

    #region Getters

    [timespan] getProcessingTime(){
        return ($this.finishedAt - $this.startedAt)
    }

    [timespan] getProcessingTime([datetime] $dt){
        return ($dt - $this.startedAt)
    }

    [string] getExternalByKey($externalKey) {
        return [ProcessorFactory]::externalKeys[$externalKey]
    }

    #endregion

    #region Read Content

    [int] AssignFile([SOURCE] $source){
        if ($this.async) { $this.ResumeJobs() }

        $this.currentFile = $this.fileContent[$source]
        $this.filePos = 0
        $this.fileLength = $this.currentFile.Count

        return ($this.fileContent[$source].Count)
    }

    [PSCustomObject] ReadLine(){
        $line = $this.currentFile[$this.filePos]
        $this.filePos++

        if ($line -and $null -eq $line.content) { $line = (ConvertFrom-LineData -line $line) }

        return $line
    }

    [int] Seek([datetime] $seekDateTime){
        $lower = 0
        $upper = $this.currentFile.Length
        $current = [Math]::Floor(($upper + $lower)/2)
        $item = $this.currentFile[$current]

        while ($item.created_at -ne $seekDateTime) {

            if ($seekDateTime -gt $item.created_at) { $lower = $current }
            else { $upper = $current }

            $current = [Math]::Floor(($upper + $lower)/2)
            $item = $this.currentFile[$current]

            if ($current -eq $lower -or $current -eq $upper) { break }
        }

        if ($item.created_at -ge $seekDateTime){
            $this.filePos = $current
        }
        else {
            $this.filePos = $upper
        }

        return $this.filePos
    }

    [int] Seek([long] $seekId){
        $lower = 0
        $upper = $this.currentFile.Length
        $current = [Math]::Floor(($upper + $lower)/2)
        $item = $this.currentFile[$current]

        while ($item.id -ne $seekId) {

            if ($seekId -gt $item.id){ $lower = $current }
            else { $upper = $current }

            $current = [Math]::Floor(($upper + $lower)/2)
            $item = $this.currentFile[$current]

            if ($current -eq $lower -or $current -eq $upper) { break }
        }

        if ($item.id -ge $seekId){
            $this.filePos = $current
        }
        else {
            $this.filePos = $upper
        }

        return $this.filePos
    }

    [bool] EOF(){
        return ($this.filePos -eq $this.fileLength)
    }

    [int] getFilePosition(){
        return $this.filePos
    }

    #endregion

    #region Jobs

    [void] addJob([SOURCE] $source){
        $this.async = $true
        $this.MountSingleFile($source, $true)
    }

    [void] ResumeJobs(){
        foreach ($job in (Get-Job | Where-Object { $this.jobs.ContainsValue($_.id) -and $_.State -eq [JobState]::Completed -and ([SOURCE]$_.name -in $this.config.analyzedSources) })){
            #Write-Host "Mounting job $($job.name)"
            $result = (Receive-Job -id $job.id)
            if ($result){
                $this.fileContent[[SOURCE]($job.name)] = $result
            }
            else{
                $this.fileContent[[SOURCE]($job.name)] = ''
            }

            $this.jobs.Remove([SOURCE]($job.name))
            Remove-Job -id $job.id
        }
    }

    #endregion
}