Storage status is saved in the database. Failed storages are rechecked every 10 minutes, while working storages are rechecked every request. Using the files_external app will recheck all external storages when the settings page is viewed, or whenever an external storage is saved.remotes/origin/handlebars-approach
parent
89d6439445
commit
df19cabb44
@ -0,0 +1,462 @@ |
||||
<?php |
||||
/** |
||||
* @author Robin McCorkell <rmccorkell@karoshi.org.uk> |
||||
* |
||||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
namespace OC\Files\Storage\Wrapper; |
||||
|
||||
/** |
||||
* Availability checker for storages |
||||
* |
||||
* Throws a StorageNotAvailableException for storages with known failures |
||||
*/ |
||||
class Availability extends Wrapper { |
||||
const RECHECK_TTL_SEC = 600; // 10 minutes |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
private function updateAvailability() { |
||||
try { |
||||
$result = $this->test(); |
||||
} catch (\Exception $e) { |
||||
$result = false; |
||||
} |
||||
$this->setAvailability($result); |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
private function isAvailable() { |
||||
$availability = $this->getAvailability(); |
||||
if (!$availability['available']) { |
||||
// trigger a recheck if TTL reached |
||||
if ((time() - $availability['last_checked']) > self::RECHECK_TTL_SEC) { |
||||
return $this->updateAvailability(); |
||||
} |
||||
} |
||||
return $availability['available']; |
||||
} |
||||
|
||||
/** |
||||
* @throws \OCP\Files\StorageNotAvailableException |
||||
*/ |
||||
private function checkAvailability() { |
||||
if (!$this->isAvailable()) { |
||||
throw new \OCP\Files\StorageNotAvailableException(); |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function mkdir($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::mkdir($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function rmdir($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::rmdir($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function opendir($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::opendir($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function is_dir($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::is_dir($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function is_file($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::is_file($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function stat($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::stat($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function filetype($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::filetype($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function filesize($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::filesize($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function isCreatable($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::isCreatable($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function isReadable($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::isReadable($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function isUpdatable($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::isUpdatable($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function isDeletable($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::isDeletable($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function isSharable($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::isSharable($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getPermissions($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getPermissions($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function file_exists($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::file_exists($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function filemtime($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::filemtime($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function file_get_contents($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::file_get_contents($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function file_put_contents($path, $data) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::file_put_contents($path, $data); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function unlink($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::unlink($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function rename($path1, $path2) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::rename($path1, $path2); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function copy($path1, $path2) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::copy($path1, $path2); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function fopen($path, $mode) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::fopen($path, $mode); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getMimeType($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getMimeType($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function hash($type, $path, $raw = false) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::hash($type, $path, $raw); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function free_space($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::free_space($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function search($query) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::search($query); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function touch($path, $mtime = null) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::touch($path, $mtime); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getLocalFile($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getLocalFile($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getLocalFolder($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getLocalFolder($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function hasUpdated($path, $time) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::hasUpdated($path, $time); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getOwner($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getOwner($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getETag($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getETag($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getDirectDownload($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getDirectDownload($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
/** {@inheritdoc} */ |
||||
public function getMetaData($path) { |
||||
$this->checkAvailability(); |
||||
try { |
||||
return parent::getMetaData($path); |
||||
} catch (\OCP\Files\StorageNotAvailableException $e) { |
||||
$this->setAvailability(false); |
||||
throw $e; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,149 @@ |
||||
<?php |
||||
/** |
||||
* @author Robin McCorkell <rmccorkell@karoshi.org.uk> |
||||
* |
||||
* @copyright Copyright (c) 2015, ownCloud, Inc. |
||||
* @license AGPL-3.0 |
||||
* |
||||
* This code is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License, version 3, |
||||
* as published by the Free Software Foundation. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License, version 3, |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
||||
* |
||||
*/ |
||||
namespace Test\Files\Storage\Wrapper; |
||||
|
||||
class Availability extends \Test\TestCase { |
||||
protected function getWrapperInstance() { |
||||
$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') |
||||
->disableOriginalConstructor() |
||||
->getMock(); |
||||
$wrapper = new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); |
||||
return [$storage, $wrapper]; |
||||
} |
||||
|
||||
/** |
||||
* Storage is available |
||||
*/ |
||||
public function testAvailable() { |
||||
list($storage, $wrapper) = $this->getWrapperInstance(); |
||||
$storage->expects($this->once()) |
||||
->method('getAvailability') |
||||
->willReturn(['available' => true, 'last_checked' => 0]); |
||||
$storage->expects($this->never()) |
||||
->method('test'); |
||||
$storage->expects($this->once()) |
||||
->method('mkdir'); |
||||
|
||||
$wrapper->mkdir('foobar'); |
||||
} |
||||
|
||||
/** |
||||
* Storage marked unavailable, TTL not expired |
||||
* |
||||
* @expectedException \OCP\Files\StorageNotAvailableException |
||||
*/ |
||||
public function testUnavailable() { |
||||
list($storage, $wrapper) = $this->getWrapperInstance(); |
||||
$storage->expects($this->once()) |
||||
->method('getAvailability') |
||||
->willReturn(['available' => false, 'last_checked' => time()]); |
||||
$storage->expects($this->never()) |
||||
->method('test'); |
||||
$storage->expects($this->never()) |
||||
->method('mkdir'); |
||||
|
||||
$wrapper->mkdir('foobar'); |
||||
} |
||||
|
||||
/** |
||||
* Storage marked unavailable, TTL expired |
||||
*/ |
||||
public function testUnavailableRecheck() { |
||||
list($storage, $wrapper) = $this->getWrapperInstance(); |
||||
$storage->expects($this->once()) |
||||
->method('getAvailability') |
||||
->willReturn(['available' => false, 'last_checked' => 0]); |
||||
$storage->expects($this->once()) |
||||
->method('test') |
||||
->willReturn(true); |
||||
$storage->expects($this->once()) |
||||
->method('setAvailability') |
||||
->with($this->equalTo(true)); |
||||
$storage->expects($this->once()) |
||||
->method('mkdir'); |
||||
|
||||
$wrapper->mkdir('foobar'); |
||||
} |
||||
|
||||
/** |
||||
* Storage marked available, but throws StorageNotAvailableException |
||||
* |
||||
* @expectedException \OCP\Files\StorageNotAvailableException |
||||
*/ |
||||
public function testAvailableThrowStorageNotAvailable() { |
||||
list($storage, $wrapper) = $this->getWrapperInstance(); |
||||
$storage->expects($this->once()) |
||||
->method('getAvailability') |
||||
->willReturn(['available' => true, 'last_checked' => 0]); |
||||
$storage->expects($this->never()) |
||||
->method('test'); |
||||
$storage->expects($this->once()) |
||||
->method('mkdir') |
||||
->will($this->throwException(new \OCP\Files\StorageNotAvailableException())); |
||||
$storage->expects($this->once()) |
||||
->method('setAvailability') |
||||
->with($this->equalTo(false)); |
||||
|
||||
$wrapper->mkdir('foobar'); |
||||
} |
||||
|
||||
/** |
||||
* Storage available, but call fails |
||||
* Method failure does not indicate storage unavailability |
||||
*/ |
||||
public function testAvailableFailure() { |
||||
list($storage, $wrapper) = $this->getWrapperInstance(); |
||||
$storage->expects($this->once()) |
||||
->method('getAvailability') |
||||
->willReturn(['available' => true, 'last_checked' => 0]); |
||||
$storage->expects($this->never()) |
||||
->method('test'); |
||||
$storage->expects($this->once()) |
||||
->method('mkdir') |
||||
->willReturn(false); |
||||
$storage->expects($this->never()) |
||||
->method('setAvailability'); |
||||
|
||||
$wrapper->mkdir('foobar'); |
||||
} |
||||
|
||||
/** |
||||
* Storage available, but throws exception |
||||
* Standard exception does not indicate storage unavailability |
||||
* |
||||
* @expectedException \Exception |
||||
*/ |
||||
public function testAvailableThrow() { |
||||
list($storage, $wrapper) = $this->getWrapperInstance(); |
||||
$storage->expects($this->once()) |
||||
->method('getAvailability') |
||||
->willReturn(['available' => true, 'last_checked' => 0]); |
||||
$storage->expects($this->never()) |
||||
->method('test'); |
||||
$storage->expects($this->once()) |
||||
->method('mkdir') |
||||
->will($this->throwException(new \Exception())); |
||||
$storage->expects($this->never()) |
||||
->method('setAvailability'); |
||||
|
||||
$wrapper->mkdir('foobar'); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue