main/stores/CliFileStore.ps1
<#
.SYNOPSIS Config Store implementation based on CliXml serialization #> class CliFileStore : ConfigBaseStore { # Static Members static [string] $Name = 'CliFileStore' static [Version] $Version = [Version] '0.1.0' static [string] $PolicyTypeName = 'System.xUtility.RetryPolicy' static $OptionTypes = @([TimeSpan], [ScriptBlock], [System.IO.FileInfo]) # Public variables [string] $CacheControlType [string] $CacheId # NOTE: Store additional custom cache control types at this level [TimeSpan] $TimedCacheControl [ScriptBlock] $CustomCacheControl # Internal members hidden [int] $ProcessId = $PID hidden [string] $FilePath hidden [PSCustomObject] $Policy hidden [ScriptBlock] $GetFromSource = { $data = @{} if ((Test-Path $this.FilePath)) { $data = Invoke-ScriptBlockWithRetry -Context { Import-Clixml -Path $this.FilePath } -RetryPolicy $this.Policy } Write-Output $data } # Constructor CliFileStore() : base() { } # Creates a new instance of the store # @Override [CliFileStore] NewInstance([string] $HiveName, [CacheStoreLevel] $Level) { $store = New-Object CliFileStore $store.StoreName = [CliFileStore]::Name $store.StoreVersion = [CliFileStore]::Version $store.HiveName = $HiveName $store.StoreLevel = $Level $store.IsInitialized = $true # Calculate the file associated with the given Hive Name $store.FilePath = [string]::Empty # Calculate cache key to use with this instance $store.CacheId = "{0}.{1}.{2}" -f [CliFileStore]::Name, $Level.ToString(), $HiveName $store.Policy = New-RetryPolicy -Policy Random -Milliseconds 5000 -Retries 3 $cacheLength = [TimeSpan] '0:0:5' $store.SetCustomParams($cacheLength) return $store } # Creates a new instance of the store using serialized data # @Override [CliFileStore] Rehydrate([string] $SerializedData) { $data = $SerializedData | ConvertFrom-Json $hydratedStore = $this.NewInstance($data.HiveName, $data.Level) if ($data.TimedCacheControl -ne $null) { $timeControl = [TimeSpan] $data.TimedCacheControl $hydratedStore.SetCustomParams($timeControl) } elseif ($data.CustomCacheControl -ne $null) { $customControl = [ScriptBlock]::Create($data.CustomCacheControl) $hydratedStore.SetCustomParams($customControl) } else { $m = 'Unable to initialize store properly, required data is not present' $err = New-Object ConfigHiveError -ArgumentList 'InvalidImplementation', $m throw($err) } if ($data.FilePath -ne $null) { $file = [System.IO.FileInfo] $data.FilePath $hydratedStore.SetCustomParams($file) } return $hydratedStore } # Calculates the file to be used given the specified hive name and level [string] GetTargetFilePath([CacheStoreLevel] $Level, [string] $HiveName) { $basePath = [string]::Empty switch ($Level) { ([CacheStoreLevel]::Origin) { # NOTE: This operation requires Administrator permissions $basePath = Get-ProgramDataPath } ([CacheStoreLevel]::System) { $basePath = Get-ProgramDataPath } ([CacheStoreLevel]::User) { $basePath = Get-AppDataPath # } ([CacheStoreLevel]::Session) { $basePath = Get-AppDataPath } Default { $m = "Cli File Store: Unsupported store level: {0}" -f $Level.ToString() $err = New-Object ConfigHiveError -ArgumentList 'UnsupportedStoreLevel', $m throw($err) } } $targetFilePath = "{0}.xml" -f $HiveName $basePath = Join-Path -Path $basePath -ChildPath 'HiveData' $basePath = Join-Path -Path $basePath -ChildPath ([CliFileStore]::Name) if ($Level -eq [CacheStoreLevel]::Session) { $procId = $this.ProcessId $levelId = "{0}-{1}" -f $Level.ToString(), $procId $basePath = Join-Path -Path $basePath -ChildPath $levelId } else { $basePath = Join-Path -Path $basePath -ChildPath $Level.ToString() } if (-not (Test-Path $basePath)) { New-Item -ItemType Directory -Path $basePath | Write-Verbose } $targetFilePath = Join-Path -Path $basePath -ChildPath $targetFilePath return $targetFilePath } # Determines whether custom parameters are required or not # @Override [bool] RequiresCustomParams() { return $false } # Custom parameters for the store, valid data: [TimeSpan], [ScriptBlock] # @Override [void] SetCustomParams($CustomParams) { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } $pType = $CustomParams.GetType() if ([CliFileStore]::OptionTypes -contains $pType) { # NOTE: Add custom supported types here if ($pType -eq [TimeSpan]) { $this.TimedCacheControl = $CustomParams $this.CacheControlType = $pType.ToString() } elseif ($pType -eq [ScriptBlock]) { $this.CustomCacheControl = $CustomParams $this.CacheControlType = $pType.ToString() } elseif ($pType -eq [System.IO.FileInfo]) { $file = [System.IO.FileInfo] $CustomParams if ($file.Extension -ne '.xml') { $m = "Cannot use a file with extension '{0}' for serialization, must use '.xml'" -f $file.Extension $err = New-Object ConfigHiveError -ArgumentList 'InvalidArgument', $m throw($err) } $this.FilePath = $file.FullName } else { $m = "Support for Cache Control Type '{0}' is not implemented appropriately" -f $pType $err = New-Object ConfigHiveError -ArgumentList 'InvalidImplementation', $m throw($err) } } else { $cts = [CliFileStore]::OptionTypes -join ', ' $m = "Invalid Custom Parameter of type '{0}', valid values are {1}" -f $pType, $cts $err = New-Object ConfigHiveError -ArgumentList 'InvalidArgument', $m throw($err) } $this.ResetCache() } # Default values for the store # @Override [void] InitializeStore([HashTable] $Values) { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } if ((Test-Path $this.FilePath)) { Warn -Message 'Store has been previously initialized' } if ($this.FilePath -eq [string]::Empty) { $this.FilePath = $this.GetTargetFilePath($this.StoreLevel, $this.HiveName) } Invoke-ScriptBlockWithRetry -Context { $Values | Export-Clixml -Path $this.FilePath } -RetryPolicy $this.Policy $this.ResetCache() } # Resets the cache [void] ResetCache() { $p = @{} switch ($this.CacheControlType) { ([TimeSpan].ToString()) { $p = @{ Key = $this.CacheId ItemDefinition = $this.GetFromSource Expiration = $this.TimedCacheControl Force = $true } } ([ScriptBlock].ToString()) { $p = @{ Key = $this.CacheId ItemDefinition = $this.GetFromSource CustomTrigger = $this.CustomCacheControl Force = $true } } Default { $m = "Support for Cache Control Type '{0}' is not implemented appropriately" -f $this.CacheControlType $err = New-Object ConfigHiveError -ArgumentList 'InvalidImplementation', $m throw($err) } } Add-ExpiringCacheItem @p } # Gets a value from the store # @Override [HashTable] GetValue([string] $Key) { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } $data = [HashTable] (Get-ExpiringCacheItem -Key $this.CacheId) if (-not ($data.Keys -contains $Key)) { $m = "[{0}] Store does not contain key '{1}'" -f $this.StoreName, $Key $err = New-Object ConfigHiveError -ArgumentList 'ValueNotFound', $m throw($err) } $r = @{ 'Name' = $Key 'Value' = $data[$Key] } return $r } # Sets a value to the store # @Override [void] SetValue([string] $Key, $Value) { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } if ($this.FilePath -eq [string]::Empty) { $this.FilePath = $this.GetTargetFilePath($this.StoreLevel, $this.HiveName) } # Read from source, read from source in case data changed $currentData = [HashTable](. $this.GetFromSource) $currentData[$Key] = $Value Invoke-ScriptBlockWithRetry -Context { $currentData | Export-Clixml -Path $this.FilePath } -RetryPolicy $this.Policy $this.ResetCache() } # Removes a value from the store # @Override [void] RemoveValue([string] $Key) { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } if ($this.FilePath -eq [string]::Empty) { $this.FilePath = $this.GetTargetFilePath($this.StoreLevel, $this.HiveName) } # Read from source in case data changed $data = [HashTable](. $this.GetFromSource) if ($data.Keys -ccontains $Key) { $data.Remove($Key) Invoke-ScriptBlockWithRetry -Context { $data | Export-Clixml -Path $this.FilePath } -RetryPolicy $this.Policy $this.ResetCache() } else { Warn -Message ("Key: '{0}' does not exist in the store" -f $Key) } } # Gets a list of the keys in the store # @Override [string[]] GetKeys() { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } $currentData = [HashTable] (Get-ExpiringCacheItem -Key $this.CacheId) return $currentData.Keys } # Sets a custom retry policy for writting into the file [void] SetCustomRetryPolicy([PSCustomObject] $proposedPolicy) { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } if ($proposedPolicy.PSTypeNames[0] -ne [CliFileStore]::PolicyTypeName) { $m = "[Store.{0}] Invalid Retry Policy object: {1}", [CliFileStore]::Name, $proposedPolicy.PSTypeNames[0] $err = New-Object ConfigHiveError -ArgumentList 'InvalidRetryPolicyObject', $m throw($err) } $this.Policy = $proposedPolicy } # Serialize initialization data # @Override [string] SerializeInstanceData() { if ($this.IsInitialized -ne $true) { $m = "[Store.{0}] Attempt to use an uninitialized store" -f [CliFileStore]::Name $err = New-Object ConfigHiveError -ArgumentList 'UninitializedStore', $m throw($err) } $serialData = @{} $serialData['HiveName'] = $this.HiveName $serialData['Level'] = ([string] $this.StoreLevel) if ($this.FilePath -ne [string]::Empty) { $serialData['FilePath'] = ([System.IO.FileInfo] $this.FilePath).FullName } if ($this.CacheControlType -eq [TimeSpan].ToString()) { $controlStr = $this.TimedCacheControl.ToString() $serialData['TimedCacheControl'] = $controlStr } elseif ($this.CacheControlType -eq [ScriptBlock].ToString()) { $controlStr = $this.CustomCacheControl.ToString() $serialData['CustomCacheControl'] = $controlStr } else { $m = "Serialization for Cache Control Type '{0}' is not supported" -f $this.CacheControlType $err = New-Object ConfigHiveError -ArgumentList 'InvalidImplementation', $m throw($err) } return ($serialData | ConvertTo-Json -Compress ) } } $Script:AvailableStores += 'CliFileStore' |