Public/Storage/Save-VergeFile.ps1

function Save-VergeFile {
    <#
    .SYNOPSIS
        Downloads a file from the VergeOS media catalog.

    .DESCRIPTION
        Save-VergeFile downloads a file from the VergeOS files catalog to a local path.
        This can be used to export ISOs, disk images, or other files.

    .PARAMETER Name
        The name of the file to download from VergeOS.

    .PARAMETER Key
        The key (ID) of the file to download.

    .PARAMETER File
        A file object from Get-VergeFile.

    .PARAMETER Destination
        The local path where the file should be saved. Can be a directory or full file path.
        Defaults to the current directory.

    .PARAMETER Force
        Overwrite the destination file if it already exists.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Save-VergeFile -Name "ubuntu-22.04.iso" -Destination "C:\Downloads"

        Downloads the ISO to C:\Downloads\ubuntu-22.04.iso

    .EXAMPLE
        Get-VergeFile -Type iso | Save-VergeFile -Destination "/backup/isos"

        Downloads all ISO files to the backup directory.

    .EXAMPLE
        Save-VergeFile -Name "server.qcow2" -Destination "./server-backup.qcow2" -Force

        Downloads a disk image, overwriting if it exists.

    .OUTPUTS
        System.IO.FileInfo object for the downloaded file.

    .NOTES
        Large files may take significant time to download depending on network speed.
        Ensure sufficient disk space at the destination.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByName')]
    [OutputType([System.IO.FileInfo])]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByName')]
        [string]$Name,

        [Parameter(Mandatory, ParameterSetName = 'ByKey')]
        [int]$Key,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByObject')]
        [PSTypeName('Verge.File')]
        [PSCustomObject]$File,

        [Parameter(Position = 1)]
        [string]$Destination = '.',

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }
    }

    process {
        try {
            # Resolve file info
            $fileInfo = $null
            $fileName = $null
            $fileKey = $null

            switch ($PSCmdlet.ParameterSetName) {
                'ByName' {
                    $fileName = $Name
                    $fileInfo = Get-VergeFile -Name $Name -Server $Server
                    if (-not $fileInfo) {
                        throw "File '$Name' not found"
                    }
                    $fileKey = $fileInfo.Key
                    $fileName = $fileInfo.Name
                }
                'ByKey' {
                    $fileKey = $Key
                    $fileInfo = Get-VergeFile -Key $Key -Server $Server
                    if (-not $fileInfo) {
                        throw "File with key $Key not found"
                    }
                    $fileName = $fileInfo.Name
                }
                'ByObject' {
                    $fileKey = $File.Key
                    $fileName = $File.Name
                    $fileInfo = $File
                }
            }

            if (-not $fileKey) {
                throw "Could not resolve file key"
            }

            # Determine output path
            $outputPath = $Destination
            if (Test-Path -Path $Destination -PathType Container) {
                $outputPath = Join-Path -Path $Destination -ChildPath $fileName
            }
            elseif (-not (Test-Path -Path (Split-Path -Path $Destination -Parent))) {
                throw "Destination directory does not exist: $(Split-Path -Path $Destination -Parent)"
            }

            # Check if file exists
            if ((Test-Path -Path $outputPath) -and -not $Force) {
                throw "File already exists at '$outputPath'. Use -Force to overwrite."
            }

            # Build download URL
            # Format: /api/v4/files/{key}?download=1&asname={filename}
            $encodedFileName = [System.Uri]::EscapeDataString($fileName)
            $downloadUrl = "$($Server.ApiBaseUrl)/files/$fileKey`?download=1&asname=$encodedFileName"

            $fileSizeMB = if ($fileInfo.SizeGB) { [math]::Round($fileInfo.SizeGB * 1024, 2) } else { 'unknown' }
            Write-Verbose "Downloading '$fileName' ($fileSizeMB MB) to '$outputPath'"

            if ($PSCmdlet.ShouldProcess("File '$fileName'", "Download to '$outputPath'")) {
                # Build authorization header
                $authType = if ($Server.AuthType) { $Server.AuthType } else { 'Basic' }
                $authHeader = "$authType $($Server.Token)"

                $requestParams = @{
                    Method  = 'GET'
                    Uri     = $downloadUrl
                    Headers = @{
                        'Authorization' = $authHeader
                    }
                    OutFile = $outputPath
                }

                if ($Server.SkipCertificateCheck) {
                    $requestParams['SkipCertificateCheck'] = $true
                }

                Write-Verbose "Downloading from: $downloadUrl"

                # Perform download
                Invoke-WebRequest @requestParams -ErrorAction Stop

                Write-Verbose "Download completed successfully"

                # Return file info
                Get-Item -Path $outputPath
            }
        }
        catch {
            $displayName = $fileName ?? $Name ?? $Key ?? 'unknown'
            Write-Error -Message "Failed to download file '$displayName': $($_.Exception.Message)" -ErrorId 'SaveVergeFileFailed'
        }
    }
}