Wsl-Image/Wsl-Image.Database.ps1
|
using namespace System.IO; using namespace System.Timers; using namespace System.Data; # cSpell: ignore Linq [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', '')] $DatabaseDatadir = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { Join-Path -Path "$HOME" -ChildPath ".local/share" } $BaseImageDatabaseFilename = [FileInfo]::new(@($DatabaseDatadir, "Wsl", "RootFS", "images.db") -join [Path]::DirectorySeparatorChar) [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage()] $BaseDatabaseStructure = (Get-Content (Join-Path $PSScriptRoot "db.sqlite") -Raw) class WslImageDatabase { WslImageDatabase() { # Create the database directory (with parents) if it doesn't exist [WslImageDatabase]::DatabaseFileName.Directory.Create() | Out-Null } [bool] IsOpen() { return $null -ne $this.db } [void] AssertOpen() { if (-not $this.IsOpen()) { # nocov throw [WslManagerException]::new("The image database is not open.") } } [void] Open() { if ($this.IsOpen()) { throw [WslManagerException]::new("The image database is already open.") } # Create the database if it doesn't exist Write-Verbose "Opening database file: $([WslImageDatabase]::DatabaseFileName.FullName)" $this.db = [SQLiteHelper]::open([WslImageDatabase]::DatabaseFileName.FullName) $this.db.UpdateTimestampColumn = 'UpdateDate' Write-Verbose "Database opened." # Get the current version from the database $rs = $this.db.ExecuteSingleQuery("PRAGMA user_version;") $this.version = if ($rs.Item.Count -gt 0) { $rs[0].user_version } else { 0 } Write-Verbose "Database version: $($this.version)" } [void] Close() { if ($this.IsOpen()) { $this.db.Close() $this.db = $null $this.version = 0 } } [bool] IsUpdatePending() { return $this.version -lt [WslImageDatabase]::CurrentVersion } [PSCustomObject] GetImageSourceCache([WslImageType]$Type) { $this.AssertOpen() $dt = $this.db.ExecuteSingleQuery("SELECT * FROM ImageSourceCache WHERE Type = @Type;", @{ Type = $Type.ToString() }) return $dt | ForEach-Object { [PSCustomObject]@{ Type = $_.Type Url = $_.Url LastUpdate = $_.LastUpdate Etag = $_.Etag } } } [void] UpdateImageSourceCache([WslImageType]$Type, [PSCustomObject]$CacheData) { $this.AssertOpen() $query = $this.db.CreateUpsertQuery("ImageSourceCache") $parameters = @{ Type = $Type.ToString() Url = $CacheData.Url LastUpdate = $CacheData.LastUpdate Etag = $CacheData.Etag } $this.db.ExecuteNonQuery($query, $parameters) } [PSCustomObject[]] GetImageSources([string]$QueryString, [hashtable]$Parameters = @{}) { $this.AssertOpen() $query = "SELECT * FROM ImageSource" if ($QueryString) { $query += " WHERE $QueryString;" } else { $query += ";" } Write-Verbose "Executing query to get image sources: $query with parameters: $($Parameters | ConvertTo-Json -Depth 5)" $dt = $this.db.ExecuteSingleQuery($query, $Parameters) if ($dt) { return $dt | ForEach-Object { [PSCustomObject]@{ Id = $_.Id Name = $_.Name Url = if ([System.DBNull]::Value.Equals($_.Url)) { $null } else { $_.Url } Type = $_.Type -as [WslImageType] Tags = if ($_.Tags) { @($_.Tags -split ',') } else { @('none') } Configured = if ('TRUE' -eq $_.Configured) { $true } else { $false } Username = $_.Username Uid = $_.Uid Distribution = $_.Distribution Release = $_.Release LocalFilename = $_.LocalFilename DigestSource = $_.DigestSource DigestAlgorithm = $_.DigestAlgorithm DigestUrl = if ([System.DBNull]::Value.Equals($_.DigestUrl)) { $null } else { $_.DigestUrl } Digest = if ([System.DBNull]::Value.Equals($_.Digest)) { $null } else { $_.Digest } GroupTag = if ([System.DBNull]::Value.Equals($_.GroupTag)) { $null } else { $_.GroupTag } CreationDate = [System.DateTime]::Parse($_.CreationDate) UpdateDate = [System.DateTime]::Parse($_.UpdateDate) Size = if ($_.Size -is [System.DBNull]) { 0 } else { $_.Size } } } } else { return @() } } [PSCustomObject[]] GetImageBuiltins([WslImageType]$Type) { return $this.GetImageSources("Type = @Type", @{ Type = $Type.ToString() }) } [void] SaveImageBuiltins([WslImageType]$Type, [PSCustomObject[]]$Images, [string]$GroupTag = $null) { $this.AssertOpen() $query = $this.db.CreateUpsertQuery("ImageSource", @('Id')) foreach ($image in $Images) { $hash = if ($image.Hash) { $image.Hash } else { $image.HashSource } $parameters = @{ Id = [Guid]::NewGuid().ToString() Name = $image.Name Tags = if ($null -ne $image.Tags -and $image.Tags.Count -gt 0) { $image.Tags -join ',' } else { $image.Release } Url = $image.Url Type = $image.Type.ToString() Configured = if ($image.Configured) { 'TRUE' } else { 'FALSE' } Username = $image.Username Uid = $image.Uid Distribution = if ($image.Distribution) { $image.Distribution } else { $image.Os } Release = $image.Release LocalFilename = $image.LocalFilename DigestSource = $hash.Type DigestAlgorithm = if ($hash.Algorithm) { $hash.Algorithm } else { "SHA256" } Digest = if ($image.FileHash) { $image.FileHash } elseif ($image.Digest) { $image.Digest } else { $null } DigestUrl = $hash.Url GroupTag = $GroupTag Size = if ($image.PSObject.Properties.Match('Size')) { $image.Size } else { $null } } try { $this.db.ExecuteNonQuery($query, $parameters) } catch { throw [WslManagerException]::new("Failed to insert or update image $($image.Name) into the database. Exception: $($_.Exception.Message)", $_.Exception) } } Write-Verbose "Saved $($Images.Count) images of type $Type into the database with group tag $GroupTag. Removing old images..." try { $this.db.ExecuteNonQuery("DELETE FROM ImageSource WHERE Type = @Type AND GroupTag IS NOT NULL AND GroupTag IS NOT @GroupTag;", @{ Type = $Type.ToString(); GroupTag = $GroupTag }) } catch { # nocov throw [WslManagerException]::new("Failed to remove old images of type $Type from the database. Exception: $($_.Exception.Message)", $_.Exception) } # Update local images state Write-Verbose "Updating local images state based on new image sources..." try { $this.db.ExecuteNonQuery("UPDATE LocalImage SET State = 'Outdated' FROM ImageSource WHERE LocalImage.ImageSourceId = ImageSource.Id AND LocalImage.Digest <> ImageSource.Digest;") } catch { # nocov throw [WslManagerException]::new("Failed to update local images state. Exception: $($_.Exception.Message)", $_.Exception) } } [void]SaveImageSource([PSCustomObject]$ImageSource) { $this.AssertOpen() $query = $this.db.CreateUpsertQuery("ImageSource", @('Id')) $hash = if ($ImageSource.Hash) { $ImageSource.Hash } else { $ImageSource.HashSource } if (-not $hash) { $hash = [PSCustomObject]@{ Type = $ImageSource.DigestSource Algorithm = $ImageSource.DigestAlgorithm Url = $ImageSource.DigestUrl } } $parameters = @{ Id = $ImageSource.Id.ToString() Name = $ImageSource.Name Tags = if ($ImageSource.Tags) { $ImageSource.Tags -join ',' } else { $ImageSource.Release } Url = $ImageSource.Url Type = $ImageSource.Type.ToString() Configured = if ($ImageSource.Configured) { 'TRUE' } else { 'FALSE' } Username = $ImageSource.Username Uid = $ImageSource.Uid Distribution = $ImageSource.Distribution Release = $ImageSource.Release LocalFilename = $ImageSource.LocalFilename DigestSource = $hash.Type DigestAlgorithm = if ($hash.Algorithm) { $hash.Algorithm } else { "SHA256" } Digest = if ($ImageSource.FileHash) { $ImageSource.FileHash } elseif ($ImageSource.Digest) { $ImageSource.Digest } else { $null } DigestUrl = $hash.Url GroupTag = if ($ImageSource.PSObject.Properties.Match('GroupTag')) { $ImageSource.GroupTag } else { $null } Size = if ($ImageSource.PSObject.Properties.Match('Size')) { $ImageSource.Size } else { $null } } Write-Verbose "Inserting or updating image source $($ImageSource.Name) into the database..." try { $this.db.ExecuteNonQuery($query, $parameters) } catch { throw [WslManagerException]::new("Failed to insert or update image source $($ImageSource.Name) into the database. Exception: $($_.Exception.Message)", $_.Exception) } Write-Verbose "Updating local images state based on new image source for Id $($ImageSource.Id)..." try { $this.db.ExecuteNonQuery("UPDATE LocalImage SET State = 'Outdated' FROM ImageSource WHERE ImageSource.Id = @Id AND LocalImage.ImageSourceId = ImageSource.Id AND LocalImage.Digest <> ImageSource.Digest;",@{ Id = $ImageSource.Id.ToString() }) } catch { # nocov throw [WslManagerException]::new("Failed to update local images state. Exception: $($_.Exception.Message)", $_.Exception) } } [PSCustomObject[]] GetLocalImages([string]$QueryString, [hashtable]$Parameters = @{}) { $this.AssertOpen() $query = "SELECT * FROM LocalImage" if ($QueryString) { $query += " WHERE $QueryString;" } else { $query += ";" } $dt = $this.db.ExecuteSingleQuery($query, $Parameters) if ($null -eq $dt) { return @() } return $dt | ForEach-Object { [PSCustomObject]@{ Id = $_.Id ImageSourceId = $_.ImageSourceId Name = $_.Name Url = if ([System.DBNull]::Value.Equals($_.Url)) { $null } else { $_.Url } Type = $_.Type -as [WslImageType] Tags = if ($_.Tags) { $_.Tags -split ',' } else { @() } Configured = if ('TRUE' -eq $_.Configured) { $true } else { $false } Username = $_.Username Uid = $_.Uid Distribution = $_.Distribution Release = $_.Release LocalFilename = $_.LocalFilename HashSource = [PSCustomObject]@{ Type = $_.DigestSource Algorithm = $_.DigestAlgorithm Mandatory = $true Url = if ([System.DBNull]::Value.Equals($_.DigestUrl)) { $null } else { $_.DigestUrl } } Digest = if ([System.DBNull]::Value.Equals($_.Digest)) { $null } else { $_.Digest } State = $_.State CreationDate = [System.DateTime]::Parse($_.CreationDate) UpdateDate = [System.DateTime]::Parse($_.UpdateDate) Size = if ($_.Size -is [System.DBNull]) { 0 } else { $_.Size } } } } [PSCustomObject[]] GetLocalImages() { return $this.GetLocalImages($null, $null) } [void]SaveLocalImage([PSCustomObject]$LocalImage) { $this.AssertOpen() $query = $this.db.CreateUpsertQuery("LocalImage", @('Id')) $hash = if ($LocalImage.Hash) { $LocalImage.Hash } else { $LocalImage.HashSource } if (-not $hash) { $hash = [PSCustomObject]@{ Type = $LocalImage.DigestSource Algorithm = $LocalImage.DigestAlgorithm Url = $LocalImage.DigestUrl } } $parameters = @{ Id = $LocalImage.Id.ToString() ImageSourceId = if ($LocalImage.SourceId) { $LocalImage.SourceId.ToString() } else { $null } Name = $LocalImage.Name Tags = if ($LocalImage.Tags) { $LocalImage.Tags -join ',' } else { $LocalImage.Release } Url = $LocalImage.Url Type = $LocalImage.Type.ToString() State = $LocalImage.State.ToString() Configured = if ($LocalImage.Configured) { 'TRUE' } else { 'FALSE' } Username = $LocalImage.Username Uid = $LocalImage.Uid Distribution = $LocalImage.Distribution Release = $LocalImage.Release LocalFilename = $LocalImage.LocalFilename DigestSource = $hash.Type DigestAlgorithm = if ($hash.Algorithm) { $hash.Algorithm } else { "SHA256" } Digest = if ($LocalImage.FileHash) { $LocalImage.FileHash } elseif ($LocalImage.Digest) { $LocalImage.Digest } else { $null } DigestUrl = $hash.Url Size = if ($LocalImage.PSObject.Properties.Match('Size')) { $LocalImage.Size } else { $null } } try { $this.db.ExecuteNonQuery($query, $parameters) } catch { throw [WslManagerException]::new("Failed to insert or update local image $($LocalImage.Name) into the database. Exception: $($_.Exception.Message)", $_.Exception) } } [PSCustomObject] CreateLocalImageFromImageSource([Guid]$ImageSourceId) { $this.AssertOpen() $dt = $this.db.ExecuteSingleQuery([WslImageDatabase]::CreateLocalImageSql, @{ Id = [Guid]::NewGuid().ToString() ImageSourceId = $ImageSourceId.ToString() }) if ($null -eq $dt -or $dt.Rows.Count -eq 0) { throw [WslManagerException]::new("Image source with ID $ImageSourceId not found.($dt)") } return $dt | ForEach-Object { [PSCustomObject]@{ Id = $_.Id ImageSourceId = $_.ImageSourceId Name = $_.Name Url = if ([System.DBNull]::Value.Equals($_.Url)) { $null } else { $_.Url } Type = $_.Type -as [WslImageType] Tags = if ($_.Tags) { $_.Tags -split ',' } else { @() } Configured = if ('TRUE' -eq $_.Configured) { $true } else { $false } Username = $_.Username Uid = $_.Uid Distribution = $_.Distribution Release = $_.Release LocalFilename = $_.LocalFilename HashSource = [PSCustomObject]@{ Type = $_.DigestSource Algorithm = $_.DigestAlgorithm Mandatory = $true Url = if ([System.DBNull]::Value.Equals($_.DigestUrl)) { $null } else { $_.DigestUrl } } Digest = if ([System.DBNull]::Value.Equals($_.Digest)) { $null } else { $_.Digest } State = $_.State Size = if ($_.Size -is [System.DBNull]) { 0 } else { $_.Size } } } } [void] RemoveLocalImage([Guid]$Id) { $this.AssertOpen() try { $this.db.ExecuteNonQuery("DELETE FROM LocalImage WHERE Id = @Id;", @{ Id = $Id.ToString() }) } catch { # nocov throw [WslManagerException]::new("Failed to remove local image with ID $Id. Exception: $($_.Exception.Message)", $_.Exception) } } [void] RemoveImageSource([Guid]$Id) { $this.AssertOpen() try { $this.db.ExecuteNonQuery("DELETE FROM ImageSource WHERE Id = @Id;", @{ Id = $Id.ToString() }) } catch { # nocov throw [WslManagerException]::new("Failed to remove image source with ID $Id. Exception: $($_.Exception.Message)", $_.Exception) } } [void] CreateDatabaseStructure() { $this.AssertOpen() # Create the necessary tables and indexes $this.db.ExecuteNonQuery([WslImageDatabase]::DatabaseStructure) } [void] TransferBuiltinImages([WslImageType]$Type = [WslImageType]::Builtin) { $this.AssertOpen() Write-Verbose "Transferring built-in images from source $Type..." $Uri = [System.Uri]([WslImageDatabase]::WslImageSources[$Type]) $CacheFilename = $Uri.Segments[-1] $cacheFile = Join-Path -Path ([WslImage]::BasePath) -ChildPath $CacheFilename if (-not (Test-Path -Path $cacheFile)) { Write-Verbose "Cache file $cacheFile does not exist." return } Write-Verbose "Loading cache from file $cacheFile" $cache = Get-Content -Path $cacheFile | ConvertFrom-Json # First insert the cache information Write-Verbose "Inserting cache information into ImageSourceCache..." $query = $this.db.CreateUpsertQuery("ImageSourceCache") $parameters = @{ Type = $Type.ToString() Url = $cache.Url LastUpdate = $cache.lastUpdate Etag = $cache.etag } $this.db.ExecuteNonQuery($query, $parameters) # Next insert the cache information into ImageSource Write-Verbose "Inserting cache information into ImageSource..." $query = $this.db.CreateUpsertQuery("ImageSource") Write-Verbose "query: $query" foreach ($image in $cache.builtins) { $hash = if ($image.Hash) { $image.Hash } else { $image.HashSource } $parameters = @{ Id = [Guid]::NewGuid().ToString() # CreationDate = $null # UpdateDate = $null Name = $image.Name Tags = if ($image.Tags) { $image.Tags -join ',' } else { $image.Release } Url = $image.Url Type = ($image.Type -as [WslImageType]).ToString() Configured = if ($image.Configured) { 'TRUE' } else { 'FALSE' } Username = $image.Username Uid = $image.Uid Distribution = $image.Os Release = $image.Release LocalFilename = $image.LocalFilename DigestSource = $hash.Type DigestAlgorithm = if ($hash.Algorithm) { $hash.Algorithm } else { "SHA256" } DigestUrl = $hash.Url Digest = $null } $this.db.ExecuteNonQuery($query, $parameters) } # Delete the source file Remove-Item -Path $cacheFile -Force | Out-Null } [void] AddImageSourceGroupTag() { $this.AssertOpen() Write-Verbose "Adding GroupTag column to ImageSource table..." try { $this.db.ExecuteNonQuery([WslImageDatabase]::AddImageSourceGroupTagSql) } catch { # nocov throw [WslManagerException]::new("Failed to add GroupTag column to ImageSource table. Exception: $($_.Exception.Message)", $_.Exception) } } [void] AddImageSizeColumn() { $this.AssertOpen() Write-Verbose "Adding Size column to ImageSource and LocalImage tables..." try { $this.db.ExecuteNonQuery([WslImageDatabase]::AddSizeToImagesSql) } catch { # nocov throw [WslManagerException]::new("Failed to add Size column to ImageSource and LocalImage tables. Exception: $($_.Exception.Message)", $_.Exception) } } [void] AddUniqueIndexOnLocalImage() { $this.AssertOpen() Write-Verbose "Adding unique index on LocalImage (ImageSourceId, Name)..." try { $this.db.ExecuteNonQuery("CREATE UNIQUE INDEX IF NOT EXISTS IX_LocalImage_ImageSourceId_Name ON LocalImage (ImageSourceId, Name);") } catch { # nocov throw [WslManagerException]::new("Failed to add unique index on LocalImage (ImageSourceId, Name). Exception: $($_.Exception.Message)", $_.Exception) } } [void] ChangePrimaryKeyToTags() { $this.AssertOpen() Write-Verbose "Changing primary key of ImageSource from (Type, Distribution, Release, Configured) to (Type, Distribution, Tags, Configured)..." try { $this.db.ExecuteNonQuery([WslImageDatabase]::ChangePrimaryKeyToTagsSql) } catch { # nocov throw [WslManagerException]::new("Failed to change primary key of ImageSource to use Tags instead of Release. Exception: $($_.Exception.Message)", $_.Exception) } } [void] TransferLocalImages([DirectoryInfo] $BasePath = $null) { $this.AssertOpen() if ($null -eq $BasePath) { $BasePath = [WslImage]::BasePath } Move-LocalWslImage -Database $this.db -BasePath $BasePath } [void]UpdateVersion([int]$NewVersion) { $this.AssertOpen() if ($NewVersion -le $this.version) { # nocov Write-Warning "Attempted to update database version to $NewVersion, which is not greater than the current version $($this.version). Skipping." return } $this.db.ExecuteNonQuery("PRAGMA user_version = $NewVersion;VACUUM;") $this.version = $NewVersion } [void] UpdateIfNeeded([int]$ExpectedVersion) { $this.AssertOpen() Write-Verbose "Updating image database from version $($this.version)..." if ($this.version -lt 1 -and $ExpectedVersion -ge 1) { # Fresh database, create structure Write-Verbose "Upgrading to version 1: creating database structure..." $this.CreateDatabaseStructure() $this.UpdateVersion(1) } if ($this.version -lt 2 -and $ExpectedVersion -ge 2) { Write-Verbose "Upgrading to version 2: transferring existing built-in images..." $this.TransferBuiltinImages([WslImageType]::Builtin) $this.TransferBuiltinImages([WslImageType]::Incus) $this.UpdateVersion(2) } if ($this.version -lt 3 -and $ExpectedVersion -ge 3) { Write-Verbose "Upgrading to version 3: adding GroupTag column to ImageSource table..." $this.AddImageSourceGroupTag() $this.UpdateVersion(3) } if ($this.version -lt 4 -and $ExpectedVersion -ge 4) { Write-Verbose "Upgrading to version 4: transferring local images..." $this.TransferLocalImages($null) $this.UpdateVersion(4) } if ($this.version -lt 5 -and $ExpectedVersion -ge 5) { Write-Verbose "Upgrading to version 5: adding Size column to ImageSource and LocalImage tables..." $this.AddImageSizeColumn() $this.UpdateVersion(5) } if ($this.version -lt 6 -and $ExpectedVersion -ge 6) { Write-Verbose "Upgrading to version 6: adding unique index on LocalImage (ImageSourceId, Name)..." $this.AddUniqueIndexOnLocalImage() $this.UpdateVersion(6) } if ($this.version -lt 7 -and $ExpectedVersion -ge 7) { Write-Verbose "Upgrading to version 7: changing primary key to use Tags instead of Release..." $this.ChangePrimaryKeyToTags() $this.UpdateVersion(7) } } hidden [SQLiteHelper] $db hidden [int] $version static [FileInfo] $DatabaseFileName = $BaseImageDatabaseFilename static [int] $CurrentVersion = 7 static [string] $DatabaseStructure = $BaseDatabaseStructure static [hashtable] $WslImageSources = $WslImageSources # Singleton instance hidden static [WslImageDatabase] $Instance hidden static [Timer] $SessionCloseTimer hidden static [int] $SessionCloseTimeout = 180000 # static migration queries hidden static [string] $AddImageSourceGroupTagSql = @" ALTER TABLE ImageSource ADD COLUMN [GroupTag] TEXT; UPDATE ImageSource SET [GroupTag] = ImageSourceCache.Etag FROM ImageSourceCache WHERE ImageSource.Type = ImageSourceCache.Type; "@ hidden static [string] $CreateLocalImageSql = @" INSERT INTO LocalImage (Id,ImageSourceId,Name,Tags,Url,State,Type,Configured,Username,Uid,Distribution,Release,LocalFilename,DigestSource,DigestAlgorithm,DigestUrl,Digest,Size) SELECT @Id,Id,Name,Tags,Url,'NotDownloaded',Type,Configured,Username,Uid,Distribution,Release,LocalFilename,DigestSource,DigestAlgorithm,DigestUrl,Digest,Size FROM ImageSource WHERE Id = @ImageSourceId ON CONFLICT(ImageSourceId, Name) DO UPDATE SET Url = excluded.Url, Type = excluded.Type, Configured = excluded.Configured, Username = excluded.Username, Uid = excluded.Uid, Distribution = excluded.Distribution, Release = excluded.Release, LocalFilename = excluded.LocalFilename, DigestSource = excluded.DigestSource, DigestAlgorithm = excluded.DigestAlgorithm, DigestUrl = excluded.DigestUrl, Digest = excluded.Digest, Size = excluded.Size, UpdateDate = CURRENT_TIMESTAMP RETURNING *; "@ hidden static [string] $AddSizeToImagesSql = @" ALTER TABLE ImageSource ADD COLUMN [Size] INTEGER; ALTER TABLE LocalImage ADD COLUMN [Size] INTEGER; "@ hidden static [string] $ChangePrimaryKeyToTagsSql = @" -- Create a new table with the correct primary key DROP TABLE IF EXISTS ImageSource_new; UPDATE ImageSource SET Tags = [Release] WHERE Tags IS NULL OR Tags = ''; CREATE TABLE ImageSource_new ( Id TEXT, CreationDate TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UpdateDate TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, Name TEXT NOT NULL, Tags TEXT, Url TEXT, Type TEXT NOT NULL DEFAULT 'Builtin', Configured TEXT NOT NULL DEFAULT 'FALSE', Username TEXT NOT NULL DEFAULT 'root', Uid INTEGER NOT NULL DEFAULT 0, Distribution TEXT, [Release] TEXT, LocalFilename TEXT, DigestSource TEXT DEFAULT 'docker', DigestAlgorithm TEXT DEFAULT 'SHA256', DigestUrl TEXT, Digest TEXT, [GroupTag] TEXT, [Size] INTEGER, PRIMARY KEY (Type, Distribution, Tags, Configured), UNIQUE (Id) ); -- Copy data from old table to new table INSERT INTO ImageSource_new SELECT * FROM ImageSource; -- Drop the old table DROP TABLE ImageSource; -- Rename the new table ALTER TABLE ImageSource_new RENAME TO ImageSource; "@ } function Get-WslImageDatabase { if (-not [WslImageDatabase]::Instance) { [WslImageDatabase]::Instance = [WslImageDatabase]::new() } if (-not [WslImageDatabase]::Instance.IsOpen()) { [WslImageDatabase]::Instance.Open() [WslImageDatabase]::Instance.UpdateIfNeeded([WslImageDatabase]::CurrentVersion) # Put a session close timer of 3 minutes $timer = [Timer]::new([WslImageDatabase]::SessionCloseTimeout) $timer.AutoReset = $false if ([WslImageDatabase]::SessionCloseTimer) { [WslImageDatabase]::SessionCloseTimer.Dispose() } [WslImageDatabase]::SessionCloseTimer = $timer $null = Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action { [WslImageDatabase]::Instance.Close() } $timer.Start() } return [WslImageDatabase]::Instance } function Close-WslImageDatabase { if ([WslImageDatabase]::Instance -and [WslImageDatabase]::Instance.IsOpen()) { [WslImageDatabase]::Instance.Close() } if ([WslImageDatabase]::SessionCloseTimer) { [WslImageDatabase]::SessionCloseTimer.Dispose() [WslImageDatabase]::SessionCloseTimer = $null } } |