From 90c1b610661e08d315228fbb4aa9da6fe4f72714 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 13:52:18 +0300 Subject: [PATCH 01/13] make demo page better --- demos/index.php | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/demos/index.php b/demos/index.php index 7af78c1..b60b0e3 100644 --- a/demos/index.php +++ b/demos/index.php @@ -4,12 +4,15 @@ namespace Atk4\Filestore\Demos; +use Atk4\Data\Model; use Atk4\Data\Persistence; use Atk4\Filestore\Helper; use Atk4\Filestore\Model\File; use Atk4\Ui\Callback; use Atk4\Ui\Columns; +use Atk4\Ui\Crud; use Atk4\Ui\Form; +use Atk4\Ui\Header; use Atk4\Ui\Js\JsExpression; use Atk4\Ui\View; use League\Flysystem\Filesystem; @@ -36,32 +39,44 @@ $col = Columns::addTo($app); -$form = Form::addTo($col->addColumn()); +// New friend form +$c1 = $col->addColumn()->setStyle('border', '1px solid gray'); + +Header::addTo($c1, ['Add New Friend']); +$form = Form::addTo($c1); $form->setModel( (new Friend($app->db, [ 'filesystem' => $filesystem, ]))->createEntity() ); -$gr = \Atk4\Ui\Grid::addTo($col->addColumn(), [ - 'menu' => false, - 'paginator' => false, -]); -$gr->setModel(new File($app->db)); - -$form->onSubmit(function (Form $form) use ($gr) { +$form->onSubmit(function (Form $form) use ($app) { $form->model->save(); - return $gr->jsReload(); + return $app->layout->jsReload(); }); -View::addTo($app, ['ui' => 'divider']); +// Grid with all filestore files +$c2 = $col->addColumn()->setStyle('border', '1px solid gray'); -$crud = \Atk4\Ui\Crud::addTo($app); -$crud->setModel(new Friend($app->db, ['filesystem' => $filesystem])); +Header::addTo($c2, ['All Filestore Files']); +$gr = Crud::addTo($c2, [ + 'paginator' => false, +]); +$files = new File($app->db, ['flysystem' => $filesystem]); +$files->removeUserAction('add'); +$files->removeUserAction('edit'); +$files->removeUserAction('delete'); +$gr->setModel($files); View::addTo($app, ['ui' => 'divider']); +// CRUD with all Friends records +Header::addTo($app, ['All Friends']); +$crud = Crud::addTo($app); +$crud->setModel(new Friend($app->db, ['filesystem' => $filesystem])); + +// custom actions $callbackDownload = Callback::addTo($app); $callbackDownload->set(function () use ($crud) { $id = $crud->getApp()->stickyGet('row_id'); From dfbe8f01d951251900441c18c4dc64bcab37d1a2 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 13:58:28 +0300 Subject: [PATCH 02/13] cleanup and fix Helper --- src/Helper.php | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Helper.php b/src/Helper.php index f491560..f63905b 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -15,8 +15,8 @@ class Helper */ public static function download(File $model, App $app): void { - $app->setResponseHeader('Content-Description', 'File Transfer'); - $app->setResponseHeader('Content-Type', 'application/octet-stream'); + $app->setResponseHeader('Content-Description', 'Download File'); + $app->setResponseHeader('Content-Type', $model->get('meta_mime_type')); $app->setResponseHeader('Cache-Control', 'must-revalidate'); $app->setResponseHeader('Expires', '-1'); $app->setResponseHeader('Content-Disposition', 'attachment; filename="' . $model->get('meta_filename') . '"'); @@ -27,24 +27,12 @@ public static function download(File $model, App $app): void static::output($model, $app); } - /** - * @return never - */ - protected static function output(File $model, App $app): void - { - $path = $model->get('location'); - $resource = $model->flysystem->readStream($path); - $stream = (new Psr17Factory())->createStreamFromResource($resource); - - $app->terminate($stream); - } - /** * @return never */ public static function view(File $model, App $app): void { - $app->setResponseHeader('Content-Description', 'File Transfer'); + $app->setResponseHeader('Content-Description', 'View File'); $app->setResponseHeader('Content-Type', $model->get('meta_mime_type')); $app->setResponseHeader('Cache-Control', 'must-revalidate'); $app->setResponseHeader('Expires', '-1'); @@ -55,4 +43,16 @@ public static function view(File $model, App $app): void static::output($model, $app); } + + /** + * @return never + */ + protected static function output(File $model, App $app): void + { + $path = $model->get('location'); + $resource = $model->flysystem->readStream($path); + $stream = (new Psr17Factory())->createStreamFromResource($resource); + + $app->terminate($stream); + } } From b578fc3d00f1b900c6d58032a22efdc2e647d084 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 14:05:40 +0300 Subject: [PATCH 03/13] cleanup statuses, fix delete --- src/Field/FileField.php | 9 +++++--- src/Form/Control/Upload.php | 2 +- src/Model/File.php | 43 ++++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/Field/FileField.php b/src/Field/FileField.php index 020de64..2c74e03 100644 --- a/src/Field/FileField.php +++ b/src/Field/FileField.php @@ -59,7 +59,8 @@ protected function init(): void $this->fieldNameBase = preg_replace('~_id$~', '', $this->shortName); $this->importFields(); - $this->onHookToOwnerEntity(Model::HOOK_AFTER_SAVE, function (Model $m) { + // on insert/update delete old file and mark new one as linked + $fx = function (Model $m) { if ($m->isDirty($this->shortName)) { $old = $m->getDirtyRef()[$this->shortName]; $new = $m->get($this->shortName); @@ -71,10 +72,12 @@ protected function init(): void // mark new file as linked if ($new) { - $m->refModel($this->shortName)->loadBy('token', $new)->save(['status' => 'linked']); + $m->refModel($this->shortName)->loadBy('token', $new)->save(['status' => File::STATUS_LINKED]); } } - }); + }; + $this->onHookToOwnerEntity(Model::HOOK_AFTER_INSERT, $fx); + $this->onHookToOwnerEntity(Model::HOOK_AFTER_UPDATE, $fx); $this->onHookToOwnerEntity(Model::HOOK_AFTER_DELETE, function (Model $m) { $token = $m->get($this->shortName); diff --git a/src/Form/Control/Upload.php b/src/Form/Control/Upload.php index 61e3f5d..bec2206 100644 --- a/src/Form/Control/Upload.php +++ b/src/Form/Control/Upload.php @@ -67,7 +67,7 @@ protected function deleted(string $token): ?JsExpressionable $model = $this->entityField->getField()->fileModel; $entity = $model->loadBy('token', $token); - if ($entity->get('status') === 'draft') { + if ($entity->get('status') === $entity::STATUS_DRAFT) { $entity->delete(); } diff --git a/src/Model/File.php b/src/Model/File.php index e4073b5..0da5497 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -6,6 +6,7 @@ use Atk4\Data\Model; use League\Flysystem\Filesystem; +use League\MimeTypeDetection\FinfoMimeTypeDetector; class File extends Model { @@ -13,34 +14,34 @@ class File extends Model public ?string $titleField = 'meta_filename'; + /** @const string All uploaded files first get this status */ + public const STATUS_DRAFT = 'draft'; + /** @const string When file is linked to some other model */ + public const STATUS_LINKED = 'linked'; + /** @const array */ + public const ALL_STATUSES = [ + self::STATUS_DRAFT, + self::STATUS_LINKED, + ]; + /** @var Filesystem */ public $flysystem; - public function newFile(): Model - { - $entity = $this->createEntity(); - - $entity->set('token', uniqid('token-')); - $entity->set('location', uniqid('file-') . '.bin'); - - return $entity; - } - protected function init(): void { parent::init(); $this->addField('token', ['system' => true, 'type' => 'string', 'required' => true]); $this->addField('location'); - $this->addField('url'); - $this->addField('storage'); - $this->hasOne('source_file_id', [ + $this->addField('url'); // not implemented + $this->addField('storage'); // not implemented + $this->hasOne('source_file_id', [ // this field can be used to link thumb images (when we'll implement that) to source image for example 'model' => [self::class], ]); $this->addField('status', [ - 'enum' => ['draft', 'uploaded', 'thumbok', 'normalok', 'ready', 'linked'], - 'default' => 'draft', + 'enum' => self::ALL_STATUSES, + 'default' => self::STATUS_DRAFT, ]); $this->addField('meta_filename'); @@ -52,10 +53,22 @@ protected function init(): void $this->addField('meta_image_width', ['type' => 'integer']); $this->addField('meta_image_height', ['type' => 'integer']); + // cascade-delete all related child files $this->onHook(Model::HOOK_BEFORE_DELETE, function (self $m) { if ($m->flysystem) { // @phpstan-ignore-line $m->flysystem->delete($m->get('location')); } }); } + + public function newFile(): Model + { + $this->assertIsModel(); + + $entity = $this->createEntity(); + $entity->set('token', uniqid('token-')); + $entity->set('location', uniqid('file-') . '.bin'); + + return $entity; + } } From bf55af2195584d4d02b5889a30999f5817b9e261 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 14:06:57 +0300 Subject: [PATCH 04/13] fix file delete --- src/Model/File.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Model/File.php b/src/Model/File.php index 0da5497..950cfe7 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -55,8 +55,17 @@ protected function init(): void // cascade-delete all related child files $this->onHook(Model::HOOK_BEFORE_DELETE, function (self $m) { - if ($m->flysystem) { // @phpstan-ignore-line - $m->flysystem->delete($m->get('location')); + $files = (clone $m->getModel())->addCondition('source_file_id', $m->getId()); + foreach ($files as $file) { + $file->delete(); + } + }); + + // delete physical file from storage after we delete DB record + $this->onHook(Model::HOOK_AFTER_DELETE, function (self $m) { + $path = $m->get('location'); + if ($path && $m->flysystem && $m->flysystem->fileExists($path)) { // @phpstan-ignore-line + $m->flysystem->delete($path); } }); } From 5680d698b2dbabe0199eb204caa071401abf4964 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 14:11:41 +0300 Subject: [PATCH 05/13] cs --- demos/index.php | 1 - src/Model/File.php | 1 - 2 files changed, 2 deletions(-) diff --git a/demos/index.php b/demos/index.php index b60b0e3..05045cd 100644 --- a/demos/index.php +++ b/demos/index.php @@ -4,7 +4,6 @@ namespace Atk4\Filestore\Demos; -use Atk4\Data\Model; use Atk4\Data\Persistence; use Atk4\Filestore\Helper; use Atk4\Filestore\Model\File; diff --git a/src/Model/File.php b/src/Model/File.php index 950cfe7..0d56ff6 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -6,7 +6,6 @@ use Atk4\Data\Model; use League\Flysystem\Filesystem; -use League\MimeTypeDetection\FinfoMimeTypeDetector; class File extends Model { From d909016eba168751b8096aa34f6f13975f2f51d6 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 14:19:00 +0300 Subject: [PATCH 06/13] try phpstan --- src/Model/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/File.php b/src/Model/File.php index 0d56ff6..c7166f4 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -63,7 +63,7 @@ protected function init(): void // delete physical file from storage after we delete DB record $this->onHook(Model::HOOK_AFTER_DELETE, function (self $m) { $path = $m->get('location'); - if ($path && $m->flysystem && $m->flysystem->fileExists($path)) { // @phpstan-ignore-line + if ($path && $m->flysystem && $m->flysystem->fileExists($path)) { $m->flysystem->delete($path); } }); From 5495f8ee81ff96323cc27d3c760b5fb5d1716d97 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 14:32:55 +0300 Subject: [PATCH 07/13] fix comments --- src/Model/File.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Model/File.php b/src/Model/File.php index c7166f4..12d1634 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -13,17 +13,17 @@ class File extends Model public ?string $titleField = 'meta_filename'; - /** @const string All uploaded files first get this status */ + /** All uploaded files first get this status */ public const STATUS_DRAFT = 'draft'; - /** @const string When file is linked to some other model */ + /** When file is linked to some other model */ public const STATUS_LINKED = 'linked'; - /** @const array */ + /** @const list */ public const ALL_STATUSES = [ self::STATUS_DRAFT, self::STATUS_LINKED, ]; - /** @var Filesystem */ + /** @var Filesystem|null */ public $flysystem; protected function init(): void From cdbf5f3452f176593733c2fa7e21402c45887574 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 16:03:22 +0300 Subject: [PATCH 08/13] remove unused fields --- demos/_demo-data/create-db.php | 2 -- src/Model/File.php | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/demos/_demo-data/create-db.php b/demos/_demo-data/create-db.php index dd086ef..d9da2e1 100644 --- a/demos/_demo-data/create-db.php +++ b/demos/_demo-data/create-db.php @@ -23,9 +23,7 @@ $fileModel->addField('token', ['required' => true]); $fileModel->addField('location', ['type' => 'text']); $fileModel->addField('url', ['type' => 'text']); -$fileModel->addField('storage'); $fileModel->addField('status'); -$fileModel->addField('source_file_id', ['type' => 'integer']); $fileModel->addField('meta_filename'); $fileModel->addField('meta_extension'); $fileModel->addField('meta_md5'); diff --git a/src/Model/File.php b/src/Model/File.php index 12d1634..d992c0c 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -33,10 +33,6 @@ protected function init(): void $this->addField('token', ['system' => true, 'type' => 'string', 'required' => true]); $this->addField('location'); $this->addField('url'); // not implemented - $this->addField('storage'); // not implemented - $this->hasOne('source_file_id', [ // this field can be used to link thumb images (when we'll implement that) to source image for example - 'model' => [self::class], - ]); $this->addField('status', [ 'enum' => self::ALL_STATUSES, @@ -52,14 +48,6 @@ protected function init(): void $this->addField('meta_image_width', ['type' => 'integer']); $this->addField('meta_image_height', ['type' => 'integer']); - // cascade-delete all related child files - $this->onHook(Model::HOOK_BEFORE_DELETE, function (self $m) { - $files = (clone $m->getModel())->addCondition('source_file_id', $m->getId()); - foreach ($files as $file) { - $file->delete(); - } - }); - // delete physical file from storage after we delete DB record $this->onHook(Model::HOOK_AFTER_DELETE, function (self $m) { $path = $m->get('location'); From 689662e9f4aa68c23884e9648d7b142a0aa7c373 Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 16:05:28 +0300 Subject: [PATCH 09/13] remove comment --- src/Model/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/File.php b/src/Model/File.php index d992c0c..36e5b91 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -32,7 +32,7 @@ protected function init(): void $this->addField('token', ['system' => true, 'type' => 'string', 'required' => true]); $this->addField('location'); - $this->addField('url'); // not implemented + $this->addField('url'); $this->addField('status', [ 'enum' => self::ALL_STATUSES, From ef803eafa2a8b92f61ea38e89b5c5d8d27089754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 16 Apr 2023 15:13:49 +0200 Subject: [PATCH 10/13] fix flysystem phpstan --- src/Model/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Model/File.php b/src/Model/File.php index 36e5b91..5381a9f 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -23,7 +23,7 @@ class File extends Model self::STATUS_LINKED, ]; - /** @var Filesystem|null */ + /** @var Filesystem */ public $flysystem; protected function init(): void @@ -51,7 +51,7 @@ protected function init(): void // delete physical file from storage after we delete DB record $this->onHook(Model::HOOK_AFTER_DELETE, function (self $m) { $path = $m->get('location'); - if ($path && $m->flysystem && $m->flysystem->fileExists($path)) { + if ($path && $m->flysystem->fileExists($path)) { $m->flysystem->delete($path); } }); From 1c557d970ff7778e340f3d2d1f78d0c1750a1b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 16 Apr 2023 15:25:30 +0200 Subject: [PATCH 11/13] review --- demos/index.php | 12 +++++------- src/Helper.php | 28 ++++++++++++++-------------- src/Model/File.php | 3 +++ 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/demos/index.php b/demos/index.php index 05045cd..01e9be7 100644 --- a/demos/index.php +++ b/demos/index.php @@ -36,11 +36,10 @@ $adapter = new \League\Flysystem\Local\LocalFilesystemAdapter(__DIR__ . '/_demo-data/localfiles'); $filesystem = new Filesystem($adapter); -$col = Columns::addTo($app); - -// New friend form -$c1 = $col->addColumn()->setStyle('border', '1px solid gray'); +$columnsLayout = Columns::addTo($app); +// new friend form +$c1 = $columnsLayout->addColumn(); Header::addTo($c1, ['Add New Friend']); $form = Form::addTo($c1); $form->setModel( @@ -55,9 +54,8 @@ return $app->layout->jsReload(); }); -// Grid with all filestore files -$c2 = $col->addColumn()->setStyle('border', '1px solid gray'); - +// list all filestore files +$c2 = $columnsLayout->addColumn(); Header::addTo($c2, ['All Filestore Files']); $gr = Crud::addTo($c2, [ 'paginator' => false, diff --git a/src/Helper.php b/src/Helper.php index f63905b..1999dec 100644 --- a/src/Helper.php +++ b/src/Helper.php @@ -10,6 +10,18 @@ class Helper { + /** + * @return never + */ + protected static function terminate(File $model, App $app): void + { + $path = $model->get('location'); + $resource = $model->flysystem->readStream($path); + $stream = (new Psr17Factory())->createStreamFromResource($resource); + + $app->terminate($stream); + } + /** * @return never */ @@ -24,7 +36,7 @@ public static function download(File $model, App $app): void $app->setResponseHeader('Pragma', 'public'); $app->setResponseHeader('Accept-Ranges', 'bytes'); - static::output($model, $app); + static::terminate($model, $app); } /** @@ -41,18 +53,6 @@ public static function view(File $model, App $app): void $app->setResponseHeader('Pragma', 'public'); $app->setResponseHeader('Accept-Ranges', 'bytes'); - static::output($model, $app); - } - - /** - * @return never - */ - protected static function output(File $model, App $app): void - { - $path = $model->get('location'); - $resource = $model->flysystem->readStream($path); - $stream = (new Psr17Factory())->createStreamFromResource($resource); - - $app->terminate($stream); + static::terminate($model, $app); } } diff --git a/src/Model/File.php b/src/Model/File.php index 5381a9f..6885aef 100644 --- a/src/Model/File.php +++ b/src/Model/File.php @@ -57,6 +57,9 @@ protected function init(): void }); } + /** + * @return static + */ public function newFile(): Model { $this->assertIsModel(); From fc9b1fb8aedb59aedf5f1663457f9a4be1cb7aab Mon Sep 17 00:00:00 2001 From: DarkSide Date: Sun, 16 Apr 2023 16:29:11 +0300 Subject: [PATCH 12/13] done --- demos/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demos/index.php b/demos/index.php index 01e9be7..6769e85 100644 --- a/demos/index.php +++ b/demos/index.php @@ -11,6 +11,7 @@ use Atk4\Ui\Columns; use Atk4\Ui\Crud; use Atk4\Ui\Form; +use Atk4\Ui\Grid; use Atk4\Ui\Header; use Atk4\Ui\Js\JsExpression; use Atk4\Ui\View; @@ -57,7 +58,7 @@ // list all filestore files $c2 = $columnsLayout->addColumn(); Header::addTo($c2, ['All Filestore Files']); -$gr = Crud::addTo($c2, [ +$gr = Grid::addTo($c2, [ 'paginator' => false, ]); $files = new File($app->db, ['flysystem' => $filesystem]); From 6265531d1afcb942ef2b321b9f2ff75c70f1fda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 16 Apr 2023 15:31:23 +0200 Subject: [PATCH 13/13] fix done --- demos/index.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/demos/index.php b/demos/index.php index 6769e85..966ce44 100644 --- a/demos/index.php +++ b/demos/index.php @@ -62,9 +62,6 @@ 'paginator' => false, ]); $files = new File($app->db, ['flysystem' => $filesystem]); -$files->removeUserAction('add'); -$files->removeUserAction('edit'); -$files->removeUserAction('delete'); $gr->setModel($files); View::addTo($app, ['ui' => 'divider']);