Merge branch 'dev'

* dev:
  v1.7.3 rc.1
  chunk upload and multipart upload beta.2
  chunk upload and multipart upload beta.1
  multipart upload build
  multipart upload
  chunk upload
  chunk upload
This commit is contained in:
carodej
2020-07-30 08:58:19 +02:00
25 changed files with 633 additions and 334 deletions

View File

@@ -145,15 +145,17 @@ class FileManagerFile extends Model
*/
public function getFileUrlAttribute()
{
// Get file from s3
// Get file from external storage
if (is_storage_driver(['s3', 'spaces', 'wasabi', 'backblaze'])) {
$file_pretty_name = get_pretty_name($this->attributes['basename'], $this->attributes['name'], $this->attributes['mimetype']);
$header = [
"ResponseAcceptRanges" => "bytes",
"ResponseContentType" => $this->attributes['mimetype'],
"ResponseContentLength" => $this->attributes['filesize'],
"ResponseContentRange" => "bytes 0-600/" . $this->attributes['filesize'],
'ResponseContentDisposition' => 'attachment; filename=' . $this->attributes['name'] . '.' . $this->attributes['mimetype'],
'ResponseContentDisposition' => 'attachment; filename=' . $file_pretty_name,
];
return Storage::temporaryUrl('file-manager/' . $this->attributes['basename'], now()->addDay(), $header);

View File

@@ -197,8 +197,7 @@ class FileAccessController extends Controller
*/
private function download_file($file)
{
// Format pretty filename
$file_pretty_name = $file->name . '.' . $file->mimetype;
$file_pretty_name = get_pretty_name($file->basename, $file->name, $file->mimetype);
// Get file path
$path = '/file-manager/' . $file->basename;

View File

@@ -148,6 +148,7 @@ class BrowseController extends Controller
$folders = FileManagerFolder::with(['parent', 'shared:token,id,item_id,permission,protected'])
->where('user_id', $user_id)
->where('parent_id', $unique_id)
->orderBy('created_at', 'DESC')
->get();
$files = FileManagerFile::with(['parent', 'shared:token,id,item_id,permission,protected'])

View File

@@ -245,7 +245,7 @@ class EditItemsController extends Controller
}
/**
* Delete file for authenticated master|editor user
* Upload file for authenticated master|editor user
*
* @param UploadRequest $request
* @return FileManagerFile|Model

View File

@@ -468,8 +468,8 @@ class SetupWizardController extends Controller
// Create legal pages and index content
if ($request->license === 'Extended') {
$pages = collect(config('vuefilemanager.pages'));
$content = collect(config('vuefilemanager.content'));
$pages = collect(config('content.pages'));
$content = collect(config('content.content'));
$content->each(function ($content) {
Setting::updateOrCreate($content);

View File

@@ -25,8 +25,8 @@ class UpgradeAppController extends Controller
// Create legal pages and index content
if ($request->license === 'Extended') {
$pages = collect(config('vuefilemanager.pages'));
$content = collect(config('vuefilemanager.content'));
$pages = collect(config('content.pages'));
$content = collect(config('content.content'));
$content->each(function ($content) {
Setting::updateOrCreate($content);

View File

@@ -19,11 +19,11 @@ use Intervention\Image\ImageManagerStatic as Image;
*/
function obfuscate_email($email)
{
$em = explode("@",$email);
$name = implode('@', array_slice($em, 0, count($em)-1));
$len = floor(strlen($name)/2);
$em = explode("@", $email);
$name = implode('@', array_slice($em, 0, count($em) - 1));
$len = floor(strlen($name) / 2);
return substr($name,0, $len) . str_repeat('*', $len) . "@" . end($em);
return substr($name, 0, $len) . str_repeat('*', $len) . "@" . end($em);
}
/**
@@ -355,6 +355,17 @@ function format_gigabytes($gigabytes)
}
}
/**
* Convert megabytes to bytes
*
* @param $megabytes
* @return int|string
*/
function format_bytes($megabytes)
{
return Metric::megabytes($megabytes)->numberOfBytes();
}
/**
* Get storage usage in percent
*
@@ -464,10 +475,10 @@ function format_date($date, $format = '%d. %B. %Y, %H:%M')
* @param $file
* @return string
*/
function get_file_type($file)
function get_file_type($file_mimetype)
{
// Get mimetype from file
$mimetype = explode('/', $file->getMimeType());
$mimetype = explode('/', $file_mimetype);
switch ($mimetype[0]) {
case 'image':
@@ -482,4 +493,39 @@ function get_file_type($file)
default:
return 'file';
}
}
/**
* Get file type from mimetype
*
* @param $mimetype
* @return mixed
*/
function get_file_type_from_mimetype($mimetype)
{
return explode('/', $mimetype)[1];
}
/**
* Format pretty name file
*
* @param $basename
* @param $name
* @param $mimetype
* @return string
*/
function get_pretty_name($basename, $name, $mimetype)
{
$file_extension = substr(strrchr($basename, '.'), 1);
if (strpos($name, $file_extension) !== false) {
return $name;
}
if ($file_extension) {
return $name . '.' . $file_extension;
}
return $name . '.' . $mimetype;
}

View File

@@ -102,7 +102,7 @@ class Demo
$filename = Str::random() . '-' . str_replace(' ', '', $file->getClientOriginalName());
$thumbnail = null;
$filesize = $file->getSize();
$filetype = get_file_type($file);
$filetype = get_file_type($file->getMimeType());
return [
'id' => random_int(1000, 9999),

View File

@@ -8,11 +8,17 @@ use App\FileManagerFile;
use App\FileManagerFolder;
use App\Http\Requests\FileFunctions\RenameItemRequest;
use App\User;
use Aws\Exception\MultipartUploadException;
use Aws\S3\MultipartUploader;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\ImageManagerStatic as Image;
use Symfony\Component\HttpKernel\Exception\HttpException;
class Editor
@@ -187,88 +193,6 @@ class Editor
}
}
/**
* Upload file
*
* @param $request
* @param null $shared
* @return FileManagerFile|\Illuminate\Database\Eloquent\Model
* @throws \Exception
*/
public static function upload($request, $shared = null)
{
// Get parent_id from request
$folder_id = $request->parent_id === 0 ? 0 : $request->parent_id;
$file = $request->file('file');
// Get user data
$user_scope = is_null($shared) ? $request->user()->token()->scopes[0] : 'editor';
$user_id = is_null($shared) ? Auth::id() : $shared->user_id;
$user_storage_used = user_storage_percentage($user_id, $file->getSize());
// Get storage limitation setup
$storage_limitation = get_setting('storage_limitation');
// Check if user can upload
if ($storage_limitation && $user_storage_used >= 100) {
abort(423, 'You exceed your storage limit!');
}
// File
$filename = Str::random() . '-' . str_replace(' ', '', $file->getClientOriginalName());
$filetype = get_file_type($file);
$filesize = $file->getSize();
$directory = 'file-manager';
$thumbnail = null;
// create directory if not exist
if (!Storage::exists($directory)) {
Storage::makeDirectory($directory);
}
// Store to disk
Storage::putFileAs($directory, $file, $filename, 'private');
// Create image thumbnail
if (in_array($file->getMimeType(), ['image/gif', 'image/jpeg', 'image/jpg', 'image/png', 'image/webp'])) {
// Get thumbnail name
$thumbnail = 'thumbnail-' . $filename;
// Create intervention image
$image = Image::make($file->getRealPath())->orientate();
// Resize image
$image->resize(564, null, function ($constraint) {
$constraint->aspectRatio();
})->stream();
// Store thumbnail to disk
Storage::put($directory . '/' . $thumbnail, $image);
} elseif ($file->getMimeType() == 'image/svg+xml') {
$thumbnail = $filename;
}
// Store file
$options = [
'name' => pathinfo($file->getClientOriginalName())['filename'],
'mimetype' => $file->getClientOriginalExtension(),
'unique_id' => get_unique_id(),
'user_scope' => $user_scope,
'folder_id' => $folder_id,
'thumbnail' => $thumbnail,
'basename' => $filename,
'filesize' => $filesize,
'type' => $filetype,
'user_id' => $user_id,
];
// Return new file
return FileManagerFile::create($options);
}
/**
* Move folder or file to new location
*
@@ -304,4 +228,258 @@ class Editor
]);
}
}
/**
* Upload file
*
* @param $request
* @param null $shared
* @return FileManagerFile|\Illuminate\Database\Eloquent\Model
* @throws \Exception
*/
public static function upload($request, $shared = null)
{
// Get parent_id from request
$file = $request->file('file');
// Check or create directories
self::check_directories(['chunks', 'file-manager']);
// File name
$user_file_name = basename('chunks/' . substr($file->getClientOriginalName(), 17), '.part');
$disk_file_name = basename('chunks/' . $file->getClientOriginalName(), '.part');
$temp_filename = $file->getClientOriginalName();
// Generate file
File::append(storage_path() . '/app/chunks/' . $temp_filename, $file->get());
// If last then process file
if ($request->boolean('is_last')) {
$disk_local = Storage::disk('local');
// Get user data
$user_scope = is_null($shared) ? $request->user()->token()->scopes[0] : 'editor';
$user_id = is_null($shared) ? Auth::id() : $shared->user_id;
// File Info
$file_size = $disk_local->size('chunks/' . $temp_filename);
$file_mimetype = $disk_local->mimeType('chunks/' . $temp_filename);
// Check if user has enough space to upload file
self::check_user_storage_capacity($user_id, $file_size, $temp_filename);
// Create thumbnail
$thumbnail = self::get_image_thumbnail('chunks/' . $temp_filename, $user_file_name);
// Move finished file from chunk to file-manager directory
$disk_local->move('chunks/' . $temp_filename, 'file-manager/' . $disk_file_name);
// Store file
$options = [
'mimetype' => get_file_type_from_mimetype($file_mimetype),
'type' => get_file_type($file_mimetype),
'folder_id' => $request->parent_id,
'name' => $user_file_name,
'unique_id' => get_unique_id(),
'basename' => $disk_file_name,
'user_scope' => $user_scope,
'thumbnail' => $thumbnail,
'filesize' => $file_size,
'user_id' => $user_id,
];
// Move files to external storage
if (!is_storage_driver(['local'])) {
// Clear failed uploads if exists
self::clear_failed_files();
// Move file to external storage service
self::move_to_external_storage($disk_file_name, $thumbnail);
}
// Return new file
return FileManagerFile::create($options);
}
}
/**
* Clear failed files
*/
private static function clear_failed_files()
{
$local_disk = Storage::disk('local');
// Get all files from storage
$files = collect([
$local_disk->allFiles('file-manager'),
$local_disk->allFiles('chunks')
])->collapse();
$files->each(function ($file) use ($local_disk) {
// Get the file's last modification time.
$last_modified = $local_disk->lastModified($file);
// Get diffInHours
$diff = Carbon::parse($last_modified)->diffInHours(Carbon::now());
// Delete if file is in local storage more than 24 hours
if ($diff > 24) {
Log::info('Failed file or chunk ' . $file . ' deleted.');
// Delete file from local storage
$local_disk->delete($file);
}
});
}
/**
* Move file to external storage if is set
*
* @param string $filename
* @param string|null $thumbnail
*/
private static function move_to_external_storage(string $filename, ?string $thumbnail): void
{
$disk_local = Storage::disk('local');
foreach ([$filename, $thumbnail] as $file) {
// Check if file exist
if (!$file) continue;
// Get file size
$filesize = $disk_local->size('file-manager/' . $file);
// If file is bigger than 5.2MB then run multipart upload
if ($filesize > 5242880) {
// Get driver
$driver = \Storage::getDriver();
// Get adapter
$adapter = $driver->getAdapter();
// Get client
$client = $adapter->getClient();
// Prepare the upload parameters.
$uploader = new MultipartUploader($client, storage_path() . '/app/file-manager/' . $file, [
'bucket' => $adapter->getBucket(),
'key' => 'file-manager/' . $file
]);
try {
// Upload content
$uploader->upload();
} catch (MultipartUploadException $e) {
// Write error log
Log::error($e->getMessage());
// Delete file after error
$disk_local->delete('file-manager/' . $file);
throw new HttpException(409, $e->getMessage());
}
} else {
// Stream file object to s3
Storage::putFileAs('file-manager', storage_path() . '/app/file-manager/' . $file, $file, 'private');
}
// Delete file after upload
$disk_local->delete('file-manager/' . $file);
}
}
/**
* Check if directories 'chunks' and 'file-manager exist', if no, then create
*
* @param $directories
*/
private static function check_directories($directories): void
{
foreach ($directories as $directory) {
if (!Storage::disk('local')->exists($directory)) {
Storage::disk('local')->makeDirectory($directory);
}
if (!is_storage_driver(['local'])) {
if (!Storage::exists($directory)) {
Storage::makeDirectory($directory);
}
}
}
}
/**
* Create thumbnail for images
*
* @param string $file_path
* @param string $filename
* @param $file
* @return string|null
*/
private static function get_image_thumbnail(string $file_path, string $filename)
{
$local_disk = Storage::disk('local');
// Create thumbnail from image
if (in_array($local_disk->mimeType($file_path), ['image/gif', 'image/jpeg', 'image/jpg', 'image/png', 'image/webp'])) {
// Get thumbnail name
$thumbnail = 'thumbnail-' . $filename;
// Create intervention image
$image = Image::make(storage_path() . '/app/' . $file_path)->orientate();
// Resize image
$image->resize(512, null, function ($constraint) {
$constraint->aspectRatio();
})->stream();
// Store thumbnail to disk
$local_disk->put('file-manager/' . $thumbnail, $image);
}
// Return thumbnail as svg file
if ($local_disk->mimeType($file_path) === 'image/svg+xml') {
$thumbnail = $filename;
}
return $thumbnail ?? null;
}
/**
* Check if user has enough space to upload file
*
* @param $user_id
* @param int $file_size
* @param $temp_filename
*/
private static function check_user_storage_capacity($user_id, int $file_size, $temp_filename): void
{
// Get user storage percentage and get storage_limitation setting
$user_storage_used = user_storage_percentage($user_id, $file_size);
$storage_limitation = get_setting('storage_limitation');
// Check if user can upload
if ($storage_limitation && $user_storage_used >= 100) {
// Delete file
Storage::disk('local')->delete('chunks/' . $temp_filename);
// Abort uploading
abort(423, 'You exceed your storage limit!');
}
}
}