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, [bool] $downloaded){
        $start = Get-Date
        $this.startedAt = $start

        if ($downloaded) {
            $fileName = $this.config.getFileName($source, [DownloaderFactory]::current.fileExtension)
        }
        else {
            $fileName = $this.config.getFileName($source, '*')
        }

        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, [bool] $downloaded){
        $this.startedAt = Get-Date
        $this.async = $runAsJob

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

        $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(){
        if (-not $this.currentFile) { return $null }

        $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){
        if (-not $this.currentFile) { return 0 }

        $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){
        if (-not $this.currentFile) { return 0 }

        $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(){
        if (-not $this.currentFile) { return $true }
        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, $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
}