Update quota counters on upload and deletion

This commit is contained in:
Sergio Brighenti 2020-02-28 14:29:29 +01:00
parent b2e0d683a2
commit 4297683e74
7 changed files with 141 additions and 62 deletions

View file

@ -1,9 +1,15 @@
## v.3.1 (WIP)
+ The theme is now re-applied after every system update.
+ Added registration system.
+ Added password recovery system.
+ Added ability to export all media of an account.
+ Added ability to choose between default and raw url on copy.
+ Added hide by default option.
+ Added user disk quota.
+ Fixed bug html files raws are rendered in a browser.
+ The theme is now re-applied after every system update.
+ Updated system settings page.
+ Updated translations.
+ Small fixes and improvements.
## v.3.0.2
+ Fixed error with migrate command.

View file

@ -2,7 +2,10 @@
namespace App\Controllers;
use App\Database\DB;
use App\Web\Media;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -102,29 +105,7 @@ class AdminController extends Controller
*/
public function recalculateUserQuota(Response $response): Response
{
$uploads = $this->database->query('SELECT `id`,`user_id`, `storage_path` FROM `uploads`')->fetchAll();
$usersQuotas = [];
$filesystem = $this->storage;
foreach ($uploads as $upload) {
if (!array_key_exists($upload->user_id, $usersQuotas)) {
$usersQuotas[$upload->user_id] = 0;
}
try {
$usersQuotas[$upload->user_id] += $filesystem->getSize($upload->storage_path);
} catch (FileNotFoundException $e) {
$this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $upload->id);
}
}
foreach ($usersQuotas as $userId => $quota) {
$this->database->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [
$quota,
$userId,
]);
}
Media::recalculateQuotas($this->database, $this->storage);
$this->session->alert(lang('quota_recalculated'));
return redirect($response, route('system'));
}

View file

@ -73,6 +73,38 @@ abstract class Controller
return $totalSize;
}
/**
* @param Request $request
* @param $userId
* @param $fileSize
* @param bool $dec
* @return bool
* @throws HttpNotFoundException
* @throws HttpUnauthorizedException
*/
protected function updateUserQuota(Request $request, $userId, $fileSize, $dec = false)
{
$user = $this->getUser($request, $userId);
if ($dec) {
$tot = max($user->current_disk_quota - $fileSize, 0);
} else {
$tot = $user->current_disk_quota + $fileSize;
$quotaEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'quota_enabled\'')->fetch()->value ?? 'off';
if ($quotaEnabled === 'on' && $user->max_disk_quota > 0 && $user->max_disk_quota < $tot) {
return false;
}
}
$this->database->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [
$tot,
$user->id
]);
return true;
}
/**
* @param Request $request
* @param $id

View file

@ -189,6 +189,7 @@ class MediaController extends Controller
* @throws HttpUnauthorizedException
*
* @throws HttpNotFoundException
* @throws FileNotFoundException
*/
public function delete(Request $request, Response $response, int $id): Response
{
@ -199,7 +200,8 @@ class MediaController extends Controller
}
if ($this->session->get('admin', false) || $media->user_id === $this->session->get('user_id')) {
$this->deleteMedia($request, $media->storage_path, $id);
$size = $this->deleteMedia($request, $media->storage_path, $id);
$this->updateUserQuota($request, $media->user_id, $size, true);
$this->logger->info('User '.$this->session->get('username').' deleted a media.', [$id]);
$this->session->set('used_space', humanFileSize($this->getUsedSpaceByUser($this->session->get('user_id'))));
} else {
@ -248,7 +250,8 @@ class MediaController extends Controller
}
if ($this->session->get('admin', false) || $user->id === $media->user_id) {
$this->deleteMedia($request, $media->storage_path, $media->mediaId);
$size = $this->deleteMedia($request, $media->storage_path, $media->mediaId);
$this->updateUserQuota($request, $media->user_id, $size, true);
$this->logger->info('User '.$user->username.' deleted a media via token.', [$media->mediaId]);
} else {
throw new HttpUnauthorizedException($request);
@ -262,12 +265,15 @@ class MediaController extends Controller
* @param string $storagePath
* @param int $id
*
* @return bool|false|int
* @throws HttpNotFoundException
*/
protected function deleteMedia(Request $request, string $storagePath, int $id)
{
try {
$size = $this->storage->getSize($storagePath);
$this->storage->delete($storagePath);
return $size;
} catch (FileNotFoundException $e) {
throw new HttpNotFoundException($request);
} finally {

View file

@ -39,6 +39,7 @@ class UploadController extends Controller
*
* @return Response
* @throws FileExistsException
* @throws \Exception
*
*/
public function upload(Request $request, Response $response): Response
@ -96,33 +97,44 @@ class UploadController extends Controller
return json($response, $json, 401);
}
do {
$code = humanRandomString();
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
$json['message'] = 'User disk quota exceeded.';
$published = 1;
if (($this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off') === 'on') {
$published = 0;
return json($response, $json, 507);
}
$fileInfo = pathinfo($file->getClientFilename());
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
try {
do {
$code = humanRandomString();
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
$this->storage->writeStream($storagePath, $file->getStream()->detach());
$published = 1;
if (($this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off') === 'on') {
$published = 0;
}
$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
$user->id,
$code,
$file->getClientFilename(),
$storagePath,
$published,
]);
$fileInfo = pathinfo($file->getClientFilename());
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
$json['message'] = 'OK.';
$json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
$this->storage->writeStream($storagePath, $file->getStream()->detach());
$this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]);
$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
$user->id,
$code,
$file->getClientFilename(),
$storagePath,
$published,
]);
return json($response, $json, 201);
$json['message'] = 'OK.';
$json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
$this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]);
return json($response, $json, 201);
} catch (\Exception $e) {
$this->updateUserQuota($request, $user->id, $file->getSize(), false);
throw $e;
}
}
}

37
app/Web/Media.php Normal file
View file

@ -0,0 +1,37 @@
<?php
namespace App\Web;
use App\Database\DB;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
class Media
{
public static function recalculateQuotas(DB $db, Filesystem $filesystem)
{
$uploads = $db->query('SELECT `id`,`user_id`, `storage_path` FROM `uploads`')->fetchAll();
$usersQuotas = [];
foreach ($uploads as $upload) {
if (!array_key_exists($upload->user_id, $usersQuotas)) {
$usersQuotas[$upload->user_id] = 0;
}
try {
$usersQuotas[$upload->user_id] += $filesystem->getSize($upload->storage_path);
} catch (FileNotFoundException $e) {
$db->query('DELETE FROM `uploads` WHERE `id` = ?', $upload->id);
}
}
foreach ($usersQuotas as $userId => $quota) {
$db->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [
$quota,
$userId,
]);
}
}
}

View file

@ -6,6 +6,7 @@ require __DIR__.'/../vendor/autoload.php';
use App\Database\DB;
use App\Database\Migrator;
use App\Factories\ViewFactory;
use App\Web\Media;
use App\Web\Session;
use App\Web\View;
use DI\Bridge\Slim\Bridge;
@ -25,16 +26,16 @@ define('BASE_DIR', realpath(__DIR__.'/../').DIRECTORY_SEPARATOR);
// default config
$config = [
'base_url' => str_replace('/install/', '', (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')."://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"),
'debug' => true,
'db' => [
'debug' => true,
'db' => [
'connection' => 'sqlite',
'dsn' => realpath(__DIR__.'/../').implode(DIRECTORY_SEPARATOR, ['resources', 'database', 'xbackbone.db']),
'username' => null,
'password' => null,
'dsn' => realpath(__DIR__.'/../').implode(DIRECTORY_SEPARATOR, ['resources', 'database', 'xbackbone.db']),
'username' => null,
'password' => null,
],
'storage' => [
'driver' => 'local',
'path' => realpath(__DIR__.'/../').DIRECTORY_SEPARATOR.'storage',
'path' => realpath(__DIR__.'/../').DIRECTORY_SEPARATOR.'storage',
],
];
@ -45,7 +46,7 @@ if (file_exists(__DIR__.'/../config.php')) {
$builder = new ContainerBuilder();
$builder->addDefinitions([
'config' => value($config),
'config' => value($config),
View::class => factory(function (Container $container) {
return ViewFactory::createInstallerInstance($container);
}),
@ -93,7 +94,7 @@ $app->get('/', function (Response $response, View $view, Session $session) use (
]);
})->setName('install');
$app->post('/', function (Request $request, Response $response, Filesystem $storage, Session $session) use (&$config) {
$app->post('/', function (Request $request, Response $response, Filesystem $storage, Session $session, DB $db) use (&$config) {
// Check if there is a previous installation, if not, setup the config file
$installed = true;
@ -185,8 +186,6 @@ $app->post('/', function (Request $request, Response $response, Filesystem $stor
$firstMigrate = true;
}
$db = new DB(dsnFromConfig($config), $config['db']['username'], $config['db']['password']);
$migrator = new Migrator($db, __DIR__.'/../resources/schemas', $firstMigrate);
$migrator->migrate();
} catch (PDOException $e) {
@ -200,6 +199,18 @@ $app->post('/', function (Request $request, Response $response, Filesystem $stor
$db->query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES (?, 'admin', ?, 1, ?)", [param($request, 'email'), password_hash(param($request, 'password'), PASSWORD_DEFAULT), humanRandomString(5)]);
}
// re-apply the previous theme if is present
$css = $db->query('SELECT `value` FROM `settings` WHERE `key` = \'css\'')->fetch()->value;
if ($css) {
$content = file_get_contents($css);
if ($content !== false) {
file_put_contents(BASE_DIR.'static/bootstrap/css/bootstrap.min.css', $content);
}
}
// recalculate user quota
Media::recalculateQuotas($db, $storage);
// if is upgrading and existing installation, put it out maintenance
if ($installed) {
unset($config['maintenance']);
@ -217,12 +228,6 @@ $app->post('/', function (Request $request, Response $response, Filesystem $stor
return redirect($response, '/install');
}
// re-apply the previous theme if is present
$css = $db->query('SELECT `value` FROM `settings` WHERE `key` = \'css\'')->fetch()->value;
if ($css) {
file_put_contents(BASE_DIR.'static/bootstrap/css/bootstrap.min.css', file_get_contents($css));
}
// post install cleanup
cleanDirectory(__DIR__.'/../resources/cache');
cleanDirectory(__DIR__.'/../resources/sessions');