Private/Core/CacheManager.ps1

function Get-DATCachedItem {
    <#
    .SYNOPSIS
        Retrieves a cached item if it exists and hasn't expired.
    .PARAMETER Key
        Unique cache key (e.g., 'Dell_DriverPackCatalog', 'Lenovo_CatalogV2').
    .PARAMETER MaxAgeHours
        Maximum age in hours before the cache entry is considered stale. Default: 24.
    .OUTPUTS
        Returns the cached file path if valid, or $null if expired/missing.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Key,

        [int]$MaxAgeHours = 24
    )

    $MetaFile = Join-Path $script:CachePath "$Key.meta.json"
    $DataFile = Join-Path $script:CachePath $Key

    if (-not (Test-Path $MetaFile) -or -not (Test-Path $DataFile)) {
        Write-Verbose "Cache miss for key: $Key"
        return $null
    }

    $Meta = Get-Content $MetaFile -Raw | ConvertFrom-Json
    $CachedTime = [datetime]::Parse($Meta.cachedAt)
    $Age = (Get-Date) - $CachedTime

    if ($Age.TotalHours -gt $MaxAgeHours) {
        Write-Verbose "Cache expired for key: $Key (age: $([math]::Round($Age.TotalHours, 1)) hours)"
        return $null
    }

    # Verify integrity if hash is stored
    if ($Meta.sha256) {
        $ActualHash = (Get-FileHash -Path $DataFile -Algorithm SHA256).Hash
        if ($ActualHash -ne $Meta.sha256) {
            Write-DATLog -Message "Cache integrity check failed for $Key. Removing corrupted entry." -Severity 2
            Remove-Item $MetaFile -Force -ErrorAction SilentlyContinue
            Remove-Item $DataFile -Force -ErrorAction SilentlyContinue
            return $null
        }
    }

    Write-Verbose "Cache hit for key: $Key (age: $([math]::Round($Age.TotalHours, 1)) hours)"
    return $DataFile
}

function Set-DATCachedItem {
    <#
    .SYNOPSIS
        Stores a file in the cache with metadata.
    .PARAMETER Key
        Unique cache key.
    .PARAMETER SourcePath
        Path to the file to cache (will be copied to cache directory).
    .PARAMETER SourceUrl
        Original URL the file was downloaded from (for metadata).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Key,

        [Parameter(Mandatory)]
        [string]$SourcePath,

        [string]$SourceUrl
    )

    if (-not (Test-Path $script:CachePath)) {
        New-Item -Path $script:CachePath -ItemType Directory -Force | Out-Null
    }

    $DataFile = Join-Path $script:CachePath $Key
    $MetaFile = Join-Path $script:CachePath "$Key.meta.json"

    Copy-Item -Path $SourcePath -Destination $DataFile -Force
    $Hash = (Get-FileHash -Path $DataFile -Algorithm SHA256).Hash

    $Meta = @{
        key       = $Key
        cachedAt  = (Get-Date).ToString('o')
        sourceUrl = $SourceUrl
        sha256    = $Hash
        sizeBytes = (Get-Item $DataFile).Length
    }

    $Meta | ConvertTo-Json | Set-Content -Path $MetaFile -Encoding UTF8

    Write-Verbose "Cached item: $Key ($([math]::Round($Meta.sizeBytes / 1MB, 2)) MB)"
    return $DataFile
}

function Remove-DATCachedItem {
    <#
    .SYNOPSIS
        Removes a specific item from the cache.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Key
    )

    $DataFile = Join-Path $script:CachePath $Key
    $MetaFile = Join-Path $script:CachePath "$Key.meta.json"

    Remove-Item $DataFile -Force -ErrorAction SilentlyContinue
    Remove-Item $MetaFile -Force -ErrorAction SilentlyContinue
}

function Clear-DATCache {
    <#
    .SYNOPSIS
        Clears all cached items, or only items older than a specified age.
    .PARAMETER OlderThanHours
        Only remove items older than this many hours. If not specified, removes everything.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [int]$OlderThanHours
    )

    if (-not (Test-Path $script:CachePath)) { return }

    $MetaFiles = Get-ChildItem -Path $script:CachePath -Filter '*.meta.json' -ErrorAction SilentlyContinue

    foreach ($MetaFile in $MetaFiles) {
        $ShouldRemove = $true

        if ($OlderThanHours) {
            $Meta = Get-Content $MetaFile.FullName -Raw | ConvertFrom-Json
            $CachedTime = [datetime]::Parse($Meta.cachedAt)
            $Age = (Get-Date) - $CachedTime
            $ShouldRemove = ($Age.TotalHours -gt $OlderThanHours)
        }

        if ($ShouldRemove) {
            $Key = $MetaFile.BaseName -replace '\.meta$', ''
            if ($PSCmdlet.ShouldProcess($Key, 'Remove cached item')) {
                $DataFile = Join-Path $script:CachePath $Key
                Remove-Item $MetaFile.FullName -Force -ErrorAction SilentlyContinue
                Remove-Item $DataFile -Force -ErrorAction SilentlyContinue
                Write-Verbose "Removed cached item: $Key"
            }
        }
    }
}

function Get-DATCacheInfo {
    <#
    .SYNOPSIS
        Returns information about all cached items.
    #>

    [CmdletBinding()]
    param()

    if (-not (Test-Path $script:CachePath)) { return @() }

    $MetaFiles = Get-ChildItem -Path $script:CachePath -Filter '*.meta.json' -ErrorAction SilentlyContinue

    foreach ($MetaFile in $MetaFiles) {
        $Meta = Get-Content $MetaFile.FullName -Raw | ConvertFrom-Json
        $Key = $MetaFile.BaseName -replace '\.meta$', ''
        $DataFile = Join-Path $script:CachePath $Key

        [PSCustomObject]@{
            Key       = $Key
            CachedAt  = [datetime]::Parse($Meta.cachedAt)
            AgeHours  = [math]::Round(((Get-Date) - [datetime]::Parse($Meta.cachedAt)).TotalHours, 1)
            SizeMB    = if (Test-Path $DataFile) { [math]::Round((Get-Item $DataFile).Length / 1MB, 2) } else { 0 }
            SourceUrl = $Meta.sourceUrl
            SHA256    = $Meta.sha256
        }
    }
}