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

@@ -16,6 +16,7 @@ But, it can't be done without you, development is more and more complicated and
- [Server Requirements](#server-requirements)
- [Installation](#installation)
- [PHP Configuration](#php-configuration)
- [Chunk Upload](#chunk-upload)
- [Nginx Configuration](#nginx-configuration)
- [Apache Configuration](#apache-configuration)
- [Recover Failed Installation](#installation-failed)
@@ -62,26 +63,34 @@ But, it can't be done without you, development is more and more complicated and
## Installation
#### 1. Upload files on your server
Copy project files to web root folder of your domain. It's mostly located in `html`, `www` or `public_html` folder name.
Then change your **public directory** for your domain. It should be changed to /public directory in the app.
#### 2. Configure your web root folder
Configure your web server's document / web root to point to the public directory of the software. For example, if you've uploaded the software in `example.com` folder, your web directory should be changed to `example.com/public` folder.
#### 3. Check your .env file
Make sure `.env` file was uploaded. This type of file can be hidden in default.
#### 3.1 When you install from GitHub
When you download repository from GitHub, you have to rename your `.env.example` file to `.env`. Then run command below in your terminal to install vendors. Composer is required.
```
composer install
```
#### 4. Set write permissions
Set `755` permission (CHMOD) to these file and folders directory within all children subdirectories:
- /bootstrap/cache
- /storage
- /.env
#### 5. Open your application in your web browser
Then open your application in web browser. If everything works fine, you will be redirect to setup wizard installation process.
At first step you have to verify your purchase code. **Subscription service with stripe payments is available only for Extended License.**
At first step you have to verify your purchase code. **Subscription service with stripe payments is available only for Extended License.** If you can't verify your purchase code, check, if you did previously steps correctly.
#### 6. Follow setup wizard steps
That was the hardest part of installation proces. Please follow instructions in every step of Setup Wizard to successfully install VueFileManager.
@@ -90,11 +99,18 @@ There are several PHP settings good to know to setup before you try upload any f
```
memory_limit = 512M
upload_max_filesize = 1024M
post_max_size = 1024M
upload_max_filesize = 128M
post_max_size = 128M
max_file_uploads = 50
max_execution_time = 3600
```
## Chunk & Multipart Upload
VueFileManager in default supporting chunk upload. Default chunk upload size is `128MB`. If you wish change this default value, go to `/config/vuefilemanager.php` and change `chunk_size` attribute.
When you use external storage, and upload large files, to prevent failing upload process make sure you have enough space in your application space and set higher `max_execution_time` in your php.ini to move your files to external storage.
## Nginx Configuration
If you running VueFileManager undex Nginx, don't forget set this value in your `nginx.conf` file:
```
@@ -322,4 +338,3 @@ The following support channels are available at your fingertips:
If you discover a security vulnerability within this project, please send an e-mail to [peterpapp@makingcg.com](peterpapp@makingcg.com). All security vulnerabilities will be promptly addressed.

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

102
config/content.php Normal file
View File

@@ -0,0 +1,102 @@
<?php
return [
'pages' => [
[
'visibility' => 1,
'title' => 'Terms of Service',
'slug' => 'terms-of-service',
'content' => 'Laoreet cum hendrerit iaculis arcu phasellus congue et elementum, pharetra risus imperdiet aptent posuere rutrum parturient blandit, dapibus tellus ridiculus potenti aliquam sociis turpis. Nullam commodo eget laoreet risus cursus vel placerat, in dapibus sociis gravida faucibus sodales, fringilla potenti elit semper iaculis ullamcorper. Dignissim vulputate pretium montes pellentesque mollis, consectetur adipiscing curabitur semper sem rhoncus, litora viverra curae proin.',
],
[
'visibility' => 1,
'title' => 'Privacy Policy',
'slug' => 'privacy-policy',
'content' => 'Sit orci justo augue maecenas laoreet consectetur natoque magnis in viverra sagittis, himenaeos urna facilisis mus proin primis diam accumsan tristique inceptos. Primis quisque posuere sit praesent lobortis feugiat semper convallis facilisis, vivamus gravida ligula nostra curae eu donec duis parturient senectus, arcu dolor viverra penatibus natoque cum nisi commodo. Litora sociis mauris justo nullam suspendisse mattis maecenas nascetur congue phasellus cras ultricies posuere donec, dapibus egestas diam lacus ornare montes senectus tincidunt eu taciti sed consequat.',
],
[
'visibility' => 1,
'title' => 'Cookie Policy',
'slug' => 'cookie-policy',
'content' => 'Metus penatibus ligula dolor natoque non habitasse laoreet facilisis, libero vivamus eget semper vulputate interdum integer, phasellus lorem enim blandit consectetur nullam sollicitudin. Hendrerit interdum luctus ut in molestie himenaeos eros cum laoreet parturient est, eu lectus hac et netus viverra dictumst congue elit sem senectus litora, fames scelerisque adipiscing inceptos fringilla montes sociosqu suscipit auctor potenti. Elementum lacus vulputate viverra ac morbi ligula ipsum facilisi, sit eu imperdiet lacinia congue dis vitae.',
],
],
'content' => [
[
'name' => 'section_features',
'value' => '1',
],
[
'name' => 'section_feature_boxes',
'value' => '1',
],
[
'name' => 'section_pricing_content',
'value' => '1',
],
[
'name' => 'section_get_started',
'value' => '1',
],
[
'name' => 'header_title',
'value' => 'Simple <span style="color: #41B883">&</span> Powerful Personal Cloud Storage',
],
[
'name' => 'header_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'features_title',
'value' => 'The Fastest Growing <span style="color: #41B883">File Manager</span> on the CodeCanyon Market',
],
[
'name' => 'features_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'feature_title_1',
'value' => 'Truly Freedom',
],
[
'name' => 'feature_description_1',
'value' => 'You have full control over VueFileManager, no third authorities will control your service or usage, only you.',
],
[
'name' => 'feature_title_2',
'value' => 'The Sky is the Limit',
],
[
'name' => 'feature_description_2',
'value' => 'VueFileManager is cloud storage software. You have to install and running application on your own server hosting.',
],
[
'name' => 'feature_title_3',
'value' => 'No Monthly Fees',
],
[
'name' => 'feature_description_3',
'value' => 'When you running VueFileManager on your own server hosting, anybody can\'t control your content or resell your user data. Your data is safe.',
],
[
'name' => 'pricing_title',
'value' => 'Pick the <span style="color: #41B883;">Best Plan</span> For Your Needs',
],
[
'name' => 'pricing_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'get_started_title',
'value' => 'Ready to Get <span style="color: #41B883">Started</span><br> With Us?',
],
[
'name' => 'get_started_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'footer_content',
'value' => '© 2020 Simple & Powerfull Personal Cloud Storage. Developed by <a href="https://hi5ve.digital" target="_blank">Hi5Ve.Digital</a>',
],
],
];

View File

@@ -2,105 +2,8 @@
return [
'version' => '1.7.2',
'version' => '1.7.3',
'pages' => [
[
'visibility' => 1,
'title' => 'Terms of Service',
'slug' => 'terms-of-service',
'content' => 'Laoreet cum hendrerit iaculis arcu phasellus congue et elementum, pharetra risus imperdiet aptent posuere rutrum parturient blandit, dapibus tellus ridiculus potenti aliquam sociis turpis. Nullam commodo eget laoreet risus cursus vel placerat, in dapibus sociis gravida faucibus sodales, fringilla potenti elit semper iaculis ullamcorper. Dignissim vulputate pretium montes pellentesque mollis, consectetur adipiscing curabitur semper sem rhoncus, litora viverra curae proin.',
],
[
'visibility' => 1,
'title' => 'Privacy Policy',
'slug' => 'privacy-policy',
'content' => 'Sit orci justo augue maecenas laoreet consectetur natoque magnis in viverra sagittis, himenaeos urna facilisis mus proin primis diam accumsan tristique inceptos. Primis quisque posuere sit praesent lobortis feugiat semper convallis facilisis, vivamus gravida ligula nostra curae eu donec duis parturient senectus, arcu dolor viverra penatibus natoque cum nisi commodo. Litora sociis mauris justo nullam suspendisse mattis maecenas nascetur congue phasellus cras ultricies posuere donec, dapibus egestas diam lacus ornare montes senectus tincidunt eu taciti sed consequat.',
],
[
'visibility' => 1,
'title' => 'Cookie Policy',
'slug' => 'cookie-policy',
'content' => 'Metus penatibus ligula dolor natoque non habitasse laoreet facilisis, libero vivamus eget semper vulputate interdum integer, phasellus lorem enim blandit consectetur nullam sollicitudin. Hendrerit interdum luctus ut in molestie himenaeos eros cum laoreet parturient est, eu lectus hac et netus viverra dictumst congue elit sem senectus litora, fames scelerisque adipiscing inceptos fringilla montes sociosqu suscipit auctor potenti. Elementum lacus vulputate viverra ac morbi ligula ipsum facilisi, sit eu imperdiet lacinia congue dis vitae.',
],
],
'content' => [
[
'name' => 'section_features',
'value' => '1',
],
[
'name' => 'section_feature_boxes',
'value' => '1',
],
[
'name' => 'section_pricing_content',
'value' => '1',
],
[
'name' => 'section_get_started',
'value' => '1',
],
[
'name' => 'header_title',
'value' => 'Simple <span style="color: #41B883">&</span> Powerfull Personal Cloud Storage',
],
[
'name' => 'header_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'features_title',
'value' => 'The Fastest Growing <span style="color: #41B883">File Manager</span> on the CodeCanyon Market',
],
[
'name' => 'features_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'feature_title_1',
'value' => 'Truly Freedom',
],
[
'name' => 'feature_description_1',
'value' => 'You have full control over VueFileManager, no third authorities will control your service or usage, only you.',
],
[
'name' => 'feature_title_2',
'value' => 'The Sky is the Limit',
],
[
'name' => 'feature_description_2',
'value' => 'VueFileManager is cloud storage software. You have to install and running application on your own server hosting.',
],
[
'name' => 'feature_title_3',
'value' => 'No Monthly Fees',
],
[
'name' => 'feature_description_3',
'value' => 'When you running VueFileManager on your own server hosting, anybody can\'t control your content or resell your user data. Your data is safe.',
],
[
'name' => 'pricing_title',
'value' => 'Pick the <span style="color: #41B883;">Best Plan</span> For Your Needs',
],
[
'name' => 'pricing_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'get_started_title',
'value' => 'Ready to Get <span style="color: #41B883">Started</span><br> With Us?',
],
[
'name' => 'get_started_description',
'value' => 'Your private cloud storage software build on Laravel & Vue.js. No limits & no monthly fees. Trully freedom.',
],
[
'name' => 'footer_content',
'value' => '© 2020 Simple & Powerfull Personal Cloud Storage. Developed by <a href="https://hi5ve.digital" target="_blank">Hi5Ve.Digital</a>',
],
],
// Define size of chunk uploaded by MB. E.g. integer 128 means chunk size will be 128MB.
'chunk_size' => '128',
];

2
public/js/main.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -238,6 +238,7 @@
flex: 0 0 100%;
@include transition(150ms);
position: relative;
scroll-behavior: smooth;
&.is-fileinfo-visible {
flex: 0 1 100%;

View File

@@ -2,24 +2,39 @@
<transition name="info-panel">
<div v-if="uploadingFilesCount" class="upload-progress">
<div class="progress-title">
<span>{{ $t('uploading.progress', {current:uploadingFilesCount.current, total: uploadingFilesCount.total}) }}</span>
<span v-if="isProcessingFile">
<refresh-cw-icon size="12" class="sync-alt"></refresh-cw-icon>
{{ $t('uploading.processing_file') }}
</span>
<span v-if="!isProcessingFile && uploadingFilesCount.total === 1">
{{ $t('uploading.progress_single_upload', {progress: uploadingFileProgress}) }}
</span>
<span v-if="!isProcessingFile && uploadingFilesCount.total > 1">
{{ $t('uploading.progress', {current:uploadingFilesCount.current, total: uploadingFilesCount.total, progress: uploadingFileProgress}) }}
</span>
</div>
<ProgressBar :progress="uploadingFileProgress"/>
<ProgressBar :progress="uploadingFileProgress" />
</div>
</transition>
</template>
<script>
import ProgressBar from '@/components/FilesView/ProgressBar'
import { RefreshCwIcon } from 'vue-feather-icons'
import {mapGetters} from 'vuex'
export default {
name: 'UploadProgress',
components: {
RefreshCwIcon,
ProgressBar,
},
computed: {
...mapGetters(['uploadingFileProgress', 'uploadingFilesCount'])
...mapGetters([
'uploadingFileProgress',
'uploadingFilesCount',
'isProcessingFile',
])
}
}
</script>
@@ -28,6 +43,24 @@
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.sync-alt {
animation: spin 1s linear infinite;
margin-right: 5px;
polyline, path {
stroke: $theme;
}
}
@keyframes spin {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.info-panel-enter-active,
.info-panel-leave-active {
transition: all 0.3s ease;

View File

@@ -52,6 +52,7 @@
border-radius: 6px;
width: 40px;
height: 40px;
object-fit: cover;
}
&.large {

View File

@@ -73,73 +73,101 @@ const Helpers = {
this.$store.dispatch('createFolder', folderName)
}
Vue.prototype.$uploadFiles = async function (files) {
// Prevent submit empty files
if (files && files.length == 0) return
Vue.prototype.$handleUploading = async function (files, parent_id) {
let fileCount = files ? files.length : 0
let fileCountSucceed = 1
let fileBuffer = []
// Append the file list to fileBuffer array
Array.prototype.push.apply(fileBuffer, files);
let fileSucceed = 0
// Update files count in progressbar
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileCountSucceed,
total: fileCount
current: fileSucceed,
total: files.length
})
// Reset upload progress to 0
store.commit('UPLOADING_FILE_PROGRESS', 0)
// Get parent id
const rootFolder = this.$store.getters.currentFolder
? this.$store.getters.currentFolder.unique_id
: 0
let parentFolder = this.$store.getters.currentFolder ? this.$store.getters.currentFolder.unique_id : 0
let rootFolder = parent_id ? parent_id : parentFolder
for (var i = files.length - 1; i >= 0; i--) {
let formData = new FormData()
// Upload files
do {
let file = fileBuffer.shift(),
chunks = []
// Append data
formData.append('file', files[i])
// Calculate ceils
let size = this.$store.getters.config.chunkSize,
chunksCeil = Math.ceil(file.size / size);
// Append form data
formData.append('parent_id', rootFolder)
// Create chunks
for (let i = 0; i < chunksCeil; i++) {
chunks.push(file.slice(
i * size, Math.min(i * size + size, file.size), file.type
));
}
// Upload data
await store.dispatch('uploadFiles', formData)
.then(() => {
// Progress file log
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileCountSucceed,
total: fileCount
// Set Data
let formData = new FormData(),
uploadedSize = 0,
isNotGeneralError = true,
filename = Array(16).fill(0).map(x => Math.random().toString(36).charAt(2)).join('') + '-' + file.name + '.part'
do {
let isLast = chunks.length === 1,
chunk = chunks.shift(),
attempts = 0
// Set form data
formData.set('file', chunk, filename);
formData.set('parent_id', rootFolder)
formData.set('is_last', isLast);
// Upload chunks
do {
await store.dispatch('uploadFiles', {
form: formData,
fileSize: file.size,
totalUploadedSize: uploadedSize
}).then(() => {
uploadedSize = uploadedSize + chunk.size
}).catch((error) => {
// Count attempts
attempts++
// Break uploading proccess
if (error.response.status === 500)
isNotGeneralError = false
// Show Error
if (attempts === 3)
this.$isSomethingWrong()
})
} while (isNotGeneralError && attempts !== 0 && attempts !== 3)
// Uploading finished
if (fileCount === fileCountSucceed) {
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
} else {
// Add uploaded file
fileCountSucceed++
}
}).catch(error => {
switch (error.response.status) {
case 423:
events.$emit('alert:open', {
emoji: '😬',
title: this.$t('popup_exceed_limit.title'),
message: this.$t('popup_exceed_limit.message')
})
break;
case 413:
events.$emit('alert:open', {
emoji: '😟',
title: this.$t('popup_paylod_error.title'),
message: this.$t('popup_paylod_error.message')
})
break;
default:
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
break;
}
})
}
} while (isNotGeneralError && chunks.length !== 0)
fileSucceed++
// Progress file log
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileSucceed,
total: files.length
})
} while (fileBuffer.length !== 0)
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
}
Vue.prototype.$uploadFiles = async function (files) {
this.$handleUploading(files, undefined)
}
Vue.prototype.$uploadExternalFiles = async function (event, parent_id) {
@@ -148,67 +176,9 @@ const Helpers = {
if (event.dataTransfer.items.length == 0) return
// Get files
const files = [...event.dataTransfer.items].map(item => item.getAsFile());
let files = [...event.dataTransfer.items].map(item => item.getAsFile());
let fileCountSucceed = 1
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileCountSucceed,
total: files.length
})
for (var i = files.length - 1; i >= 0; i--) {
let formData = new FormData()
// Append data
formData.append('file', files[i])
// Append form data
formData.append('parent_id', parent_id)
// Upload data
await store.dispatch('uploadFiles', formData).then(() => {
// Progress file log
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
current: fileCountSucceed,
total: files.length
})
// Progress file log
store.commit('INCREASE_FOLDER_ITEM', parent_id)
// Uploading finished
if (files.length === fileCountSucceed) {
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
} else {
// Add uploaded file
fileCountSucceed++
}
}).catch(error => {
switch (error.response.status) {
case 423:
events.$emit('alert:open', {
emoji: '😬',
title: this.$t('popup_exceed_limit.title'),
message: this.$t('popup_exceed_limit.message')
})
break;
case 413:
events.$emit('alert:open', {
emoji: '😟',
title: this.$t('popup_paylod_error.title'),
message: this.$t('popup_paylod_error.message')
})
break;
default:
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
break;
}
})
}
this.$handleUploading(files, parent_id)
}
Vue.prototype.$downloadFile = function (url, filename) {

View File

@@ -680,7 +680,9 @@
"href": "Please confirm your payment."
},
"uploading": {
"progress": "上传文件 {current}/{total}"
"processing_file": "Processing File...",
"progress_single_upload": "上传文件 {progress}%",
"progress": "上传文件 {progress}% - {current}/{total}"
},
"user_add_card": {
"default_description": "",

View File

@@ -680,7 +680,9 @@
"href": "Please confirm your payment."
},
"uploading": {
"progress": "Uploading files {current}/{total}"
"processing_file": "Processing File...",
"progress_single_upload": "Uploading File {progress}%",
"progress": "Uploading File {progress}% - {current}/{total}"
},
"user_add_card": {
"default_description": "Your card will be charged for billing plans as first.",

View File

@@ -680,7 +680,9 @@
"href": "Prosím potvrďte Vašu platbu."
},
"uploading": {
"progress": "Nahrávam súbory {current}/{total}"
"processing_file": "Spracuvávam súbor...",
"progress_single_upload": "Nahrávam súbor {progress}%",
"progress": "Nahrávam súbory {progress}% - {current}/{total}"
},
"user_add_card": {
"default_description": "Vaša karta bude uložená a použitá pre platbu ako prvá.",

View File

@@ -8,6 +8,7 @@ const defaultState = {
fileInfoDetail: undefined,
currentFolder: undefined,
uploadingFileProgress: 0,
isProcessingFile: false,
navigation: undefined,
isSearching: false,
browseHistory: [],
@@ -285,11 +286,15 @@ const mutations = {
STORE_CURRENT_FOLDER(state, folder) {
state.currentFolder = folder
},
PROCESSING_FILE(state, status) {
state.isProcessingFile = status
}
}
const getters = {
uploadingFileProgress: state => state.uploadingFileProgress,
uploadingFilesCount: state => state.uploadingFilesCount,
isProcessingFile: state => state.isProcessingFile,
fileInfoDetail: state => state.fileInfoDetail,
currentFolder: state => state.currentFolder,
browseHistory: state => state.browseHistory,

View File

@@ -42,6 +42,8 @@ const actions = {
.then(response => {
commit('ADD_NEW_FOLDER', response.data)
events.$emit('scrollTop')
if ( getters.currentFolder.location !== 'public' ) {
dispatch('getAppData')
}
@@ -72,7 +74,7 @@ const actions = {
})
.catch(() => isSomethingWrong())
},
uploadFiles: ({commit, getters}, files) => {
uploadFiles: ({commit, getters}, {form, fileSize, totalUploadedSize}) => {
return new Promise((resolve, reject) => {
// Get route
@@ -81,29 +83,58 @@ const actions = {
: '/api/upload'
axios
.post(route, files, {
.post(route, form, {
headers: {
'Content-Type': 'multipart/form-data'
'Content-Type': 'application/octet-stream'
},
onUploadProgress: progressEvent => {
var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
onUploadProgress: event => {
commit('UPLOADING_FILE_PROGRESS', percentCompleted)
var percentCompleted = Math.floor(((totalUploadedSize + event.loaded) / fileSize) * 100)
commit('UPLOADING_FILE_PROGRESS', percentCompleted >= 100 ? 100 : percentCompleted)
if (percentCompleted >= 100) {
commit('PROCESSING_FILE', true)
}
}
})
.then(response => {
commit('PROCESSING_FILE', false)
// Check if user is in uploading folder, if yes, than show new file
if (response.data.folder_id == getters.currentFolder.unique_id)
commit('ADD_NEW_ITEMS', response.data)
commit('UPLOADING_FILE_PROGRESS', 0)
resolve(response)
})
.catch(error => {
commit('PROCESSING_FILE', false)
reject(error)
switch (error.response.status) {
case 423:
events.$emit('alert:open', {
emoji: '😬',
title: i18n.t('popup_exceed_limit.title'),
message: i18n.t('popup_exceed_limit.message')
})
break;
case 413:
events.$emit('alert:open', {
emoji: '😟',
title: i18n.t('popup_paylod_error.title'),
message: i18n.t('popup_paylod_error.message')
})
break;
default:
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
break;
}
// Reset uploader
commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
})

View File

@@ -94,6 +94,10 @@
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['Purchase code is invalid.']
});
} else if (error.response.status == 404) {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['You may have misconfigured the app, please read the readme file and try it again.']
});
} else {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['Something is wrong. Please try again.']

View File

@@ -15,7 +15,7 @@
.error-message {
position: absolute;
left: 0;
bottom: -25px;
top: 50px;
}
}
}

View File

@@ -58,6 +58,8 @@
installation: '{{ $installation ?? '' }}',
latest_upgrade: '{{ isset($settings->latest_upgrade) && $settings->latest_upgrade ? $settings->latest_upgrade : null }}',
chunkSize: {{ format_bytes(config('vuefilemanager.chunk_size')) }},
}
</script>