Browse Source

Update quota counters on upload and deletion

Sergio Brighenti 5 years ago
parent
commit
4297683e74

+ 7 - 1
CHANGELOG.md

@@ -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.

+ 4 - 23
app/Controllers/AdminController.php

@@ -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'));
     }

+ 32 - 0
app/Controllers/Controller.php

@@ -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

+ 8 - 2
app/Controllers/MediaController.php

@@ -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 {

+ 32 - 20
app/Controllers/UploadController.php

@@ -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]";
+
+            $this->storage->writeStream($storagePath, $file->getStream()->detach());
 
-        $json['message'] = 'OK.';
-        $json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
+            $this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
+                $user->id,
+                $code,
+                $file->getClientFilename(),
+                $storagePath,
+                $published,
+            ]);
 
-        $this->logger->info("User $user->username uploaded new media.", [$this->database->getPdo()->lastInsertId()]);
+            $json['message'] = 'OK.';
+            $json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
 
-        return json($response, $json, 201);
+            $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 - 0
app/Web/Media.php

@@ -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,
+            ]);
+        }
+    }
+}

+ 21 - 16
install/index.php

@@ -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');