diff --git a/app/Http/Controllers/UpdateController.php b/app/Http/Controllers/UpdateController.php index 8f5d1abc..5a09f474 100644 --- a/app/Http/Controllers/UpdateController.php +++ b/app/Http/Controllers/UpdateController.php @@ -5,8 +5,10 @@ namespace App\Http\Controllers; use Arr; use Log; use File; +use Cache; use Option; use Storage; +use Exception; use ZipArchive; use App\Services\OptionForm; use Illuminate\Http\Request; @@ -78,7 +80,7 @@ class UpdateController extends Controller })->handle()->always(function($form) { try { $response = file_get_contents(option('update_source')); - } catch (\Exception $e) { + } catch (Exception $e) { $form->addMessage(trans('admin.update.errors.connection').$e->getMessage(), 'danger'); } }); @@ -107,17 +109,23 @@ class UpdateController extends Controller { $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']; - $file_size = Utils::getRemoteFileSize($release_url); - $tmp_path = session('tmp_path'); + $tmp_path = Cache::get('tmp_path'); + + $client = new \GuzzleHttp\Client(); + $guzzle_config = [ + 'headers' => ['User-Agent' => config('secure.user_agent')], + 'verify' => config('secure.certificates') + ]; switch ($action) { case 'prepare-download': + Cache::forget('download-progress'); $update_cache = storage_path('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]); - - return json(compact('release_url', 'tmp_path', 'file_size')); + // We won't get remote file size here since HTTP HEAD method is not always reliable + return json(compact('release_url', 'tmp_path')); case 'start-download': - if (! session()->has('tmp_path')) { - return "No temp path is set."; + if (! $tmp_path) { + 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 { - Utils::download($release_url, $tmp_path); - - } catch (\Exception $e) { - File::delete($tmp_path); - + $client->request('GET', $release_url, array_merge($guzzle_config, [ + 'sink' => $tmp_path, + 'progress' => function ($total, $downloaded) { + 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()); } + Log::info('[Update Wizard] Finished downloading update package'); + return json(compact('tmp_path')); - case 'get-file-size': + case 'get-progress': - if (! session()->has('tmp_path')) { - return "No temp path is set."; - } - - if (file_exists($tmp_path)) { - return json(['size' => filesize($tmp_path)]); - } + return json((array) Cache::get('download-progress')); case 'extract': @@ -171,7 +191,7 @@ class UpdateController extends Controller $res = $zip->open($tmp_path); if ($res === true) { - Log::info("[ZipArchive] Extracting file $tmp_path"); + Log::info("[Update Wizard] Extracting file $tmp_path"); if ($zip->extractTo($extract_dir) === false) { return response(trans('admin.update.errors.prefix').'Cannot unzip file.'); @@ -184,29 +204,31 @@ class UpdateController extends Controller try { File::copyDirectory("$extract_dir/vendor", base_path('vendor')); - } catch (\Exception $e) { - Log::error('[Extracter] Unable to extract vendors', [$e]); + } catch (Exception $e) { + report($e); + Log::error('[Update Wizard] Unable to extract vendors'); // Skip copying vendor File::deleteDirectory("$extract_dir/vendor"); } - try { File::copyDirectory($extract_dir, base_path()); - Log::info("[Extracter] Covering files"); + Log::info('[Update Wizard] Overwrite with extracted files'); - } catch (\Exception $e) { - Log::error("[Extracter] Error occured when covering files", [$e]); + } catch (Exception $e) { + report($e); + Log::error('[Update Wizard] Error occured when overwriting files'); // Response can be returned, while cache will be cleared // @see https://gist.github.com/g-plane/2f88ad582826a78e0a26c33f4319c1e0 return response(trans('admin.update.errors.overwrite').$e->getMessage()); } finally { 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); default: @@ -224,7 +246,7 @@ class UpdateController extends Controller try { $response = file_get_contents($url); - } catch (\Exception $e) { + } catch (Exception $e) { Log::error("[CheckingUpdate] Failed to get update information: ".$e->getMessage()); } diff --git a/resources/views/admin/update.tpl b/resources/views/admin/update.tpl index 14deda48..fe3ec1b5 100644 --- a/resources/views/admin/update.tpl +++ b/resources/views/admin/update.tpl @@ -122,7 +122,7 @@

@lang('admin.update.download.size')0 Bytes

- 0% + 0%
diff --git a/tests/BrowserKitTestCase.php b/tests/BrowserKitTestCase.php index 4f329790..d2caa3d7 100644 --- a/tests/BrowserKitTestCase.php +++ b/tests/BrowserKitTestCase.php @@ -4,10 +4,13 @@ namespace Tests; use DB; use Artisan; +use Tests\Concerns\InteractsWithCache; use Laravel\BrowserKitTesting\TestCase; class BrowserKitTestCase extends TestCase { + use InteractsWithCache; + /** * The base URL to use while testing the application. * diff --git a/tests/UpdateControllerTest.php b/tests/UpdateControllerTest.php index 2c33648c..f363d4b0 100644 --- a/tests/UpdateControllerTest.php +++ b/tests/UpdateControllerTest.php @@ -107,7 +107,6 @@ class UpdateControllerTest extends BrowserKitTestCase public function testDownload() { - $this->markTestSkipped('Removed Utils class'); option(['update_source' => vfs\vfsStream::url('root/update.json')]); $this->generateFakeUpdateFile(); @@ -129,47 +128,51 @@ class UpdateControllerTest extends BrowserKitTestCase $this->get('/admin/update/download?action=prepare-download') ->seeJson([ 'release_url' => storage_path('testing/update.zip'), - 'file_size' => filesize(storage_path('testing/update.zip')) ]) - ->assertSessionHas('tmp_path'); + ->assertCacheHas('tmp_path'); // Start downloading - $this->flushSession(); + $this->flushCache(); $this->actAs('admin') ->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')); - $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') ->see(trans('admin.update.errors.prefix')); $this->generateFakeUpdateFile(); - $this->get('/admin/update/download?action=start-download') - ->seeJson([ - 'tmp_path' => storage_path('update_cache/update.zip') - ]); - $this->assertFileExists(storage_path('update_cache/update.zip')); + // TODO: This needs to be tested. + // TODO: I failed to find a good solution for testing guzzle http requests. + // + // $this->withCache(['tmp_path' => 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 - $this->flushSession(); + $this->flushCache(); $this->actAs('admin') - ->get('/admin/update/download?action=get-file-size') - ->see('No temp path is set.'); + ->get('/admin/update/download?action=get-progress') + ->see('[]'); - $this->withSession(['tmp_path' => storage_path('update_cache/update.zip')]) - ->get('/admin/update/download?action=get-file-size') + $this->withCache(['download-progress' => ['total' => 514, 'downloaded' => 114]]) + ->get('/admin/update/download?action=get-progress') ->seeJson([ - 'size' => filesize(storage_path('testing/update.zip')) + 'total' => 514, + 'downloaded' => 114 ]); // 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') ->see('No file available'); 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') ->see(trans('admin.update.errors.unzip'));