Use guzzle to download update packages

This commit is contained in:
Pig Fang 2018-08-17 23:24:08 +08:00
parent 0cedd45cd7
commit ad32b718eb
4 changed files with 82 additions and 54 deletions

View File

@ -5,8 +5,10 @@ namespace App\Http\Controllers;
use Arr; use Arr;
use Log; use Log;
use File; use File;
use Cache;
use Option; use Option;
use Storage; use Storage;
use Exception;
use ZipArchive; use ZipArchive;
use App\Services\OptionForm; use App\Services\OptionForm;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -78,7 +80,7 @@ class UpdateController extends Controller
})->handle()->always(function($form) { })->handle()->always(function($form) {
try { try {
$response = file_get_contents(option('update_source')); $response = file_get_contents(option('update_source'));
} catch (\Exception $e) { } catch (Exception $e) {
$form->addMessage(trans('admin.update.errors.connection').$e->getMessage(), 'danger'); $form->addMessage(trans('admin.update.errors.connection').$e->getMessage(), 'danger');
} }
}); });
@ -107,17 +109,23 @@ class UpdateController extends Controller
{ {
$this->refreshInfo(); $this->refreshInfo();
$action = $request->input('action'); if (! $this->newVersionAvailable())
return;
if (! $this->newVersionAvailable()) return '';
$action = $request->get('action');
$release_url = $this->getReleaseInfo($this->latestVersion)['release_url']; $release_url = $this->getReleaseInfo($this->latestVersion)['release_url'];
$file_size = Utils::getRemoteFileSize($release_url); $tmp_path = Cache::get('tmp_path');
$tmp_path = session('tmp_path');
$client = new \GuzzleHttp\Client();
$guzzle_config = [
'headers' => ['User-Agent' => config('secure.user_agent')],
'verify' => config('secure.certificates')
];
switch ($action) { switch ($action) {
case 'prepare-download': case 'prepare-download':
Cache::forget('download-progress');
$update_cache = storage_path('update_cache'); $update_cache = storage_path('update_cache');
if (! is_dir($update_cache)) { if (! is_dir($update_cache)) {
@ -126,38 +134,50 @@ class UpdateController extends Controller
} }
} }
$tmp_path = $update_cache."/update_".time().".zip"; // Set temporary path for the update package
$tmp_path = $update_cache.'/update_'.time().'.zip';
Cache::put('tmp_path', $tmp_path, 60);
Log::info('[Update Wizard] Prepare to download update package', compact('release_url', 'tmp_path'));
session(['tmp_path' => $tmp_path]); // We won't get remote file size here since HTTP HEAD method is not always reliable
return json(compact('release_url', 'tmp_path'));
return json(compact('release_url', 'tmp_path', 'file_size'));
case 'start-download': case 'start-download':
if (! session()->has('tmp_path')) { if (! $tmp_path) {
return "No temp path is set."; return 'No temp path available, please try again.';
} }
@set_time_limit(0);
$GLOBALS['last_downloaded'] = 0;
Log::info('[Update Wizard] Start downloading update package');
try { try {
Utils::download($release_url, $tmp_path); $client->request('GET', $release_url, array_merge($guzzle_config, [
'sink' => $tmp_path,
} catch (\Exception $e) { 'progress' => function ($total, $downloaded) {
File::delete($tmp_path); if ($total == 0) return;
// Log current progress per 100 KiB
if ($total == $downloaded || floor($downloaded / 102400) > floor($GLOBALS['last_downloaded'] / 102400)) {
$GLOBALS['last_downloaded'] = $downloaded;
Log::info('[Update Wizard] Download progress (in bytes):', [$total, $downloaded]);
Cache::put('download-progress', compact('total', 'downloaded'), 60);
}
}
]));
} catch (Exception $e) {
@unlink($tmp_path);
return response(trans('admin.update.errors.prefix').$e->getMessage()); return response(trans('admin.update.errors.prefix').$e->getMessage());
} }
Log::info('[Update Wizard] Finished downloading update package');
return json(compact('tmp_path')); return json(compact('tmp_path'));
case 'get-file-size': case 'get-progress':
if (! session()->has('tmp_path')) { return json((array) Cache::get('download-progress'));
return "No temp path is set.";
}
if (file_exists($tmp_path)) {
return json(['size' => filesize($tmp_path)]);
}
case 'extract': case 'extract':
@ -171,7 +191,7 @@ class UpdateController extends Controller
$res = $zip->open($tmp_path); $res = $zip->open($tmp_path);
if ($res === true) { if ($res === true) {
Log::info("[ZipArchive] Extracting file $tmp_path"); Log::info("[Update Wizard] Extracting file $tmp_path");
if ($zip->extractTo($extract_dir) === false) { if ($zip->extractTo($extract_dir) === false) {
return response(trans('admin.update.errors.prefix').'Cannot unzip file.'); return response(trans('admin.update.errors.prefix').'Cannot unzip file.');
@ -184,29 +204,31 @@ class UpdateController extends Controller
try { try {
File::copyDirectory("$extract_dir/vendor", base_path('vendor')); File::copyDirectory("$extract_dir/vendor", base_path('vendor'));
} catch (\Exception $e) { } catch (Exception $e) {
Log::error('[Extracter] Unable to extract vendors', [$e]); report($e);
Log::error('[Update Wizard] Unable to extract vendors');
// Skip copying vendor // Skip copying vendor
File::deleteDirectory("$extract_dir/vendor"); File::deleteDirectory("$extract_dir/vendor");
} }
try { try {
File::copyDirectory($extract_dir, base_path()); File::copyDirectory($extract_dir, base_path());
Log::info("[Extracter] Covering files"); Log::info('[Update Wizard] Overwrite with extracted files');
} catch (\Exception $e) { } catch (Exception $e) {
Log::error("[Extracter] Error occured when covering files", [$e]); report($e);
Log::error('[Update Wizard] Error occured when overwriting files');
// Response can be returned, while cache will be cleared // Response can be returned, while cache will be cleared
// @see https://gist.github.com/g-plane/2f88ad582826a78e0a26c33f4319c1e0 // @see https://gist.github.com/g-plane/2f88ad582826a78e0a26c33f4319c1e0
return response(trans('admin.update.errors.overwrite').$e->getMessage()); return response(trans('admin.update.errors.overwrite').$e->getMessage());
} finally { } finally {
File::deleteDirectory(storage_path('update_cache')); File::deleteDirectory(storage_path('update_cache'));
Log::info("[Extracter] Cleaning cache"); Log::info('[Update Wizard] Cleaning cache');
} }
Log::info('[Update Wizard] Done');
return json(trans('admin.update.complete'), 0); return json(trans('admin.update.complete'), 0);
default: default:
@ -224,7 +246,7 @@ class UpdateController extends Controller
try { try {
$response = file_get_contents($url); $response = file_get_contents($url);
} catch (\Exception $e) { } catch (Exception $e) {
Log::error("[CheckingUpdate] Failed to get update information: ".$e->getMessage()); Log::error("[CheckingUpdate] Failed to get update information: ".$e->getMessage());
} }

View File

@ -122,7 +122,7 @@
<p>@lang('admin.update.download.size')<span id="file-size">0</span> Bytes</p> <p>@lang('admin.update.download.size')<span id="file-size">0</span> Bytes</p>
<div class="progress"> <div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"> <div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
<span id="imported-progress">0</span>% <span id="download-progress">0</span>%
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,10 +4,13 @@ namespace Tests;
use DB; use DB;
use Artisan; use Artisan;
use Tests\Concerns\InteractsWithCache;
use Laravel\BrowserKitTesting\TestCase; use Laravel\BrowserKitTesting\TestCase;
class BrowserKitTestCase extends TestCase class BrowserKitTestCase extends TestCase
{ {
use InteractsWithCache;
/** /**
* The base URL to use while testing the application. * The base URL to use while testing the application.
* *

View File

@ -107,7 +107,6 @@ class UpdateControllerTest extends BrowserKitTestCase
public function testDownload() public function testDownload()
{ {
$this->markTestSkipped('Removed Utils class');
option(['update_source' => vfs\vfsStream::url('root/update.json')]); option(['update_source' => vfs\vfsStream::url('root/update.json')]);
$this->generateFakeUpdateFile(); $this->generateFakeUpdateFile();
@ -129,47 +128,51 @@ class UpdateControllerTest extends BrowserKitTestCase
$this->get('/admin/update/download?action=prepare-download') $this->get('/admin/update/download?action=prepare-download')
->seeJson([ ->seeJson([
'release_url' => storage_path('testing/update.zip'), 'release_url' => storage_path('testing/update.zip'),
'file_size' => filesize(storage_path('testing/update.zip'))
]) ])
->assertSessionHas('tmp_path'); ->assertCacheHas('tmp_path');
// Start downloading // Start downloading
$this->flushSession(); $this->flushCache();
$this->actAs('admin') $this->actAs('admin')
->get('/admin/update/download?action=start-download') ->get('/admin/update/download?action=start-download')
->see('No temp path is set.'); ->see('No temp path available, please try again.');
unlink(storage_path('testing/update.zip')); unlink(storage_path('testing/update.zip'));
$this->withSession(['tmp_path' => storage_path('update_cache/update.zip')]) $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')])
->get('/admin/update/download?action=start-download') ->get('/admin/update/download?action=start-download')
->see(trans('admin.update.errors.prefix')); ->see(trans('admin.update.errors.prefix'));
$this->generateFakeUpdateFile(); $this->generateFakeUpdateFile();
$this->get('/admin/update/download?action=start-download') // TODO: This needs to be tested.
->seeJson([ // TODO: I failed to find a good solution for testing guzzle http requests.
'tmp_path' => storage_path('update_cache/update.zip') //
]); // $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')])
$this->assertFileExists(storage_path('update_cache/update.zip')); // ->get('/admin/update/download?action=start-download')
// ->seeJson([
// 'tmp_path' => storage_path('update_cache/update.zip')
// ]);
// $this->assertFileExists(storage_path('update_cache/update.zip'));
// Get file size // Get file size
$this->flushSession(); $this->flushCache();
$this->actAs('admin') $this->actAs('admin')
->get('/admin/update/download?action=get-file-size') ->get('/admin/update/download?action=get-progress')
->see('No temp path is set.'); ->see('[]');
$this->withSession(['tmp_path' => storage_path('update_cache/update.zip')]) $this->withCache(['download-progress' => ['total' => 514, 'downloaded' => 114]])
->get('/admin/update/download?action=get-file-size') ->get('/admin/update/download?action=get-progress')
->seeJson([ ->seeJson([
'size' => filesize(storage_path('testing/update.zip')) 'total' => 514,
'downloaded' => 114
]); ]);
// Extract // Extract
$this->withSession(['tmp_path' => storage_path('update_cache/update')]) $this->withCache(['tmp_path' => storage_path('update_cache/update')])
->get('/admin/update/download?action=extract') ->get('/admin/update/download?action=extract')
->see('No file available'); ->see('No file available');
file_put_contents(storage_path('update_cache/update.zip'), 'text'); file_put_contents(storage_path('update_cache/update.zip'), 'text');
$this->withSession(['tmp_path' => storage_path('update_cache/update.zip')]) $this->withCache(['tmp_path' => storage_path('update_cache/update.zip')])
->get('/admin/update/download?action=extract') ->get('/admin/update/download?action=extract')
->see(trans('admin.update.errors.unzip')); ->see(trans('admin.update.errors.unzip'));