Utils.ps1

function Write-Offline-Broadcast() {
    Write-Output @"
==== BROADCAST =================================================================
 
OFFLINE MODE ENABLED! Some functionality is now disabled.
 
================================================================================
"@

}

function Write-Online-Broadcast() {
    Write-Output @"
==== BROADCAST =================================================================
 
ONLINE MODE RE-ENABLED! All functionality now restored.
 
================================================================================
 
"@

}

function Write-New-Version-Broadcast() {
    if ( $Script:PSDK_API_NEW_VERSION -or $Script:PSDK_NEW_VERSION ) {
Write-Output @"
==== UPDATE AVAILABLE ==========================================================
 
A new version is available. Please consider to execute:
 
    sdk selfupdate
 
================================================================================
"@

    }
}

function Test-SDKMAN-API-Version() {
    Write-Verbose 'Checking PSDK-Api version'
    try {
        $apiVersion = Get-SDKMAN-API-Version
        $sdkmanRemoteVersion = Invoke-API-Call "broker/download/sdkman/version/stable"

        if ( $sdkmanRemoteVersion -gt $apiVersion) {
            if ( $Global:PSDK_AUTO_SELFUPDATE ) {
                Invoke-Self-Update
            } else {
                $Script:PSDK_API_NEW_VERSION = $true
            }
        }
    } catch {
        $Script:PSDK_AVAILABLE = $false
    }
}

function Test-Posh-SDK-Version() {
    Write-Verbose 'Checking posh-sdk version'
    if ( Test-New-Posh-SDK-Version-Available ) {
        if ( $Global:PSDK_AUTO_SELFUPDATE ) {
            Invoke-Self-Update
        } else {
            $Script:PSDK_NEW_VERSION = $true
        }
    }
}

function Get-Posh-SDK-Version() {
    return Get-Content $Script:PSDK_VERSION_PATH
}

function Test-New-Posh-SDK-Version-Available() {
    try {
        $localVersion = (Get-Posh-SDK-Version).Trim()
        $currentVersion = (Invoke-RestMethod $Script:PSDK_VERSION_SERVICE).Trim()

        Write-Verbose "posh-sdk version check $currentVersion > $localVersion = $($currentVersion -gt $localVersion)"

        return ( $currentVersion -gt $localVersion )
    } catch {
        return $false
    }
}

function Get-SDKMAN-API-Version() {
    if ( !(Test-Path $Script:PSDK_API_VERSION_PATH) ) {
        return $null
    }
    return Get-Content $Script:PSDK_API_VERSION_PATH
}

function Test-Available-Broadcast($Command) {
    $version = Get-SDKMAN-API-Version
    if ( !( $version ) ) {
        return
    }

    $liveBroadcast = Invoke-Broadcast-API-Call

    Write-Verbose "Online-Mode: $Script:PSDK_AVAILABLE"

    if ( $Script:PSDK_ONLINE -and !($Script:PSDK_AVAILABLE) ) {
        Write-Offline-Broadcast
    } elseif ( !($Script:PSDK_ONLINE) -and $Script:PSDK_AVAILABLE ) {
        Write-Online-Broadcast
    }
    $Script:PSDK_ONLINE = $Script:PSDK_AVAILABLE

    if ( $liveBroadcast ) {
        Resolve-Broadcast $Command $liveBroadcast
    }
}

function Invoke-Broadcast-API-Call {
    try {
        $target = "$Script:PSDK_BROADCAST_SERVICE/broadcast/latest"
        Write-Verbose "Broadcast API call to: $target"
        return Invoke-RestMethod $target
    } catch {
        Write-Verbose "Could not reached broadcast API"
        $Script:PSDK_AVAILABLE = $false
        return $null
    }
}

function Invoke-Self-Update($Force) {
    Write-Verbose 'Perform Invoke-Self-Update'
    Write-Output 'Update list of available candidates...'
    Update-Candidates-Cache
    $Script:PSDK_API_NEW_VERSION = $false
    if ( $Force ) {
        Invoke-Posh-SDK-Update
    } else {
        if ( Test-New-Posh-SDK-Version-Available ) {
            Invoke-Posh-SDK-Update
        }
    }
    $Script:PSDK_NEW_VERSION = $false
}

function Invoke-Posh-SDK-Update {
    Write-Output 'Update posh-sdk...'
    . "$psScriptRoot\GetPoshSDK.ps1"
}

function Test-Candidate-Present($Candidate) {
    if ( !($Candidate) ) {
        throw 'No candidate provided.'
    }

    if ( !($Script:SDK_CANDIDATES -contains $Candidate) ) {
        throw "Stop! $Candidate is no valid candidate!"
    }
}

function Test-Version-Present($Version) {
    if ( !($Version)) {
        throw 'No version provided.'
    }
}

function Test-Candidate-Version-Available($Candidate, $Version) {
    Test-Candidate-Present $Candidate

    $UseDefault = $false
    if ( !($Version) ) {
        Write-Verbose 'No version provided. Fallback to default version!'
        $UseDefault = $true
    }

    # Check locally
    elseif ( Test-Is-Candidate-Version-Locally-Available $Candidate $Version ) {
        return $Version
    }

    # Check if offline
    if ( ! (Get-Online-Mode) ) {
        if ( $UseDefault ) {
            $Version = Get-Current-Candidate-Version $Candidate
            if ( $Version ) {
                return $Version
            } else {
                throw "Stop! No local default version for $Candidate and in offline mode."
            }
        }

        throw "Stop! $Candidate $Version is not available in offline mode."
    }

    if ( $UseDefault ) {
        Write-Verbose 'Try to get default version from remote'
        return Invoke-API-Call "candidates/default/$Candidate"
    }

    $VersionAvailable = Invoke-API-Call "candidates/validate/$Candidate/$Version/cygwin"

    if ( $VersionAvailable -eq 'valid' ) {
        return $Version
    } else {
        throw "Stop! $Version is not a valid $Candidate version."
    }
}

function Get-Current-Candidate-Version($Candidate) {
    $currentLink = "$Global:PSDK_DIR\$Candidate\current"

    $targetItem = Get-Junction-Target $currentLink

    if ($targetItem) {
        return $targetItem.Name
    }

    return $null
}

function Get-Junction-Target($linkPath) {
    if ( Test-Path $linkPath ) {
        try {
            $linkItem = Get-Item $linkPath

            if (Get-Member -InputObject $linkItem -Name "ReparsePoint") {
                return (Get-Item $linkItem.ReparsePoint.Target)
            }

            if (Get-Member -InputObject $linkItem -Name "Target") {
                return (Get-Item $linkItem.Target)
            }
        } catch {
            return $null
        }
    }

    return $null
}

function Get-Env-Candidate-Version($Candidate) {
    $envLink = [System.Environment]::GetEnvironmentVariable(([string]$Candidate).ToUpper() + "_HOME")

    if ( $envLink -match '(.*)current$' ) {
        Get-Current-Candidate-Version $Candidate
    } else {
        return (Get-Item $envLink).Name
    }
}

function Test-Candidate-Version-Locally-Available($Candidate, $Version) {
    if ( !(Test-Is-Candidate-Version-Locally-Available $Candidate $Version) ) {
        throw "Stop! $Candidate $Version is not installed."
    }
}

function Test-Is-Candidate-Version-Locally-Available($Candidate, $Version) {
    if ( $Version ) {
        return Test-Path "$Global:PSDK_DIR\$Candidate\$Version"
    } else {
        return $false
    }
}

function Get-Installed-Candidate-Version-List($Candidate) {
    return Get-ChildItem "$Global:PSDK_DIR\$Candidate" | ?{ $_.PSIsContainer -and $_.Name -ne 'current' } | Foreach { $_.Name }
}

function Get-Online-Candidate-Version-List($Candidate) {
    $versions = Select-String "\d+(\.\w*\d*)*(-(\w|\d)*)?" -InputObject (Get-Version-List $Candidate) -AllMatches
    $resultVersions =  $versions.Matches.Captures |  ForEach-Object { Write-Output $_.Value }
    return $resultVersions
}

function Set-Env-Candidate-Version($Candidate, $Version) {
    $candidateEnv = ([string]$candidate).ToUpper() + "_HOME"
    $candidateDir = "$Global:PSDK_DIR\$candidate"
    $candidateHome = "$candidateDir\$Version"
    $candidateBin = "$candidateHome\bin"

    if ( !([Environment]::GetEnvironmentVariable($candidateEnv) -eq $candidateHome) ) {
        [Environment]::SetEnvironmentVariable($candidateEnv, $candidateHome)
    }

    $env:PATH = "$candidateBin;$env:PATH"
}

function Set-Linked-Candidate-Version($Candidate, $Version) {
    $Link = "$Global:PSDK_DIR\$Candidate\current"
    $Target = "$Global:PSDK_DIR\$Candidate\$Version"
    Set-Junction-Via-Mklink $Link $Target
}

function Set-Junction-Via-Mklink($Link, $Target) {
    if ( Test-Path $Link ) {
        (Get-Item $Link).Delete()
    }

    Invoke-Expression -Command "cmd.exe /c mklink /J '$Link' '$Target'" | Out-Null
}

function Get-Online-Mode() {
    return $Script:PSDK_AVAILABLE -and ! ($Script:PSDK_FORCE_OFFLINE)
}

function Test-Online-Mode() {
    if ( ! (Get-Online-Mode) ) {
        throw 'This command is not available in offline mode.'
    }
}

function Invoke-API-Call([string]$Path, [string]$FileTarget, [switch]$IgnoreFailure) {
    try {
        $target = "$Script:PSDK_SERVICE/$Path"

        if ( $FileTarget ) {
            return Invoke-RestMethod $target -OutFile $FileTarget
        }

        return Invoke-RestMethod $target
    } catch {
        $Script:PSDK_AVAILABLE = $false
        if ( ! ($IgnoreFailure) ) {
            Test-Online-Mode
        } else {
            return $null
        }
    }
}

function Clear-Directory($Path) {
    $dirStats = Get-ChildItem $Path -Recurse | Measure-Object -property length -sum
    Remove-Item -Force -Recurse $Path
    $count = $dirStats.Count
    $size = $dirStats.Sum/(1024*1024)
    Write-Output "$count archive(s) flushed, freeing $size MB"
}

function Resolve-Broadcast($Command, $Broadcast) {
    $oldBroadcast = $null
    if (Test-Path $Script:PSDK_BROADCAST_PATH) {
        $oldBroadcast = (Get-Content $Script:PSDK_BROADCAST_PATH) -join "`n"
        Write-Verbose 'Old broadcast message loaded'
    }

    if ($oldBroadcast -ne $Broadcast -and !($Command -match 'b(roadcast)?') -and $Command -ne 'selfupdate' -and $Command -ne 'flush' ) {
        Write-Verbose 'Showing the new broadcast message'
        Set-Content $Script:PSDK_BROADCAST_PATH $Broadcast
        Write-Output $Broadcast
    }
}

function Initialize-Candidate-Cache() {
    if ( !(Test-Path $Script:PSDK_CANDIDATES_PATH) ) {
        throw 'Can not retrieve list of candidates'
    }

    $Script:SDK_CANDIDATES = (Get-Content $Script:PSDK_CANDIDATES_PATH).Split(',')
    Write-Verbose "Available candidates: $Script:SDK_CANDIDATES"
}

function Update-Candidates-Cache() {
    Write-Verbose 'Update candidates-cache from PSDK-Api'
    Test-Online-Mode
    Invoke-Api-Call 'broker/download/sdkman/version/stable' $Script:PSDK_API_VERSION_PATH
    Invoke-API-Call 'candidates/all' $Script:PSDK_CANDIDATES_PATH
}

function Write-Offline-Version-List($Candidate) {
    Write-Verbose 'Get version list from directory'

    Write-Output '------------------------------------------------------------'
    Write-Output "Offline Mode: only showing installed ${Candidate} versions"
    Write-Output '------------------------------------------------------------'
    Write-Output ''

    $current = Get-Current-Candidate-Version $Candidate
    $versions = Get-Installed-Candidate-Version-List $Candidate

    if ($versions) {
        foreach ($version in $versions) {
            if ($version -eq $current) {
                Write-Output " > $version"
            } else {
                Write-Output " * $version"
            }
        }
    } else {
        Write-Output ' None installed!'
    }

    Write-Output '------------------------------------------------------------'
    Write-Output '* - installed '
    Write-Output '> - currently in use '
    Write-Output '------------------------------------------------------------'
}

function Write-Version-List($Candidate) {
    Write-Verbose 'Write version list from API to CLI'
    Write-Output (Get-Version-List $Candidate)
}

function Get-Version-List($Candidate) {
    Write-Verbose 'Get version list from API'

    $current = Get-Current-Candidate-Version $Candidate
    $versions = (Get-Installed-Candidate-Version-List $Candidate) -join ','
    return Invoke-API-Call "candidates/$Candidate/cygwin/versions/list?current=$current&installed=$versions"
}

function Install-Local-Version($Candidate, $Version, $LocalPath) {
    $dir = Get-Item $LocalPath

    if ( !(Test-Path $dir -PathType Container) ) {
        throw "Local installation path $LocalPath is no directory"
    }

    Write-Output "Linking $Candidate $Version to $LocalPath"
    $link = "$Global:PSDK_DIR\$Candidate\$Version"
    Set-Junction-Via-Mklink $link $LocalPath
    Write-Output "Done installing!"
}

function Install-Remote-Version($Candidate, $Version) {

    if ( !(Test-Path $Script:PSDK_ARCHIVES_PATH) ) {
        New-Item -ItemType Directory $Script:PSDK_ARCHIVES_PATH | Out-Null
    }

    $archive = "$Script:PSDK_ARCHIVES_PATH\$Candidate-$Version.zip"
    if ( Test-Path $archive ) {
        Write-Output "Found a previously downloaded $Candidate $Version archive. Not downloading it again..."
    } else {
        Test-Online-Mode
        Write-Output "`nDownloading: $Candidate $Version`n"
        Get-File-From-Url "$Script:PSDK_SERVICE/broker/download/$Candidate/$Version`/cygwin" $archive $Candidate $Version
    }

    Write-Output "Installing: $Candidate $Version"

    # create temp dir if necessary
    if ( !(Test-Path $Script:PSDK_TEMP_PATH) ) {
        New-Item -ItemType Directory $Script:PSDK_TEMP_PATH | Out-Null
    } else {
        # clean existing temp dir
        Remove-Item "$Script:PSDK_TEMP_PATH\*.*" -Recurse -Force
    }

    # unzip downloaded archive
    Expand-Archive -Path $archive -DestinationPath $Script:PSDK_TEMP_PATH -Force

    # check if unzip successfully
    if ( ((Get-ChildItem -Directory $Script:PSDK_TEMP_PATH).count -ne 1) ) {
        throw "Could not unzip the archive of $Candidate $Version. Please delete archive from $Script:PSDK_ARCHIVES_PATH (or delete all with 'sdk flush archives'"
    }

    # needed to create the folder ahead. Else the copy process failed on the first try
    New-Item -ItemType Directory "$Global:PSDK_DIR\$Candidate\$Version" | Out-Null
    # move to target location
    # Move was replaced by copy and remove because of random access denied errors
    # when Unzip was done by via -com shell.application
    # Move-Item "$Script:PSDK_TEMP_PATH\*-$Version" "$Global:PSDK_DIR\$Candidate\$Version"
    Copy-Item -Path "$Script:PSDK_TEMP_PATH\$((Get-ChildItem -Directory $Script:PSDK_TEMP_PATH).name)\*" -Destination "$Global:PSDK_DIR\$Candidate\$Version" -Recurse
    Remove-Item "$Script:PSDK_TEMP_PATH\*.*" -Recurse -Force
    Write-Output "Done installing!"
}

function Get-File-From-Url($Url, $TargetFile, $Candidate, $Version) {
    <#
        Adepted from http://blogs.msdn.com/b/jasonn/archive/2008/06/13/downloading-files-from-the-internet-in-powershell-with-progress.aspx
    #>

    Write-Verbose "Try to download $Url with HttpWebRequest"
    $tempProgressPreference = $ProgressPreference
    $ProgressPreference = 'Continue'
    $uri = New-Object "System.Uri" $Url
    $request = [System.Net.HttpWebRequest]::Create($uri)
    $request.set_Timeout(15000)
    $response = $request.GetResponse()
    $totalLength = [System.Math]::Floor($response.get_ContentLength()/1024)
    $responseStream = $response.GetResponseStream()
    $targetStream = New-Object -TypeName System.IO.FileStream -ArgumentList $targetFile, Create
    $buffer = new-object byte[] 1000KB
    $count = $responseStream.Read($buffer,0,$buffer.length)
    $downloadedBytes = $count
    while ($count -gt 0)
    {
        if ($totalLength -lt 0) {
            $totalLength = [System.Math]::Floor($response.get_ContentLength()/1024)
        }
        $currentPercentage = ([System.Math]::Floor(($downloadedBytes/1024)* 100/ $totalLength) )
        Write-Progress -Activity "Download $Candidate $Version" -Status "Downloaded $([System.Math]::Ceiling($downloadedBytes/1024))kB of $([System.Math]::Ceiling($totalLength))kB ($currentPercentage %)" -PercentComplete $currentPercentage
        $targetStream.Write($buffer, 0, $count)
        $count = $responseStream.Read($buffer,0,$buffer.length)
        $downloadedBytes = $downloadedBytes + $count
    }
    Write-Progress -Activity "Download $Candidate $Version" -Completed
    $targetStream.Flush()
    $targetStream.Close()
    $targetStream.Dispose()
    $responseStream.Dispose()
    Write-Output ''
    $ProgressPreference = $tempProgressPreference
}