Public/Storage/Get-VergeNASVolumeFile.ps1

function Get-VergeNASVolumeFile {
    <#
    .SYNOPSIS
        Lists files and directories in a NAS volume.

    .DESCRIPTION
        Get-VergeNASVolumeFile browses the contents of a NAS volume, listing
        files and directories at the specified path.

    .PARAMETER Volume
        The volume name or object to browse.

    .PARAMETER Path
        The directory path to list. Defaults to root (/).

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

    .EXAMPLE
        Get-VergeNASVolumeFile -Volume "FileShare"

        Lists files at the root of the FileShare volume.

    .EXAMPLE
        Get-VergeNASVolumeFile -Volume "FileShare" -Path "/documents"

        Lists files in the documents directory.

    .EXAMPLE
        Get-VergeNASVolume -Name "FileShare" | Get-VergeNASVolumeFile -Path "/backup"

        Lists files via pipeline.

    .OUTPUTS
        Verge.NASVolumeFile objects containing:
        - Name: File or directory name
        - Type: 'File' or 'Directory'
        - Size: File size in bytes
        - Modified: Last modified time
        - Permissions: Unix permissions string
        - Owner: File owner
        - Group: File group

    .NOTES
        The volume must be enabled to browse files.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline)]
        [Alias('VolumeName')]
        [object]$Volume,

        [Parameter(Position = 1)]
        [string]$Path = '/',

        [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 volume
            $volumeKey = $null
            $volumeName = $null

            if ($Volume -is [string]) {
                $volumeName = $Volume
                $volumeData = Get-VergeNASVolume -Name $Volume -Server $Server
                if (-not $volumeData) {
                    throw "Volume '$Volume' not found"
                }
                $volumeKey = $volumeData.Key
                $volumeName = $volumeData.Name
            }
            elseif ($Volume.Key) {
                $volumeKey = $Volume.Key
                $volumeName = $Volume.Name
            }

            if (-not $volumeKey) {
                throw "Could not resolve volume key"
            }

            # Normalize path - API uses empty string for root, not /
            $dir = $Path
            if ($dir -eq '/') {
                $dir = ''
            }
            elseif ($dir.StartsWith('/')) {
                $dir = $dir.Substring(1)
            }

            Write-Verbose "Browsing volume '$volumeName' at path '$Path'"

            # Create browse request - params is an object, not JSON string
            # Based on actual API payload from browser devtools
            $body = @{
                volume = $volumeKey
                query  = 'get-dir'
                params = @{
                    dir     = $dir
                    limit   = 1000
                    offset  = $null
                    filter  = @{
                        extensions = ''
                    }
                    volume  = $volumeKey
                    sort    = ''
                }
            }

            $response = Invoke-VergeAPI -Method POST -Endpoint 'volume_browser' -Body $body -Connection $Server

            if (-not $response) {
                throw "No response from volume browser"
            }

            $browseKey = $response.'$key' ?? $response.id

            # Poll for results (browser operations are async)
            $maxAttempts = 30
            $attempt = 0
            $result = $null
            $completed = $false

            while ($attempt -lt $maxAttempts) {
                Start-Sleep -Milliseconds 500
                $attempt++

                # Must explicitly request the result field - it's not returned by default
                $statusResponse = Invoke-VergeAPI -Method GET -Endpoint "volume_browser/$browseKey`?fields=id,status,result" -Connection $Server

                if ($statusResponse.status -eq 'complete') {
                    $result = $statusResponse.result
                    $completed = $true
                    break
                }
                elseif ($statusResponse.status -eq 'error') {
                    throw "Browse operation failed: $($statusResponse.result)"
                }

                Write-Verbose "Waiting for browse results (attempt $attempt/$maxAttempts)..."
            }

            if (-not $completed) {
                throw "Browse operation timed out"
            }

            # Handle empty directory (result is null)
            if ($null -eq $result) {
                Write-Verbose "Directory is empty at path '$Path'"
                return
            }

            # Parse result (it's JSON string in some cases)
            if ($result -is [string]) {
                $result = $result | ConvertFrom-Json
            }

            # Process directory entries - result is usually the array directly
            # Important: Check if result is an array first before trying .entries property
            # (accessing .property on an array in PowerShell accesses each element's property)
            $entries = if ($result -is [array]) {
                $result
            } elseif ($result.entries) {
                $result.entries
            } else {
                $result
            }

            if (-not $entries -or $entries.Count -eq 0) {
                Write-Verbose "No entries found at path '$Path'"
                return
            }

            foreach ($entry in $entries) {
                # Determine type - API returns "file" or "directory"
                $fileType = if ($entry.type -eq 'directory' -or $entry.type -eq 'd' -or $entry.is_dir) {
                    'Directory'
                } else {
                    'File'
                }

                # Convert modified time if present (API uses 'date' field)
                $modified = $null
                $timestamp = $entry.date ?? $entry.mtime
                if ($timestamp) {
                    $modified = [DateTimeOffset]::FromUnixTimeSeconds($timestamp).LocalDateTime
                }

                [PSCustomObject]@{
                    PSTypeName    = 'Verge.NASVolumeFile'
                    Name          = $entry.name
                    FullPath      = if ($Path -eq '/') { "/$($entry.name)" } else { "$Path/$($entry.name)" }
                    Type          = $fileType
                    Size          = $entry.size
                    SizeDisplay   = Format-FileSize -Bytes $entry.size
                    Modified      = $modified
                    Permissions   = $entry.perms ?? $entry.mode
                    Owner         = $entry.owner ?? $entry.uid
                    Group         = $entry.group ?? $entry.gid
                    VolumeName    = $volumeName
                    VolumeKey     = $volumeKey
                    _Connection   = $Server
                }
            }
        }
        catch {
            $displayName = $volumeName ?? $volumeKey ?? 'unknown'
            Write-Error -Message "Failed to browse volume '$displayName': $($_.Exception.Message)" -ErrorId 'GetVolumeFileFailed'
        }
    }
}

function Format-FileSize {
    param([long]$Bytes)

    if ($null -eq $Bytes -or $Bytes -eq 0) {
        return '0 B'
    }

    $sizes = 'B', 'KB', 'MB', 'GB', 'TB', 'PB'
    $order = 0
    $size = [double]$Bytes

    while ($size -ge 1024 -and $order -lt $sizes.Count - 1) {
        $order++
        $size = $size / 1024
    }

    if ($order -eq 0) {
        return "$Bytes B"
    }

    return "{0:N2} {1}" -f $size, $sizes[$order]
}