mirror of
https://github.com/sbrl/Pepperminty-Wiki.git
synced 2024-11-29 18:33:01 +00:00
185 lines
6.3 KiB
PHP
185 lines
6.3 KiB
PHP
<?php
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* Resolves a relative path against a given base directory.
|
|
* @apiVersion 0.20.0
|
|
* @source https://stackoverflow.com/a/44312137/1460422
|
|
* @param string $path The relative path to resolve.
|
|
* @param string|null $basePath The base directory to resolve against.
|
|
* @return string An absolute path.
|
|
*/
|
|
function path_resolve(string $path, string $basePath = null) {
|
|
// Make absolute path
|
|
if (substr($path, 0, 1) !== DIRECTORY_SEPARATOR) {
|
|
if ($basePath === null) {
|
|
// Get PWD first to avoid getcwd() resolving symlinks if in symlinked folder
|
|
$path=(getenv('PWD') ?: getcwd()).DIRECTORY_SEPARATOR.$path;
|
|
} elseif (strlen($basePath)) {
|
|
$path=$basePath.DIRECTORY_SEPARATOR.$path;
|
|
}
|
|
}
|
|
|
|
// Resolve '.' and '..'
|
|
$components=array();
|
|
foreach(explode(DIRECTORY_SEPARATOR, rtrim($path, DIRECTORY_SEPARATOR)) as $name) {
|
|
if ($name === '..') {
|
|
array_pop($components);
|
|
} elseif ($name !== '.' && !(count($components) && $name === '')) {
|
|
// … && !(count($components) && $name === '') - we want to keep initial '/' for abs paths
|
|
$components[]=$name;
|
|
}
|
|
}
|
|
|
|
return implode(DIRECTORY_SEPARATOR, $components);
|
|
}
|
|
|
|
/*
|
|
███████ ████████ ██████ ██████ █████ ██████ ███████ ██████ ██████ ██ ██
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
███████ ██ ██ ██ ██████ ███████ ██ ███ █████ ██████ ██ ██ ███
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
███████ ██ ██████ ██ ██ ██ ██ ██████ ███████ ██████ ██████ ██ ██
|
|
*/
|
|
|
|
/**
|
|
* Represents a key-value data store.
|
|
* @license Apache 2.0
|
|
*/
|
|
class JsonStorageBox {
|
|
/**
|
|
* The SQLite database connection.
|
|
* @var \PDO
|
|
*/
|
|
private $db;
|
|
|
|
/**
|
|
* A cache of values.
|
|
* @var object[]
|
|
*/
|
|
private $cache = [];
|
|
|
|
/**
|
|
* A cache of prepared SQL statements.
|
|
* @var \PDOStatement[]
|
|
*/
|
|
private $query_cache = [];
|
|
|
|
/**
|
|
* Initialises a new store connection.
|
|
* @param string $filename The filename that the store is located in.
|
|
*/
|
|
function __construct(string $filename) {
|
|
$firstrun = !file_exists($filename);
|
|
$this->db = new \PDO("sqlite:" . path_resolve($filename, __DIR__)); // HACK: This might not work on some systems, because it depends on the current working directory
|
|
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
if($firstrun) {
|
|
$this->query("CREATE TABLE store (key TEXT UNIQUE NOT NULL, value TEXT)");
|
|
}
|
|
}
|
|
/**
|
|
* Makes a query against the database.
|
|
* @param string $sql The (potentially parametised) query to make.
|
|
* @param array $variables Optional. The variables to substitute into the SQL query.
|
|
* @return \PDOStatement The result of the query, as a PDOStatement.
|
|
*/
|
|
private function query(string $sql, array $variables = []) {
|
|
// Add to the query cache if it doesn't exist
|
|
if(!isset($this->query_cache[$sql]))
|
|
$this->query_cache[$sql] = $this->db->prepare($sql);
|
|
$this->query_cache[$sql]->execute($variables);
|
|
return $this->query_cache[$sql]; // fetchColumn(), fetchAll(), etc. are defined on the statement, not the return value of execute()
|
|
}
|
|
|
|
/**
|
|
* Determines if the given key exists in the store or not.
|
|
* @param string $key The key to test.
|
|
* @return bool Whether the key exists in the store or not.
|
|
*/
|
|
public function has(string $key) : bool {
|
|
if(isset($this->cache[$key]))
|
|
return true;
|
|
return $this->query(
|
|
"SELECT COUNT(key) FROM store WHERE key = :key;",
|
|
[ "key" => $key ]
|
|
)->fetchColumn() > 0;
|
|
}
|
|
|
|
/**
|
|
* Gets a value from the store.
|
|
* @param string $key The key value is stored under.
|
|
* @return mixed The stored value.
|
|
*/
|
|
public function get(string $key) {
|
|
// If it's not in the cache, insert it
|
|
if(!isset($this->cache[$key])) {
|
|
$this->cache[$key] = [ "modified" => false, "value" => json_decode($this->query(
|
|
"SELECT value FROM store WHERE key = :key;",
|
|
[ "key" => $key ]
|
|
)->fetchColumn()) ];
|
|
}
|
|
return $this->cache[$key]["value"];
|
|
}
|
|
|
|
/**
|
|
* Sets a value in the data store.
|
|
* Note that this does NOT save changes to disk until you close the connection!
|
|
* @param string $key The key to set the value of.
|
|
* @param mixed $value The value to store.
|
|
*/
|
|
public function set(string $key, $value) : void {
|
|
if(!isset($this->cache[$key])) $this->cache[$key] = [];
|
|
$this->cache[$key]["value"] = $value;
|
|
$this->cache[$key]["modified"] = true;
|
|
}
|
|
|
|
/**
|
|
* Deletes an item from the data store.
|
|
* @param string $key The key of the item to delete.
|
|
* @return bool Whether it was really deleted or not. Note that if it doesn't exist, then it can't be deleted. Note also that if a node is deleted before being persisted to disk, this will return false when in actuality it was deleted successfully.
|
|
*/
|
|
public function delete(string $key) : bool {
|
|
// Remove it from the cache
|
|
if(isset($this->cache[$key]))
|
|
unset($this->cache[$key]);
|
|
// Remove it from disk
|
|
return $this->query(
|
|
"DELETE FROM store WHERE key = :key;",
|
|
[ "key" => $key ]
|
|
)->rowCount() > 0;
|
|
}
|
|
|
|
/**
|
|
* Empties the store.
|
|
*/
|
|
public function clear() : void {
|
|
// Empty the cache;
|
|
$this->cache = [];
|
|
// Empty the disk
|
|
$this->query("DELETE FROM store;");
|
|
}
|
|
|
|
/**
|
|
* Syncs changes to disk and closes the PDO connection.
|
|
*/
|
|
public function close() : void {
|
|
$this->db->beginTransaction();
|
|
foreach($this->cache as $key => $value_data) {
|
|
// If it wasn't modified, there's no point in saving it, is there?
|
|
if(!$value_data["modified"])
|
|
continue;
|
|
|
|
$this->query(
|
|
"INSERT OR REPLACE INTO store(key, value) VALUES(:key, :value)",
|
|
[
|
|
"key" => $key,
|
|
"value" => json_encode($value_data["value"])
|
|
]
|
|
);
|
|
}
|
|
$this->db->commit();
|
|
$this->db = null;
|
|
}
|
|
}
|