mirror of
https://github.com/sbrl/Pepperminty-Wiki.git
synced 2024-11-22 16:33:00 +00:00
Starbeamrainbowlabs
593f16dfb9
....I was gettign increasinly nervous about not committing these to git. Hopefully at some point soon I'll be able to integrate the BkTree into Pepperminty Wiki properly - but I still need to implement word removal first before I can do that. Also, feature-search is getting big. It's refactoring time to be sure, but Im uncertain at this stage precisely _how_ I want to go about that. I've got 2 ideas: 1. Refactor the engine and the storage box into separate "library modules" 2. Refactor them into their own repository/ies or something, and include them as extra data 3. Extend the extra data system to support local files and include them in the main Pepperminty Wiki repository Thought is required. If anyone actually reads this message, do get in touch with your thoughts!
181 lines
5.9 KiB
PHP
181 lines
5.9 KiB
PHP
<?php
|
|
/**
|
|
* 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.
|
|
*/
|
|
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;
|
|
}
|
|
}
|