Implement JSON interface to item and JSON itself
This commit is contained in:
parent
2cc6c7d0ee
commit
463bd267a8
|
@ -0,0 +1,155 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Item implements JsonSerializable
|
||||||
|
{
|
||||||
|
// TODO: Change to id here in all occurrences
|
||||||
|
private string $hash;
|
||||||
|
private string $extension;
|
||||||
|
private array $tags = [];
|
||||||
|
|
||||||
|
public static function load(MediaDB $db, string $id): ?Item
|
||||||
|
{
|
||||||
|
$ret = null;
|
||||||
|
|
||||||
|
$db->map(function ($line) use (&$ret, $id) {
|
||||||
|
if ($line[0] == $id) {
|
||||||
|
$ret = Item::fromLine($line);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromLine(array $line): Item
|
||||||
|
{
|
||||||
|
$ret = new Item();
|
||||||
|
|
||||||
|
$ret->hash = array_shift($line);
|
||||||
|
$ret->tags = $line;
|
||||||
|
$ret->extensionFromTags();
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ::upload() requires the PHP file upload ARRAY from $_FILES
|
||||||
|
public static function upload(array $php_file): Item
|
||||||
|
{
|
||||||
|
$from_path = $php_file['tmp_name'] ?? null;
|
||||||
|
|
||||||
|
// --- CHECKS ---
|
||||||
|
if (!is_string($from_path))
|
||||||
|
Json::error('Passed invalid upload structure')->die();
|
||||||
|
if (!is_uploaded_file($from_path))
|
||||||
|
Json::error('Trying to upload illegal or non-existent file')->die();
|
||||||
|
|
||||||
|
// --- INITIALIZE ---
|
||||||
|
$ret = new Item();
|
||||||
|
$ret->hash = hash_file('sha256', $from_path);
|
||||||
|
$ret->extensionFromUpload($php_file);
|
||||||
|
|
||||||
|
// --- ACTUALLY GRAB FILE ---
|
||||||
|
$new_path = $ret->getPath();
|
||||||
|
if (file_exists($new_path))
|
||||||
|
Json::error('File already exists')->die();
|
||||||
|
if (!move_uploaded_file($from_path, $new_path))
|
||||||
|
Json::error('Failed to move uploaded file')->die();
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extensionFromTags()
|
||||||
|
{
|
||||||
|
foreach ($this->tags as $i_tag) {
|
||||||
|
$parts = explode(':', $i_tag);
|
||||||
|
if ($parts[0] == 'format') {
|
||||||
|
$this->extension = $parts[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes is_uploaded_file has been done
|
||||||
|
private function extensionFromUpload(array $php_file)
|
||||||
|
{
|
||||||
|
$path = $php_file['tmp_name'] ?? false;
|
||||||
|
$name = $php_file['name'] ?? null;
|
||||||
|
|
||||||
|
// Try to get extension from mimetype
|
||||||
|
$ext = (new finfo(FILEINFO_EXTENSION))->file($path);
|
||||||
|
|
||||||
|
// REVIEW: Maybe change it so it doesn't have to be separated like this but
|
||||||
|
// do somethign like ['mime/type', null] be equivalent to this?
|
||||||
|
if (array_search($ext, ['webp', 'png', 'jpeg']) !== false) {
|
||||||
|
$this->extension = $ext;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it doesn't work, try to figure it out from the original extension
|
||||||
|
if ($ext == '???') {
|
||||||
|
// We mustn't accept everything straight away, so we're only accepting
|
||||||
|
// mime-type and original extension combinations that make sense
|
||||||
|
$whitelist = [
|
||||||
|
['application/zip', 'kra'],
|
||||||
|
['application/zip', 'krz'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// NOTE: The original extension has to be grabbed from the original name
|
||||||
|
$mime = mime_content_type($path);
|
||||||
|
$ext = pathinfo($name, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
if (array_search([$mime, $ext], $whitelist) !== false) {
|
||||||
|
$this->extension = $ext;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::error('File mime-type and or extension not allowed.')->die();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash(): string
|
||||||
|
{
|
||||||
|
return $this->hash;
|
||||||
|
}
|
||||||
|
public function getExtension(): string
|
||||||
|
{
|
||||||
|
return $this->extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(bool $absolute = false): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s/%s.%s',
|
||||||
|
$GLOBALS[$absolute ? 'path_media' : 'path_media'],
|
||||||
|
$this->hash,
|
||||||
|
$this->extension
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function getUri(): string
|
||||||
|
{
|
||||||
|
// TODO: Implement relative
|
||||||
|
return sprintf(
|
||||||
|
'%s/%s/%s.%s',
|
||||||
|
$GLOBALS['base_root'],
|
||||||
|
$GLOBALS['base_media'],
|
||||||
|
$this->hash,
|
||||||
|
$this->extension,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->hash,
|
||||||
|
'link' => $this->getUri(),
|
||||||
|
'tags' => $this->tags,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getJson(): Json
|
||||||
|
{
|
||||||
|
return Json::new($this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Json implements JsonSerializable
|
||||||
|
{
|
||||||
|
function __construct(
|
||||||
|
private mixed $struct
|
||||||
|
) {
|
||||||
|
$this->struct = $struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function new(mixed $struct): Json
|
||||||
|
{
|
||||||
|
return new Json($struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function error(mixed $msg): Json
|
||||||
|
{
|
||||||
|
return new Json([
|
||||||
|
'type' => 'error',
|
||||||
|
'msg' => $msg,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array
|
||||||
|
{
|
||||||
|
return $this->struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function die()
|
||||||
|
{
|
||||||
|
print(json_encode($this->struct));
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
239
main.php
239
main.php
|
@ -1,9 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
// Global variables
|
|
||||||
|
|
||||||
$_dr = $_SERVER['DOCUMENT_ROOT'];
|
$_dr = $_SERVER['DOCUMENT_ROOT'];
|
||||||
// TODO: Make configurable in .ini
|
// TODO: Make configurable in .ini
|
||||||
|
|
||||||
|
@ -16,14 +12,19 @@ $GLOBALS['base_root'] =
|
||||||
$_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
|
$_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
|
||||||
$GLOBALS['base_media'] = 'media';
|
$GLOBALS['base_media'] = 'media';
|
||||||
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
|
||||||
|
include_once($_dr . '/item.php');
|
||||||
|
include_once($_dr . '/mediadb.php');
|
||||||
|
include_once($_dr . '/json.php');
|
||||||
|
|
||||||
|
|
||||||
// ----- ROUTER -----
|
// ----- ROUTER -----
|
||||||
(match ([$_SERVER['DOCUMENT_URI'], $_SERVER['REQUEST_METHOD']]) {
|
(match ([$_SERVER['DOCUMENT_URI'], $_SERVER['REQUEST_METHOD']]) {
|
||||||
['/item', 'POST'] => post_item(),
|
['/item', 'POST'] => post_item(),
|
||||||
['/item', 'GET'] => get_item(),
|
['/item', 'GET'] => get_item(),
|
||||||
//['/search', 'GET'] => get_search(),
|
['/search', 'GET'] => get_search(),
|
||||||
default => Json::error('Not implemented')
|
default => Json::error('Not implemented')
|
||||||
})->die();
|
})->die();
|
||||||
|
|
||||||
|
@ -52,222 +53,26 @@ function post_item()
|
||||||
return $item->getJson();
|
return $item->getJson();
|
||||||
};
|
};
|
||||||
|
|
||||||
class MediaDB
|
function get_search()
|
||||||
{
|
{
|
||||||
private $handler;
|
$query = $_GET['q'] ?? '';
|
||||||
private bool $finished = false;
|
$limit = intval($_GET['limit'] ?? '20');
|
||||||
private array $lines = [];
|
// TODO: Implement page
|
||||||
|
|
||||||
public function __construct()
|
// If intval returns 0, it's an error
|
||||||
{
|
if ($limit == 0) return Json::error('Invalid limit parameter');
|
||||||
// Just initialize the file handler
|
if ($query !== '') return Json::error('Search not implemented yet');
|
||||||
$this->handler = fopen($GLOBALS['path_mediadb'], 'r');
|
|
||||||
if ($this->handler === false)
|
|
||||||
Json::error('Error opening media DB');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getLine(): ?array
|
$list = [];
|
||||||
{
|
|
||||||
$ret = fgetcsv($this->handler, 0, ' ');
|
|
||||||
if ($ret === false)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return $ret;
|
$db = new MediaDB();
|
||||||
}
|
$db->map(function ($line) use (&$list, $limit) {
|
||||||
|
if (count($list) >= $limit) return true;
|
||||||
|
|
||||||
public function map(callable $func)
|
$list[] = Item::fromLine($line);
|
||||||
{
|
|
||||||
// First read from what's already in memory
|
|
||||||
foreach ($this->lines as $i_line) {
|
|
||||||
if ($func($i_line) === false) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// REVIEW: Maybe this isn't needed cuz the while would be as eficient
|
return false;
|
||||||
// anyway?
|
});
|
||||||
|
|
||||||
// If we already got to the end, there's no sense in carrying on
|
return Json::new($list);
|
||||||
if ($this->finished) return;
|
|
||||||
|
|
||||||
// If we run out, read from the file and append to array so the next lookup
|
|
||||||
// is fast
|
|
||||||
while ($line = $this->getLine()) {
|
|
||||||
$this->lines[] = $line;
|
|
||||||
|
|
||||||
if ($func($line) === false) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the flag so we don't reach this point two times
|
|
||||||
$this->finished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Item
|
|
||||||
{
|
|
||||||
// TODO: Change to id here in all occurrences
|
|
||||||
private string $hash;
|
|
||||||
private string $extension;
|
|
||||||
private array $tags = [];
|
|
||||||
|
|
||||||
public static function load(MediaDB $db, string $id): ?Item
|
|
||||||
{
|
|
||||||
$ret = null;
|
|
||||||
|
|
||||||
$db->map(function ($line) use (&$ret, $id) {
|
|
||||||
if ($line[0] == $id) {
|
|
||||||
$ret = new Item();
|
|
||||||
$ret->hash = array_shift($line);
|
|
||||||
$ret->tags = $line;
|
|
||||||
$ret->extensionFromTags();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return $ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ::upload() requires the PHP file upload ARRAY from $_FILES
|
|
||||||
public static function upload(array $php_file): Item
|
|
||||||
{
|
|
||||||
$from_path = $php_file['tmp_name'] ?? null;
|
|
||||||
|
|
||||||
// --- CHECKS ---
|
|
||||||
if (!is_string($from_path))
|
|
||||||
Json::error('Passed invalid upload structure')->die();
|
|
||||||
if (!is_uploaded_file($from_path))
|
|
||||||
Json::error('Trying to upload illegal or non-existent file')->die();
|
|
||||||
|
|
||||||
// --- INITIALIZE ---
|
|
||||||
$ret = new Item();
|
|
||||||
$ret->hash = hash_file('sha256', $from_path);
|
|
||||||
$ret->extensionFromUpload($php_file);
|
|
||||||
|
|
||||||
// --- ACTUALLY GRAB FILE ---
|
|
||||||
$new_path = $ret->getPath();
|
|
||||||
if (file_exists($new_path))
|
|
||||||
Json::error('File already exists')->die();
|
|
||||||
if (!move_uploaded_file($from_path, $new_path))
|
|
||||||
Json::error('Failed to move uploaded file')->die();
|
|
||||||
|
|
||||||
return $ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function extensionFromTags()
|
|
||||||
{
|
|
||||||
foreach ($this->tags as $i_tag) {
|
|
||||||
$parts = explode(':', $i_tag);
|
|
||||||
if ($parts[0] == 'format') {
|
|
||||||
$this->extension = $parts[1];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assumes is_uploaded_file has been done
|
|
||||||
private function extensionFromUpload(array $php_file)
|
|
||||||
{
|
|
||||||
$path = $php_file['tmp_name'] ?? false;
|
|
||||||
$name = $php_file['name'] ?? null;
|
|
||||||
|
|
||||||
// Try to get extension from mimetype
|
|
||||||
$ext = (new finfo(FILEINFO_EXTENSION))->file($path);
|
|
||||||
|
|
||||||
// REVIEW: Maybe change it so it doesn't have to be separated like this but
|
|
||||||
// do somethign like ['mime/type', null] be equivalent to this?
|
|
||||||
if (array_search($ext, ['webp', 'png', 'jpeg']) !== false) {
|
|
||||||
$this->extension = $ext;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it doesn't work, try to figure it out from the original extension
|
|
||||||
if ($ext == '???') {
|
|
||||||
// We mustn't accept everything straight away, so we're only accepting
|
|
||||||
// mime-type and original extension combinations that make sense
|
|
||||||
$whitelist = [
|
|
||||||
['application/zip', 'kra'],
|
|
||||||
['application/zip', 'krz'],
|
|
||||||
];
|
|
||||||
|
|
||||||
// NOTE: The original extension has to be grabbed from the original name
|
|
||||||
$mime = mime_content_type($path);
|
|
||||||
$ext = pathinfo($name, PATHINFO_EXTENSION);
|
|
||||||
|
|
||||||
if (array_search([$mime, $ext], $whitelist) !== false) {
|
|
||||||
$this->extension = $ext;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Json::error('File mime-type and or extension not allowed.')->die();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHash(): string
|
|
||||||
{
|
|
||||||
return $this->hash;
|
|
||||||
}
|
|
||||||
public function getExtension(): string
|
|
||||||
{
|
|
||||||
return $this->extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPath(bool $absolute = false): string
|
|
||||||
{
|
|
||||||
return sprintf(
|
|
||||||
'%s/%s.%s',
|
|
||||||
$GLOBALS[$absolute ? 'path_media' : 'path_media'],
|
|
||||||
$this->hash,
|
|
||||||
$this->extension
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public function getUri(): string
|
|
||||||
{
|
|
||||||
// TODO: Implement relative
|
|
||||||
return sprintf(
|
|
||||||
'%s/%s/%s.%s',
|
|
||||||
$GLOBALS['base_root'],
|
|
||||||
$GLOBALS['base_media'],
|
|
||||||
$this->hash,
|
|
||||||
$this->extension,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getJson(): Json
|
|
||||||
{
|
|
||||||
return Json::new([
|
|
||||||
'id' => $this->hash,
|
|
||||||
'link' => $this->getUri(),
|
|
||||||
'tags' => $this->tags,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Json
|
|
||||||
{
|
|
||||||
function __construct(
|
|
||||||
private mixed $struct
|
|
||||||
) {
|
|
||||||
$this->struct = $struct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function new(mixed $struct): Json
|
|
||||||
{
|
|
||||||
return new Json($struct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function error(mixed $msg): Json
|
|
||||||
{
|
|
||||||
return new Json([
|
|
||||||
'type' => 'error',
|
|
||||||
'msg' => $msg,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function die()
|
|
||||||
{
|
|
||||||
print(json_encode($this->struct));
|
|
||||||
die();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class MediaDB
|
||||||
|
{
|
||||||
|
private $handler;
|
||||||
|
private array $lines = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Just initialize the file handler
|
||||||
|
$this->handler = fopen($GLOBALS['path_mediadb'], 'r');
|
||||||
|
if ($this->handler === false)
|
||||||
|
Json::error('Error opening media DB');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLine(): ?array
|
||||||
|
{
|
||||||
|
$ret = fgetcsv($this->handler, 0, ' ');
|
||||||
|
if ($ret === false)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function map(callable $func)
|
||||||
|
{
|
||||||
|
// First read from what's already in memory
|
||||||
|
foreach ($this->lines as $i_line) {
|
||||||
|
if ($func($i_line) === true) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we run out, read from the file and append to array so the next lookup
|
||||||
|
// is fast
|
||||||
|
while ($line = $this->getLine()) {
|
||||||
|
$this->lines[] = $line;
|
||||||
|
|
||||||
|
if ($func($line) === true) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue