Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VSLIV30-2679 attachments cache #37

Merged
merged 5 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading