refactor: create zips in-memory instead of on filesystem

Co-Authored-By: Roardom <78790963+Roardom@users.noreply.github.com>
This commit is contained in:
HDVinnie
2025-06-19 12:18:54 -04:00
parent c4e409bd74
commit ca504efaa1
5 changed files with 133 additions and 83 deletions

View File

@@ -20,7 +20,7 @@ use App\Helpers\Bencode;
use App\Models\Playlist;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use ZipArchive;
use ZipStream\ZipStream;
/**
* @see \Tests\Todo\Feature\Http\Controllers\PlaylistControllerTest
@@ -30,7 +30,7 @@ class PlaylistZipController extends Controller
/**
* Download All Playlist Torrents.
*/
public function show(Playlist $playlist): \Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
public function show(Playlist $playlist): \Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\StreamedResponse
{
// Extend The Maximum Execution Time
set_time_limit(300);
@@ -41,50 +41,40 @@ class PlaylistZipController extends Controller
// Authorized User
$user = auth()->user();
// Define Dir Folder
$path = Storage::disk('temporary-zips')->path('');
// Check Directory exists
if (!File::isDirectory($path)) {
File::makeDirectory($path, 0755, true, true);
}
// Zip File Name
$zipFileName = '['.$user->username.']'.$playlist->name.'.zip';
// Create ZipArchive Obj
$zipArchive = new ZipArchive();
return response()->streamDownload(
function () use ($zipFileName, $user, $playlist): void {
$zip = new ZipStream(outputName: sanitize_filename($zipFileName));
if ($zipArchive->open($path.$zipFileName, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
$announceUrl = route('announce', ['passkey' => $user->passkey]);
$announceUrl = route('announce', ['passkey' => $user->passkey]);
foreach ($playlist->torrents()->get() as $torrent) {
$dict = Bencode::bdecode(Storage::disk('torrent-files')->get($torrent->file_name));
foreach ($playlist->torrents()->get() as $torrent) {
if (Storage::disk('torrent-files')->exists($torrent->file_name)) {
$dict = Bencode::bdecode(Storage::disk('torrent-files')->get($torrent->file_name));
// Set the announce key and add the user passkey
$dict['announce'] = $announceUrl;
// Set the announce key and add the user passkey
$dict['announce'] = $announceUrl;
// Set link to torrent as the comment
if (config('torrent.comment')) {
$dict['comment'] = config('torrent.comment').'. '.route('torrents.show', ['id' => $torrent->id]);
} else {
$dict['comment'] = route('torrents.show', ['id' => $torrent->id]);
// Set link to torrent as the comment
if (config('torrent.comment')) {
$dict['comment'] = config('torrent.comment').'. '.route('torrents.show', ['id' => $torrent->id]);
} else {
$dict['comment'] = route('torrents.show', ['id' => $torrent->id]);
}
$fileToDownload = Bencode::bencode($dict);
$filename = sanitize_filename('['.config('torrent.source').']'.$torrent->name.'.torrent');
$zip->addFile($filename, $fileToDownload);
}
}
$fileToDownload = Bencode::bencode($dict);
$filename = str_replace([' ', '/', '\\'], ['.', '-', '-'], '['.config('torrent.source').']'.$torrent->name.'.torrent');
$zipArchive->addFromString($filename, $fileToDownload);
}
$zipArchive->close();
}
if (Storage::disk('temporary-zips')->exists($zipFileName)) {
return response()->download(Storage::disk('temporary-zips')->path($zipFileName))->deleteFileAfterSend(true);
}
return redirect()->back()->withErrors(trans('common.something-went-wrong'));
$zip->finish();
},
sanitize_filename($zipFileName),
);
}
}

View File

@@ -23,14 +23,14 @@ use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use ZipArchive;
use ZipStream\ZipStream;
class TorrentZipController extends Controller
{
/**
* Show zip file containing all torrents user has history of.
*/
public function show(Request $request, User $user): \Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse
public function show(Request $request, User $user): \Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\StreamedResponse
{
// Extend The Maximum Execution Time
set_time_limit(1200);
@@ -59,52 +59,38 @@ class TorrentZipController extends Controller
return redirect()->back()->withErrors('No Torrents Found');
}
// Define Dir For Zip
$zipPath = Storage::disk('temporary-zips')->path('');
return response()->streamDownload(
function () use ($zipFileName, $user, $torrents): void {
$zip = new ZipStream(outputName: sanitize_filename($zipFileName));
// Check Directory exists
if (!File::isDirectory($zipPath)) {
File::makeDirectory($zipPath, 0755, true, true);
}
$announceUrl = route('announce', ['passkey' => $user->passkey]);
// Create ZipArchive Obj
$zipArchive = new ZipArchive();
foreach ($torrents as $torrent) {
if (Storage::disk('torrent-files')->exists($torrent->file_name)) {
$dict = Bencode::bdecode(Storage::disk('torrent-files')->get($torrent->file_name));
if ($zipArchive->open($zipPath.$zipFileName, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
$announceUrl = route('announce', ['passkey' => $user->passkey]);
// Set the announce key and add the user passkey
$dict['announce'] = $announceUrl;
foreach ($torrents as $torrent) {
if (Storage::disk('torrent-files')->exists($torrent->file_name)) {
$dict = Bencode::bdecode(Storage::disk('torrent-files')->get($torrent->file_name));
// Set link to torrent as the comment
if (config('torrent.comment')) {
$dict['comment'] = config('torrent.comment').'. '.route('torrents.show', ['id' => $torrent->id]);
} else {
$dict['comment'] = route('torrents.show', ['id' => $torrent->id]);
}
// Set the announce key and add the user passkey
$dict['announce'] = $announceUrl;
$fileToDownload = Bencode::bencode($dict);
// Set link to torrent as the comment
if (config('torrent.comment')) {
$dict['comment'] = config('torrent.comment').'. '.route('torrents.show', ['id' => $torrent->id]);
} else {
$dict['comment'] = route('torrents.show', ['id' => $torrent->id]);
$filename = sanitize_filename('['.config('torrent.source').']'.$torrent->name.'.torrent');
$zip->addFile($filename, $fileToDownload);
}
$fileToDownload = Bencode::bencode($dict);
$filename = str_replace(
[' ', '/', '\\'],
['.', '-', '-'],
'['.config('torrent.source').']'.$torrent->name.'.torrent'
);
$zipArchive->addFromString($filename, $fileToDownload);
}
}
$zipArchive->close();
}
if (Storage::disk('temporary-zips')->exists($zipFileName)) {
return response()->download(Storage::disk('temporary-zips')->path($zipFileName))->deleteFileAfterSend(true);
}
$zip->finish();
},
sanitize_filename($zipFileName),
);
return redirect()->back()->withErrors(trans('common.something-went-wrong'));
}

View File

@@ -30,6 +30,7 @@
"laravel/scout": "^10.15.0",
"laravel/tinker": "^2.10.1",
"livewire/livewire": "^3.6.3",
"maennchen/zipstream-php": "^3.1",
"marcreichel/igdb-laravel": "^5.3.1",
"meilisearch/meilisearch-php": "^1.15.0",
"paragonie/constant_time_encoding": "^2.7.0",

80
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1f9f75b35bc72983c2376de766559b45",
"content-hash": "fbf86c3569b76d46d0eded20d7b15a9f",
"packages": [
{
"name": "assada/laravel-achievements",
@@ -3419,6 +3419,84 @@
],
"time": "2025-04-12T22:26:52+00:00"
},
{
"name": "maennchen/zipstream-php",
"version": "3.1.2",
"source": {
"type": "git",
"url": "https://github.com/maennchen/ZipStream-PHP.git",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-zlib": "*",
"php-64bit": "^8.2"
},
"require-dev": {
"brianium/paratest": "^7.7",
"ext-zip": "*",
"friendsofphp/php-cs-fixer": "^3.16",
"guzzlehttp/guzzle": "^7.5",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"phpunit/phpunit": "^11.0",
"vimeo/psalm": "^6.0"
},
"suggest": {
"guzzlehttp/psr7": "^2.4",
"psr/http-message": "^2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"ZipStream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Duncan",
"email": "pabs@pablotron.org"
},
{
"name": "Jonatan Männchen",
"email": "jonatan@maennchen.ch"
},
{
"name": "Jesse Donat",
"email": "donatj@gmail.com"
},
{
"name": "András Kolesár",
"email": "kolesar@kolesar.hu"
}
],
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
"keywords": [
"stream",
"zip"
],
"support": {
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
},
"funding": [
{
"url": "https://github.com/maennchen",
"type": "github"
}
],
"time": "2025-01-27T12:07:53+00:00"
},
{
"name": "marcreichel/igdb-laravel",
"version": "5.3.1",

View File

@@ -127,11 +127,6 @@ return [
'root' => storage_path('app/files/subtitles/files'),
],
'temporary-zips' => [
'driver' => 'local',
'root' => storage_path('app/tmp/zips'),
],
'temporary-nfos' => [
'driver' => 'local',
'root' => storage_path('app/tmp/nfos'),