src/metadata/GraphManager.ps1

# Copyright 2020, Adam Edwards
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

. (import-script GraphCache)

ScriptClass GraphManager {

    static {
        $cache = $null

        const TimeStateKey TimeData
        const UriCacheStateKey UriCache
        const TypeStateKey TypeManager
        const MetadataSourceStateKey MetadataSource

        function __initialize {
            $this.cache = new-so GraphCache
            # Start an asynchronous load of the metadata unless this is disabled
            # This is only meant for user interactive sessions and should be
            # disabled if this module is used in background jobs
            if ( ! (get-variable -scope script -name '__poshgraph_no_auto_metadata' -erroraction ignore) ) {
                write-verbose "Asynchronously updating Graph metadata"
                UpdateGraph ($::.GraphContext |=> GetCurrent)
            } else {
                write-verbose "Found __poshgraph_no_auto_metadata variable, skipping Graph metadata update"
            }
        }

        function UpdateGraph($context, $metadata = $null, $wait = $false, $force = $false, $metadataSourceOverridePath) {
            $graphEndpoint = $context |=> GetEndpoint

            $updateSource = GetMetadataSource $context $metadataSourceOverridePath

            # Trigger a graph update -- we purposefully ignore the result as it may not be a graph
            # but instead an incomplete retrieval. The goal here is simply to trigger the update, not
            # to get the results.
            __GetGraph $graphEndpoint $context.version $metadata $wait $force $true $context.schemaId $updateSource | out-null

            # Clear any existing cache as it is no longer valid given that the graph is being updated.
            $uriCache = $context |=> GetState $this.UriCacheStateKey
            if ( $uriCache ) {
                $uriCache.Clear() # TODO: Need to change this to handle async retrieval of new graph
            }

            # Also remove any state for the TypeManager as the updated graph will invalidate it as well.
            $typeManager = $context |=> GetState $this.TypeStateKey

            if ( $typeManager ) {
                $context |=> RemoveState $this.TypeStateKey
            }

            # Set the time of creation if this context is new and otherwise
            # change the last updated time to reflect the time of this update
            $updateTime = [DateTimeOffset]::Now
            $timeData = $context |=> GetState $this.TimeStateKey

            if ( $timeData ) {
                $timeData.UpdatedTime = $updateTime
            } else {
                $timeData = [PSCustomObject] @{
                    CreatedTime = $updateTime
                    UpdatedTime = $updateTime
                }
            }

            $context |=> SetState $this.TimeStateKey $timeData

            # Finally set the location from which the graph's metadata is retrieved
            # to reflect what is being used in this update.
            $context |=> SetState $this.MetadataSourceStateKey $updateSource
        }

        function GetMetadataSource($context, $metadataSourceOverridePath) {
            $graphEndpoint = $context |=> GetEndpoint

            $currentSource = $context |=> GetState $this.MetadataSourceStateKey

            if ( $metadataSourceOverridePath ) {
                $scheme = ( [Uri] $metadataSourceOverridePath ).scheme
                if ( ! $scheme -or $scheme -eq 'file' ) {
                    # Note that this path may be specified as a file scheme URI or a normal file name string, so convert to a file
                    # name since get-item will not accept file scheme URIs
                    $translatedPath = ([Uri] $metadataSourceOverridePath).AbsolutePath # This can be null though also :)
                    $localPath = if ( $translatedPath ) {
                        $translatedPath
                    } else {
                        $metadataSourceOverridePath.ToString()
                    }
                    (get-item $localPath).FullName
                } else {
                    $metadataSourceOverridePath
                }
            } elseif ( $currentSource ) {
                $currentSource
            } else {
                # If we only have an API version and endpoint, form the default URI
                $graphEndpoint.tostring().trimend('/'), $context.version, '$metadata' -join '/'
            }
        }

        function GetMetadataStatus($context) {
            # See comments in __GetGraph about the still unknown need for this
            if ( ! $this.cache ) {
                __initialize
            }

            $this.cache |=> GetMetadataStatus $context.connection.GraphEndpoint.Graph $context.version $context.schemaId
        }

        function GetGraph($context, $metadata = $null, $force = $false) {
            $schemaUri = GetMetadataSource $context
             __GetGraph ($context |=> GetEndpoint) $context.version $metadata $true $force $false $context.schemaId $schemaUri
        }

        function __GetGraph($endpoint, $apiVersion, $metadata, $wait = $false, $force = $false, $forceupdate = $false, $schemaId, $schemaUri) {
            # This really should not be necessary since __initialize is called at the script level, but there
            # seems to be an issue when executing in automated CI where class members are not initialized,
            # possibly due to some inability to call __initialize at some point -- we call it here if we detect
            # uninitalized state and this seems to "fix" the CI.
            if ( ! $this.cache ) {
                __initialize
            }

            if ( $Force ) {
                $this.cache |=> CancelPendingGraph $endpoint $apiVersion
            }

            if ( $wait -and ! $forceupdate ) {
                $this.cache |=> GetGraph $endpoint $apiVersion $metadata $schemaId $schemaUri
            } else {

                $asyncResult = $this.cache |=> GetGraphAsync $endpoint $apiVersion $metadata $schemaId $schemaUri

                if ( $wait ) {
                    $this.cache |=> WaitForGraphAsync $asyncResult
                } else {
                    $asyncResult
                }
            }
        }
    }
}

$::.GraphManager |=> __initialize