Skip to content

Commit

Permalink
VSLIV30-2679 attachments cache (#37)
Browse files Browse the repository at this point in the history
* cached attachments

* Apply fixes from StyleCI

* Update CachedUploads.php

debug

* debug

---------

Co-authored-by: ngasparic <Nikola.Gasparic@asseco-see.hr>
Co-authored-by: StyleCI Bot <bot@styleci.io>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent a61f7d4 commit 42cf00e
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ public function store(Request $request, Model $model): JsonResponse
}
```

# Cached attachments

Option to keep attachments in filesystem, or cache.
Based on value ATTACHMENTS_CACHE_TYPE, there are two scenarios:
- FILE
- attachments are stored in filesystem (ATTACHMENTS_CACHE_LOCATION)
- mapping is saved in cache key ATTACHMENTS_CACHE_MAP_KEY
- [attachment1->id => path1, attachment2->id => path2, ...]
- CACHE
- attachment content is stored in cache, 1 attachment = 1 cache key
- cache key is named as: ATTACHMENTS_CACHE_KEY_PREFIX + attachment_id

ENV variables which controls the behaviour:
- ATTACHMENTS_CACHE_ENABLED=true (default: false)
- ATTACHMENTS_CACHE_MAP_KEY="ASEE_ATTACHMENTS_MAP" (default: ASEE_ATTACHMENTS_MAP)
- ATTACHMENTS_CACHE_TYPE="FILE" (default: FILE)
- ATTACHMENTS_CACHE_LOCATION="/tmp/" (default: sys_get_temp_dir())
- ATTACHMENTS_CACHE_KEY_PREFIX="ASEE_ATTACHMENT_" (default: ASEE_ATTACHMENT_)
- ATTACHMENTS_CACHE_TIME=3600 (default: 3600 seconds)

# Extending the package

Publishing the configuration will enable you to change package models as
Expand Down
21 changes: 21 additions & 0 deletions config/asseco-attachments.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,25 @@
'prefix' => 'api',
'middleware' => ['api'],
],

// Cached attachments
'cache_upload' => [
'enabled' => env('ATTACHMENTS_CACHE_ENABLED', false),

// where are the files stored: in filesystem (FILE) or Cache (CACHE)
'type' => env('ATTACHMENTS_CACHE_TYPE', 'FILE'),

// if type = FILE, files are stored in this path
'file_location' => env('ATTACHMENTS_CACHE_LOCATION', '/tmp/'),

// Cache key where mapping is saved [attachment_id => filename, attachment_id => filename, ...]
'cache_map_key' => env('ATTACHMENTS_CACHE_MAP_KEY', 'ASEE_ATTACHMENTS_MAP'),

// Cache key prefix when Cache type is used - attachments are stored separately
// key is determined as: prefix + attachment_id
'cache_key_prefix' => env('ATTACHMENTS_CACHE_KEY_PREFIX', 'ASEE_ATTACHMENT_'),

// how long is cache valid, in seconds
'cache_time' => env('ATTACHMENTS_CACHE_TIME', 3600),
],
];
3 changes: 3 additions & 0 deletions src/App/Http/Controllers/AttachmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Asseco\Attachments\App\Http\Requests\AttachmentRequest;
use Asseco\Attachments\App\Http\Requests\AttachmentUpdateRequest;
use Asseco\Attachments\App\Models\Attachment;
use Asseco\Attachments\App\Service\CachedUploads;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -46,6 +47,8 @@ public function store(AttachmentRequest $request): JsonResponse

$attachment = $this->attachment::createFrom($file, $filingPurposeId);

CachedUploads::store($file, $attachment);

return response()->json($attachment->refresh());
}

Expand Down
179 changes: 179 additions & 0 deletions src/App/Service/CachedUploads.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<?php

declare(strict_types=1);

namespace Asseco\Attachments\App\Service;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class CachedUploads
{
const DEF_CACHE_TIME = 3600;
const DEF_CACHE_KEY = 'ASEE_ATTACHMENTS_MAP';
const DEF_CACHE_PREFIX = 'ASEE_ATTACHMENT_';

const STORAGE_TYPE_FILE = 'FILE';

/**
* Is attachments cache used or not.
*
* @return bool
*/
public static function cacheUsed(): bool
{
return config('asseco-attachments.cache_upload.enabled') ?? false;
}

/**
* Get storage type - FILE | CACHE.
*
* @return string
*/
public static function getStorageType()
{
return config('asseco-attachments.cache_upload.type', self::STORAGE_TYPE_FILE) ?: self::STORAGE_TYPE_FILE;
}

/**
* If type = FILE, this is where mapping is saved.
*
* @return string
*/
public static function getCacheMapKey()
{
return config('asseco-attachments.cache_upload.cache_map_key', self::DEF_CACHE_KEY) ?: self::DEF_CACHE_KEY;
}

/**
* If type = CACHE, this is prefix for acche key, one per attachment.
*
* @return string
*/
public static function getCacheKeyPrefix()
{
return config('asseco-attachments.cache_upload.cache_key_prefix', self::DEF_CACHE_PREFIX) ?: self::DEF_CACHE_PREFIX;
}

/**
* If type = FILE, this is where files are saved.
*
* @return string
*/
public static function getFilesLocation()
{
$loc = config('asseco-attachments.cache_upload.file_location', sys_get_temp_dir()) ?: sys_get_temp_dir();

return rtrim($loc, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}

public static function getCacheTime()
{
return config('asseco-attachments.cache_upload.cache_time', self::DEF_CACHE_TIME) ?: self::DEF_CACHE_TIME;
}

/**
* Store uploaded files in tmp directory & save to Cache, for later quicker access.
*
* @param UploadedFile $file
* @param Model $attachment
* @return bool
*/
public static function store(UploadedFile $file, Model $attachment)
{
// keep & cache
if (!self::cacheUsed()) {
return false;
}

$success = false;

/** @var \Illuminate\Http\UploadedFile $file */
$from = $file->getPathname();

if (strtoupper(self::getStorageType()) == self::STORAGE_TYPE_FILE) {
// stored in files
$cacheKey = self::getCacheMapKey();
$to = self::getFilesLocation() . $attachment->id;
if (@move_uploaded_file($from, $to)) {
$cacheData = [];
if (Cache::has($cacheKey)) {
$cacheData = json_decode(Cache::get($cacheKey), true);
}
$cacheData[$attachment->id] = $to;
Cache::put($cacheKey, json_encode($cacheData), self::getCacheTime());
$success = true;
}
} else {
// stored in cache
$cacheKey = self::getCacheKeyPrefix() . $attachment->id;
Cache::put($cacheKey, file_get_contents($from), self::getCacheTime());
$success = true;
}

if ($success) {
Log::debug('Attachment ' . $attachment->id . ' stored in cache: ' . ($to ?? $cacheKey), ['method' => __METHOD__]);
}

return $success;
}

/**
* Get filename from cached attachment (stored in tmp folder).
*
* @param Model $attachment
* @return string|null
*/
public static function get(Model $attachment)
{
if (!self::cacheUsed()) {
return null;
}

if (strtoupper(self::getStorageType()) == self::STORAGE_TYPE_FILE) {
// stored in files
$cacheKey = self::getCacheMapKey();
if (!Cache::has($cacheKey)) {
return null;
}

$cacheData = json_decode(Cache::get($cacheKey), true);
$filename = Arr::get($cacheData, $attachment->id, null);
if (empty($filename)) {
return null;
}

if (is_file($filename)) {
return $filename;
}
} else {
// stored in cache
$cacheKey = self::getCacheKeyPrefix() . $attachment->id;

if (!Cache::has($cacheKey)) {
return null;
}

try {
$content = Cache::get($cacheKey);
if (empty($content)) {
return null;
}

$filename = tempnam(self::getFilesLocation(), 'attach_');
//$filename = self::getFilesLocation() . basename($attachment->path);
file_put_contents($filename, $content);

return $filename;
} catch (\Exception $e) {
Log::warning('Failed to restore attachment ' . $attachment->id . ' from cache to tmp dir.',
['method' => __METHOD__]);
}
}

return null;
}
}

0 comments on commit 42cf00e

Please sign in to comment.