Service.ps1

Set-StrictMode -Version Latest

class Service {

    Service () {}

    [Collections.Generic.IEnumerable[Location]] Get() {
        return (dboperation {
            # Return an enumerator of all location entries
            try {
                [Location[]]$arr = Find $collection ([LiteDB.Query]::All()) ([Location])
                , $arr
            }
            catch [System.InvalidCastException] {
                Write-Warning "Caught InvalidCastException when reading db, probably [LiteDB.ObjectId] entry present."
                $oidquery = [LiteDB.Query]::Where('_id', {$args -like '{"$oid":"*"}'})
                $problementries = (,$collection.Find($oidquery))
                if ($problementries.Count -gt 0) {
                    Write-Warning "Found $($problementries.Count) problem entries, attempting to remove..."
                    $problementries | Write-Debug
                    try {
                        DBDelete $collection $oidquery | Out-Null
                        Write-Warning 'Problem entries successfully removed, please repeat your command.'
                    } catch {
                        Write-Error 'Problem entries could not be removed.'
                    }
                } else {
                        Write-Error 'No problem entries found, please open an issue on https://github.com/vors/ZLocation'
                }
            }
        })
    }
    [void] Add([string]$path, [double]$weight) {
        dboperation {
            $l = DBGetById $collection $path ([Location])
            if($l) {
                $l.weight += $weight
                DBUpdate $collection $l
            } else {
                $l = [Location]::new()
                $l.path = $path
                $l.weight = $weight
                try {
                    DBInsert $collection $l
                } catch [LiteDB.LiteException] { 
                    throw $_;
                }
            }
        }
    }
    [void] Remove([string]$path) {
        dboperation {
            # Use DB's internal column name, not mapped name
            DBDelete $collection ([LiteDB.Query]::EQ('_id', [LiteDB.BSONValue]::new($path)))
        }
    }
}

class Location {
    Location() {}

    [LiteDB.BsonId()]
    [string] $path;

    [double] $weight;
}

function Get-ZLocationDatabaseFilePath
{
    if ($env:ZLOCATION_TEST -eq 1) {
        return "$PSScriptRoot\..\testdb.db"
    }
    return (Join-Path $HOME 'z-location2.db')
}

<#
 Open database, invoke a database operation, and close the database afterwards.
 This is necessary for safe multi-process concurrency.
 See: https://github.com/mbdavid/LiteDB/wiki/Concurrency
 Exposes $db and $collection variables for use by the $scriptblock
#>

function dboperation {
    param (
        [Parameter(Mandatory=$true)] $private:scriptblock
    )
    $Private:Mode = if (Get-Variable IsMacOS -ErrorAction Ignore) { 'Exclusive' } else { 'Shared' }
    # $db and $collection will be in-scope within $scriptblock
    $db = DBOpen "Filename=$( Get-ZLocationDatabaseFilePath ); Mode=$Mode"
    $collection = Get-DBCollection $db 'location'
    try {
        # retry logic: on Mac we may not be able to execute the read concurrently
        for ($__i=0; $__i -lt 5; $__i++) {
            try {
                & $private:scriptblock
                return
            } catch [System.IO.IOException] {
                # The process cannot access the file '~\z-location.db' because it is being used by another process.
                if ($__i -lt 4 ) {
                    $rand = Get-Random 100
                    Start-Sleep -Milliseconds (($__i + 1) * 100 - $rand)
                } else {
                    throw [System.IO.IOException] 'Cannot execute database operation after 5 attempts, please open an issue on https://github.com/vors/ZLocation'
                }
            }
        }
    } finally {
        $db.dispose()
    }
}

# Create empty db, collection, and index if it doesn't exist
dboperation {
    $collection.EnsureIndex('path')
}

$service = [Service]::new()

Function Get-ZService {
    ,$service
}