Merge pull request #16237 from owncloud/file-locking
High level file locking
This commit is contained in:
commit
2f8296875f
|
@ -31,6 +31,9 @@ namespace OC\Files\Storage;
|
|||
|
||||
use OC\Files\Filesystem;
|
||||
use OCA\Files_Sharing\ISharedStorage;
|
||||
use OCA\Files_Sharing\Propagator;
|
||||
use OCA\Files_Sharing\SharedMount;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
* Convert target path to source path and pass the function call to the correct storage provider
|
||||
|
@ -608,4 +611,38 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
|
|||
list($targetStorage, $targetInternalPath) = $this->resolvePath($targetInternalPath);
|
||||
return $targetStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type, ILockingProvider $provider) {
|
||||
/** @var \OCP\Files\Storage $targetStorage */
|
||||
list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
|
||||
$targetStorage->acquireLock($targetInternalPath, $type, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function releaseLock($path, $type, ILockingProvider $provider) {
|
||||
/** @var \OCP\Files\Storage $targetStorage */
|
||||
list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
|
||||
$targetStorage->releaseLock($targetInternalPath, $type, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function changeLock($path, $type, ILockingProvider $provider) {
|
||||
/** @var \OCP\Files\Storage $targetStorage */
|
||||
list($targetStorage, $targetInternalPath) = $this->resolvePath($path);
|
||||
$targetStorage->changeLock($targetInternalPath, $type, $provider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1026,6 +1026,25 @@ $CONFIG = array(
|
|||
*/
|
||||
'max_filesize_animated_gifs_public_sharing' => 10,
|
||||
|
||||
|
||||
/**
|
||||
* Enables the EXPERIMENTAL file locking.
|
||||
* This is disabled by default as it is experimental.
|
||||
*
|
||||
* Prevents concurrent processes to access the same files
|
||||
* at the same time. Can help prevent side effects that would
|
||||
* be caused by concurrent operations.
|
||||
*
|
||||
* WARNING: EXPERIMENTAL
|
||||
*/
|
||||
'filelocking.enabled' => false,
|
||||
|
||||
/**
|
||||
* Memory caching backend for file locking
|
||||
* Because most memcache backends can clean values without warning using redis is recommended
|
||||
*/
|
||||
'memcache.locking' => '\OC\Memcache\Redis',
|
||||
|
||||
/**
|
||||
* This entry is just here to show a warning in case somebody copied the sample
|
||||
* configuration. DO NOT ADD THIS SWITCH TO YOUR CONFIGURATION!
|
||||
|
|
|
@ -666,6 +666,8 @@ class OC {
|
|||
//make sure temporary files are cleaned up
|
||||
$tmpManager = \OC::$server->getTempManager();
|
||||
register_shutdown_function(array($tmpManager, 'clean'));
|
||||
$lockProvider = \OC::$server->getLockingProvider();
|
||||
register_shutdown_function(array($lockProvider, 'releaseAll'));
|
||||
|
||||
if ($systemConfig->getValue('installed', false) && !self::checkUpgrade(false)) {
|
||||
if (\OC::$server->getConfig()->getAppValue('core', 'backgroundjobs_mode', 'ajax') == 'ajax') {
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
namespace OC\Connector\Sabre;
|
||||
|
||||
use OC\Connector\Sabre\Exception\InvalidPath;
|
||||
use OC\Connector\Sabre\Exception\FileLocked;
|
||||
use OCP\Lock\LockedException;
|
||||
|
||||
class Directory extends \OC\Connector\Sabre\Node
|
||||
implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota {
|
||||
|
@ -102,6 +104,8 @@ class Directory extends \OC\Connector\Sabre\Node
|
|||
return $node->put($data);
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +131,8 @@ class Directory extends \OC\Connector\Sabre\Node
|
|||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
} catch (\OCP\Files\InvalidPathException $ex) {
|
||||
throw new InvalidPath($ex->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,11 +217,14 @@ class Directory extends \OC\Connector\Sabre\Node
|
|||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->fileView->rmdir($this->path)) {
|
||||
// assume it wasn't possible to remove due to permission issue
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
}
|
||||
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,6 @@ class FileLocked extends \Sabre\DAV\Exception {
|
|||
*/
|
||||
public function getHTTPCode() {
|
||||
|
||||
return 503;
|
||||
return 423;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ use OCP\Files\InvalidPathException;
|
|||
use OCP\Files\LockNotAcquiredException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
use Sabre\DAV\Exception;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
|
@ -79,6 +81,7 @@ class File extends Node implements IFile {
|
|||
* @throws Exception
|
||||
* @throws EntityTooLarge
|
||||
* @throws ServiceUnavailable
|
||||
* @throws FileLocked
|
||||
* @return string|null
|
||||
*/
|
||||
public function put($data) {
|
||||
|
@ -110,6 +113,12 @@ class File extends Node implements IFile {
|
|||
$partFilePath = $this->path;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->fileView->lockFile($this->path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
|
||||
/** @var \OC\Files\Storage\Storage $partStorage */
|
||||
list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath);
|
||||
|
@ -232,6 +241,8 @@ class File extends Node implements IFile {
|
|||
throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->fileView->unlockFile($this->path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
return '"' . $this->info->getEtag() . '"';
|
||||
}
|
||||
|
||||
|
@ -252,6 +263,8 @@ class File extends Node implements IFile {
|
|||
throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage());
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable("Failed to open file: " . $e->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,6 +286,8 @@ class File extends Node implements IFile {
|
|||
}
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,6 +393,8 @@ class File extends Node implements IFile {
|
|||
return $info->getEtag();
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable("Failed to put file: " . $e->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,12 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
|
||||
$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
|
||||
$this->server->on('afterMethod:GET', [$this,'httpGet']);
|
||||
$this->server->on('afterResponse', function($request, ResponseInterface $response) {
|
||||
$body = $response->getBody();
|
||||
if (is_resource($body)) {
|
||||
fclose($body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,10 +26,12 @@
|
|||
namespace OC\Connector\Sabre;
|
||||
|
||||
use OC\Connector\Sabre\Exception\InvalidPath;
|
||||
use OC\Connector\Sabre\Exception\FileLocked;
|
||||
use OC\Files\FileInfo;
|
||||
use OC\Files\Mount\MoveableMount;
|
||||
use OCP\Files\StorageInvalidException;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use OCP\Lock\LockedException;
|
||||
|
||||
class ObjectTree extends \Sabre\DAV\Tree {
|
||||
|
||||
|
@ -221,8 +223,10 @@ class ObjectTree extends \Sabre\DAV\Tree {
|
|||
if (!$renameOkay) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden('');
|
||||
}
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$this->markDirty($sourceDir);
|
||||
|
@ -258,8 +262,10 @@ class ObjectTree extends \Sabre\DAV\Tree {
|
|||
|
||||
try {
|
||||
$this->fileView->copy($source, $destination);
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
} catch (LockedException $e) {
|
||||
throw new FileLocked($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination);
|
||||
|
|
|
@ -43,6 +43,7 @@ use OCP\Files\FileNameTooLongException;
|
|||
use OCP\Files\InvalidCharacterInPathException;
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\ReservedWordException;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
* Storage backend class for providing common filesystem operation methods
|
||||
|
@ -621,4 +622,32 @@ abstract class Common implements Storage {
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type, ILockingProvider $provider) {
|
||||
$provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function releaseLock($path, $type, ILockingProvider $provider) {
|
||||
$provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function changeLock($path, $type, ILockingProvider $provider) {
|
||||
$provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
*/
|
||||
|
||||
namespace OC\Files\Storage;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
* Provide a common interface to all different storage options
|
||||
|
@ -76,4 +77,26 @@ interface Storage extends \OCP\Files\Storage {
|
|||
*/
|
||||
public function getMetaData($path);
|
||||
|
||||
/**
|
||||
* @param string $path The path of the file to acquire the lock for
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type, ILockingProvider $provider);
|
||||
|
||||
/**
|
||||
* @param string $path The path of the file to release the lock for
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function releaseLock($path, $type, ILockingProvider $provider);
|
||||
|
||||
/**
|
||||
* @param string $path The path of the file to change the lock for
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function changeLock($path, $type, ILockingProvider $provider);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
namespace OC\Files\Storage\Wrapper;
|
||||
|
||||
use OC\Files\Cache\Wrapper\CacheJail;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
* Jail to a subdirectory of the wrapped storage
|
||||
|
@ -424,4 +425,32 @@ class Jail extends Wrapper {
|
|||
public function getETag($path) {
|
||||
return $this->storage->getETag($this->getSourcePath($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type, ILockingProvider $provider) {
|
||||
$this->storage->acquireLock($this->getSourcePath($path), $type, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function releaseLock($path, $type, ILockingProvider $provider) {
|
||||
$this->storage->releaseLock($this->getSourcePath($path), $type, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function changeLock($path, $type, ILockingProvider $provider) {
|
||||
$this->storage->changeLock($this->getSourcePath($path), $type, $provider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
namespace OC\Files\Storage\Wrapper;
|
||||
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
class Wrapper implements \OC\Files\Storage\Storage {
|
||||
/**
|
||||
|
@ -541,4 +542,32 @@ class Wrapper implements \OC\Files\Storage\Storage {
|
|||
public function getMetaData($path) {
|
||||
return $this->storage->getMetaData($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type, ILockingProvider $provider) {
|
||||
$this->storage->acquireLock($path, $type, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function releaseLock($path, $type, ILockingProvider $provider) {
|
||||
$this->storage->releaseLock($path, $type, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function changeLock($path, $type, ILockingProvider $provider) {
|
||||
$this->storage->changeLock($path, $type, $provider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,15 @@
|
|||
|
||||
namespace OC\Files;
|
||||
|
||||
use Icewind\Streams\CallbackWrapper;
|
||||
use OC\Files\Cache\Updater;
|
||||
use OC\Files\Mount\MoveableMount;
|
||||
use OCP\Files\FileNameTooLongException;
|
||||
use OCP\Files\InvalidCharacterInPathException;
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\ReservedWordException;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
|
||||
/**
|
||||
* Class to provide access to ownCloud filesystem via a "view", and methods for
|
||||
|
@ -71,6 +74,11 @@ class View {
|
|||
/** @var \OC\Files\Cache\Updater */
|
||||
protected $updater;
|
||||
|
||||
/**
|
||||
* @var \OCP\Lock\ILockingProvider
|
||||
*/
|
||||
private $lockingProvider;
|
||||
|
||||
/**
|
||||
* @param string $root
|
||||
* @throws \Exception If $root contains an invalid path
|
||||
|
@ -85,6 +93,7 @@ class View {
|
|||
|
||||
$this->fakeRoot = $root;
|
||||
$this->updater = new Updater($this);
|
||||
$this->lockingProvider = \OC::$server->getLockingProvider();
|
||||
}
|
||||
|
||||
public function getAbsolutePath($path = '/') {
|
||||
|
@ -525,6 +534,7 @@ class View {
|
|||
and !Filesystem::isFileBlacklisted($path)
|
||||
) {
|
||||
$path = $this->getRelativePath($absolutePath);
|
||||
|
||||
$exists = $this->file_exists($path);
|
||||
$run = true;
|
||||
if ($this->shouldEmitHooks($path)) {
|
||||
|
@ -604,6 +614,10 @@ class View {
|
|||
if ($path1 == null or $path2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
|
||||
$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$run = true;
|
||||
if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
|
||||
// if it was a rename from a part file to a regular file it was a write and not a rename operation
|
||||
|
@ -629,6 +643,9 @@ class View {
|
|||
$internalPath1 = $mount1->getInternalPath($absolutePath1);
|
||||
$internalPath2 = $mount2->getInternalPath($absolutePath2);
|
||||
|
||||
$this->changeLock($path1, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
|
||||
if ($this->isTargetAllowed($absolutePath2)) {
|
||||
/**
|
||||
|
@ -649,6 +666,15 @@ class View {
|
|||
} else {
|
||||
$result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2);
|
||||
}
|
||||
|
||||
$this->unlockFile($path1, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
if ($internalPath1 === '' and $mount1 instanceof MoveableMount) {
|
||||
// since $path2 now points to a different storage we need to unlock the path on the old storage separately
|
||||
$storage2->releaseLock($internalPath2, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
|
||||
}
|
||||
|
||||
if ((Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
|
||||
// if it was a rename from a part file to a regular file it was a write and not a rename operation
|
||||
$this->updater->update($path2);
|
||||
|
@ -676,6 +702,8 @@ class View {
|
|||
}
|
||||
return $result;
|
||||
} else {
|
||||
$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
|
||||
$this->unlockFile($path2, ILockingProvider::LOCK_SHARED);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
@ -707,6 +735,10 @@ class View {
|
|||
return false;
|
||||
}
|
||||
$run = true;
|
||||
|
||||
$this->lockFile($path2, ILockingProvider::LOCK_SHARED);
|
||||
$this->lockFile($path1, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$exists = $this->file_exists($path2);
|
||||
if ($this->shouldEmitHooks()) {
|
||||
\OC_Hook::emit(
|
||||
|
@ -727,6 +759,9 @@ class View {
|
|||
$internalPath1 = $mount1->getInternalPath($absolutePath1);
|
||||
$storage2 = $mount2->getStorage();
|
||||
$internalPath2 = $mount2->getInternalPath($absolutePath2);
|
||||
|
||||
$this->changeLock($path2, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
if ($mount1->getMountPoint() == $mount2->getMountPoint()) {
|
||||
if ($storage1) {
|
||||
$result = $storage1->copy($internalPath1, $internalPath2);
|
||||
|
@ -737,6 +772,10 @@ class View {
|
|||
$result = $storage2->copyFromStorage($storage1, $internalPath1, $internalPath2);
|
||||
}
|
||||
$this->updater->update($path2);
|
||||
|
||||
$this->unlockFile($path2, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
if ($this->shouldEmitHooks() && $result !== false) {
|
||||
\OC_Hook::emit(
|
||||
Filesystem::CLASSNAME,
|
||||
|
@ -750,6 +789,8 @@ class View {
|
|||
}
|
||||
return $result;
|
||||
} else {
|
||||
$this->unlockFile($path2, ILockingProvider::LOCK_SHARED);
|
||||
$this->unlockFile($path1, ILockingProvider::LOCK_SHARED);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
@ -929,14 +970,31 @@ class View {
|
|||
return false;
|
||||
}
|
||||
|
||||
if(in_array('write', $hooks) || in_array('delete', $hooks) || in_array('read', $hooks)) {
|
||||
// always a shared lock during pre-hooks so the hook can read the file
|
||||
$this->lockFile($path, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
|
||||
$run = $this->runHooks($hooks, $path);
|
||||
list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
|
||||
if ($run and $storage) {
|
||||
if (in_array('write', $hooks) || in_array('delete', $hooks)) {
|
||||
$this->changeLock($path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
try {
|
||||
if (!is_null($extraParam)) {
|
||||
$result = $storage->$operation($internalPath, $extraParam);
|
||||
} else {
|
||||
$result = $storage->$operation($internalPath);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if (in_array('write', $hooks) || in_array('delete', $hooks)) {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
} else if (in_array('read', $hooks)) {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (in_array('delete', $hooks) and $result) {
|
||||
$this->updater->remove($path);
|
||||
|
@ -948,12 +1006,31 @@ class View {
|
|||
$this->updater->update($path, $extraParam);
|
||||
}
|
||||
|
||||
if ($operation === 'fopen' and $result) {
|
||||
$result = CallbackWrapper::wrap($result, null, null, function () use ($hooks, $path) {
|
||||
if (in_array('write', $hooks)) {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
} else if (in_array('read', $hooks)) {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (in_array('write', $hooks) || in_array('delete', $hooks)) {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
} else if (in_array('read', $hooks)) {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->shouldEmitHooks($path) && $result !== false) {
|
||||
if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
|
||||
$this->runHooks($hooks, $path, true);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
} else {
|
||||
$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -1553,4 +1630,114 @@ class View {
|
|||
throw new InvalidPathException($l10n->t('File name is too long'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all parent folders of $path
|
||||
*
|
||||
* @param string $path
|
||||
* @return string[]
|
||||
*/
|
||||
private function getParents($path) {
|
||||
$path = trim($path, '/');
|
||||
if (!$path) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parts = explode('/', $path);
|
||||
|
||||
// remove the single file
|
||||
array_pop($parts);
|
||||
$result = array('/');
|
||||
$resultPath = '';
|
||||
foreach ($parts as $part) {
|
||||
if ($part) {
|
||||
$resultPath .= '/' . $part;
|
||||
$result[] = $resultPath;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path the path of the file to lock, relative to the view
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
*/
|
||||
private function lockPath($path, $type) {
|
||||
$mount = $this->getMount($path);
|
||||
if ($mount) {
|
||||
$mount->getStorage()->acquireLock(
|
||||
$mount->getInternalPath(
|
||||
$this->getAbsolutePath($path)
|
||||
),
|
||||
$type,
|
||||
$this->lockingProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path the path of the file to lock, relative to the view
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
*/
|
||||
private function changeLock($path, $type) {
|
||||
$mount = $this->getMount($path);
|
||||
if ($mount) {
|
||||
$mount->getStorage()->changeLock(
|
||||
$mount->getInternalPath(
|
||||
$this->getAbsolutePath($path)
|
||||
),
|
||||
$type,
|
||||
$this->lockingProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path the path of the file to unlock, relative to the view
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
*/
|
||||
private function unlockPath($path, $type) {
|
||||
$mount = $this->getMount($path);
|
||||
if ($mount) {
|
||||
$mount->getStorage()->releaseLock(
|
||||
$mount->getInternalPath(
|
||||
$this->getAbsolutePath($path)
|
||||
),
|
||||
$type,
|
||||
$this->lockingProvider
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a path and all its parents up to the root of the view
|
||||
*
|
||||
* @param string $path the path of the file to lock relative to the view
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
*/
|
||||
public function lockFile($path, $type) {
|
||||
$path = '/' . trim($path, '/');
|
||||
$this->lockPath($path, $type);
|
||||
|
||||
$parents = $this->getParents($path);
|
||||
foreach ($parents as $parent) {
|
||||
$this->lockPath($parent, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a path and all its parents up to the root of the view
|
||||
*
|
||||
* @param string $path the path of the file to lock relative to the view
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
*/
|
||||
public function unlockFile($path, $type) {
|
||||
$path = rtrim($path, '/');
|
||||
$this->unlockPath($path, $type);
|
||||
|
||||
$parents = $this->getParents($path);
|
||||
foreach ($parents as $parent) {
|
||||
$this->unlockPath($parent, ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@ class MemcacheLockingProvider implements ILockingProvider {
|
|||
*/
|
||||
private $memcache;
|
||||
|
||||
private $acquiredLocks = [
|
||||
'shared' => [],
|
||||
'exclusive' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* @param \OCP\IMemcache $memcache
|
||||
*/
|
||||
|
@ -64,11 +69,16 @@ class MemcacheLockingProvider implements ILockingProvider {
|
|||
if (!$this->memcache->inc($path)) {
|
||||
throw new LockedException($path);
|
||||
}
|
||||
if (!isset($this->acquiredLocks['shared'][$path])) {
|
||||
$this->acquiredLocks['shared'][$path] = 0;
|
||||
}
|
||||
$this->acquiredLocks['shared'][$path]++;
|
||||
} else {
|
||||
$this->memcache->add($path, 0);
|
||||
if (!$this->memcache->cas($path, 0, 'exclusive')) {
|
||||
throw new LockedException($path);
|
||||
}
|
||||
$this->acquiredLocks['exclusive'][$path] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,9 +88,55 @@ class MemcacheLockingProvider implements ILockingProvider {
|
|||
*/
|
||||
public function releaseLock($path, $type) {
|
||||
if ($type === self::LOCK_SHARED) {
|
||||
if (isset($this->acquiredLocks['shared'][$path]) and $this->acquiredLocks['shared'][$path] > 0) {
|
||||
$this->memcache->dec($path);
|
||||
$this->acquiredLocks['shared'][$path]--;
|
||||
}
|
||||
} else if ($type === self::LOCK_EXCLUSIVE) {
|
||||
$this->memcache->cas($path, 'exclusive', 0);
|
||||
unset($this->acquiredLocks['exclusive'][$path]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the type of an existing lock
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function changeLock($path, $targetType) {
|
||||
if ($targetType === self::LOCK_SHARED) {
|
||||
if (!$this->memcache->cas($path, 'exclusive', 1)) {
|
||||
throw new LockedException($path);
|
||||
}
|
||||
unset($this->acquiredLocks['exclusive'][$path]);
|
||||
if (!isset($this->acquiredLocks['shared'][$path])) {
|
||||
$this->acquiredLocks['shared'][$path] = 0;
|
||||
}
|
||||
$this->acquiredLocks['shared'][$path]++;
|
||||
} else if ($targetType === self::LOCK_EXCLUSIVE) {
|
||||
// we can only change a shared lock to an exclusive if there's only a single owner of the shared lock
|
||||
if (!$this->memcache->cas($path, 1, 'exclusive')) {
|
||||
throw new LockedException($path);
|
||||
}
|
||||
$this->acquiredLocks['exclusive'][$path] = true;
|
||||
$this->acquiredLocks['shared'][$path]--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* release all lock acquired by this instance
|
||||
*/
|
||||
public function releaseAll() {
|
||||
foreach ($this->acquiredLocks['shared'] as $path => $count) {
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$this->releaseLock($path, self::LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->acquiredLocks['exclusive'] as $path => $hasLock) {
|
||||
$this->releaseLock($path, self::LOCK_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @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\Lock;
|
||||
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
* Locking provider that does nothing.
|
||||
*
|
||||
* To be used when locking is disabled.
|
||||
*/
|
||||
class NoopLockingProvider implements ILockingProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLocked($path, $type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function acquireLock($path, $type) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function releaseLock($path, $type) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**1
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function releaseAll() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function changeLock($path, $targetType) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
|
@ -46,13 +46,19 @@ class Factory implements ICacheFactory {
|
|||
*/
|
||||
private $distributedCacheClass;
|
||||
|
||||
/**
|
||||
* @var string $lockingCacheClass
|
||||
*/
|
||||
private $lockingCacheClass;
|
||||
|
||||
/**
|
||||
* @param string $globalPrefix
|
||||
* @param string|null $localCacheClass
|
||||
* @param string|null $distributedCacheClass
|
||||
* @param string|null $lockingCacheClass
|
||||
*/
|
||||
public function __construct($globalPrefix,
|
||||
$localCacheClass = null, $distributedCacheClass = null)
|
||||
$localCacheClass = null, $distributedCacheClass = null, $lockingCacheClass = null)
|
||||
{
|
||||
$this->globalPrefix = $globalPrefix;
|
||||
|
||||
|
@ -62,8 +68,23 @@ class Factory implements ICacheFactory {
|
|||
if (!($distributedCacheClass && $distributedCacheClass::isAvailable())) {
|
||||
$distributedCacheClass = $localCacheClass;
|
||||
}
|
||||
if (!($lockingCacheClass && $lockingCacheClass::isAvailable())) {
|
||||
// dont fallback since the fallback might not be suitable for storing lock
|
||||
$lockingCacheClass = '\OC\Memcache\Null';
|
||||
}
|
||||
$this->localCacheClass = $localCacheClass;
|
||||
$this->distributedCacheClass = $distributedCacheClass;
|
||||
$this->lockingCacheClass = $lockingCacheClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a cache instance for storing locks
|
||||
*
|
||||
* @param string $prefix
|
||||
* @return \OCP\IMemcache
|
||||
*/
|
||||
public function createLocking($prefix = '') {
|
||||
return new $this->lockingCacheClass($this->globalPrefix . '/' . $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
namespace OC\Memcache;
|
||||
|
||||
class Null extends Cache {
|
||||
class Null extends Cache implements \OCP\IMemcache {
|
||||
public function get($key) {
|
||||
return null;
|
||||
}
|
||||
|
@ -39,6 +39,22 @@ class Null extends Cache {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function add($key, $value, $ttl = 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function inc($key, $step = 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function dec($key, $step = 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function cas($key, $old, $new) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clear($prefix = '') {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ use OC\Command\AsyncBus;
|
|||
use OC\Diagnostics\NullQueryLogger;
|
||||
use OC\Diagnostics\EventLogger;
|
||||
use OC\Diagnostics\QueryLogger;
|
||||
use OC\Lock\MemcacheLockingProvider;
|
||||
use OC\Lock\NoopLockingProvider;
|
||||
use OC\Mail\Mailer;
|
||||
use OC\Memcache\ArrayCache;
|
||||
use OC\Http\Client\ClientService;
|
||||
|
@ -223,7 +225,7 @@ class Server extends SimpleContainer implements IServerContainer {
|
|||
$this->registerService('MemCacheFactory', function (Server $c) {
|
||||
$config = $c->getConfig();
|
||||
|
||||
if($config->getSystemValue('installed', false)) {
|
||||
if($config->getSystemValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
|
||||
$v = \OC_App::getAppVersions();
|
||||
$v['core'] = implode('.', \OC_Util::getVersion());
|
||||
$version = implode(',', $v);
|
||||
|
@ -232,11 +234,13 @@ class Server extends SimpleContainer implements IServerContainer {
|
|||
$prefix = md5($instanceId.'-'.$version.'-'.$path);
|
||||
return new \OC\Memcache\Factory($prefix,
|
||||
$config->getSystemValue('memcache.local', null),
|
||||
$config->getSystemValue('memcache.distributed', null)
|
||||
$config->getSystemValue('memcache.distributed', null),
|
||||
$config->getSystemValue('memcache.locking', null)
|
||||
);
|
||||
}
|
||||
|
||||
return new \OC\Memcache\Factory('',
|
||||
new ArrayCache(),
|
||||
new ArrayCache(),
|
||||
new ArrayCache()
|
||||
);
|
||||
|
@ -420,6 +424,17 @@ class Server extends SimpleContainer implements IServerContainer {
|
|||
$this->getLogger()
|
||||
);
|
||||
});
|
||||
$this->registerService('LockingProvider', function (Server $c) {
|
||||
if ($c->getConfig()->getSystemValue('filelocking.enabled', false) or (defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
|
||||
/** @var \OC\Memcache\Factory $memcacheFactory */
|
||||
$memcacheFactory = $c->getMemCacheFactory();
|
||||
$memcache = $memcacheFactory->createLocking('lock');
|
||||
if (!($memcache instanceof \OC\Memcache\Null)) {
|
||||
return new MemcacheLockingProvider($memcache);
|
||||
}
|
||||
}
|
||||
return new NoopLockingProvider();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -908,4 +923,14 @@ class Server extends SimpleContainer implements IServerContainer {
|
|||
public function getTrustedDomainHelper() {
|
||||
return $this->query('TrustedDomainHelper');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locking provider
|
||||
*
|
||||
* @return \OCP\Lock\ILockingProvider
|
||||
* @since 8.1.0
|
||||
*/
|
||||
public function getLockingProvider() {
|
||||
return $this->query('LockingProvider');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
// This means that they should be used by apps instead of the internal ownCloud classes
|
||||
namespace OCP\Files;
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
* Provide a common interface to all different storage options
|
||||
|
@ -413,4 +414,27 @@ interface Storage {
|
|||
* @since 8.1.0
|
||||
*/
|
||||
public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath);
|
||||
|
||||
/**
|
||||
* @param string $path The path of the file to acquire the lock for
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function acquireLock($path, $type, ILockingProvider $provider);
|
||||
|
||||
/**
|
||||
* @param string $path The path of the file to acquire the lock for
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
*/
|
||||
public function releaseLock($path, $type, ILockingProvider $provider);
|
||||
|
||||
/**
|
||||
* @param string $path The path of the file to change the lock for
|
||||
* @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
|
||||
* @param \OCP\Lock\ILockingProvider $provider
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function changeLock($path, $type, ILockingProvider $provider);
|
||||
}
|
||||
|
|
|
@ -413,4 +413,12 @@ interface IServerContainer {
|
|||
* @since 8.1.0
|
||||
*/
|
||||
public function getMailer();
|
||||
|
||||
/**
|
||||
* Get the locking provider
|
||||
*
|
||||
* @return \OCP\Lock\ILockingProvider
|
||||
* @since 8.1.0
|
||||
*/
|
||||
public function getLockingProvider();
|
||||
}
|
||||
|
|
|
@ -44,4 +44,18 @@ interface ILockingProvider {
|
|||
* @param int $type self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
*/
|
||||
public function releaseLock($path, $type);
|
||||
|
||||
/**
|
||||
* Change the type of an existing lock
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $targetType self::LOCK_SHARED or self::LOCK_EXCLUSIVE
|
||||
* @throws \OCP\Lock\LockedException
|
||||
*/
|
||||
public function changeLock($path, $targetType);
|
||||
|
||||
/**
|
||||
* release all lock acquired by this instance
|
||||
*/
|
||||
public function releaseAll();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
use OC\Lock\NoopLockingProvider;
|
||||
|
||||
OC_Util::checkAdminUser();
|
||||
OC_App::setActiveNavigationEntry("admin");
|
||||
|
||||
|
@ -175,6 +177,11 @@ $template->assign('fileSharingSettings', $fileSharingSettings);
|
|||
$template->assign('filesExternal', $filesExternal);
|
||||
$template->assign('updaterAppPanel', $updaterAppPanel);
|
||||
$template->assign('ocDefaultEncryptionModulePanel', $ocDefaultEncryptionModulePanel);
|
||||
if (\OC::$server->getLockingProvider() instanceof NoopLockingProvider) {
|
||||
$template->assign('fileLockingEnabled', false);
|
||||
} else {
|
||||
$template->assign('fileLockingEnabled', true);
|
||||
}
|
||||
|
||||
$formsMap = array_map(function ($form) {
|
||||
if (preg_match('%(<h2[^>]*>.*?</h2>)%i', $form, $regs)) {
|
||||
|
@ -200,6 +207,7 @@ $formsAndMore = array_merge($formsAndMore, $formsMap);
|
|||
$formsAndMore[] = ['anchor' => 'backgroundjobs', 'section-name' => $l->t('Cron')];
|
||||
$formsAndMore[] = ['anchor' => 'mail_general_settings', 'section-name' => $l->t('Email server')];
|
||||
$formsAndMore[] = ['anchor' => 'log-section', 'section-name' => $l->t('Log')];
|
||||
$formsAndMore[] = ['anchor' => 'server-status', 'section-name' => $l->t('Server Status')];
|
||||
$formsAndMore[] = ['anchor' => 'admin-tips', 'section-name' => $l->t('Tips & tricks')];
|
||||
if ($updaterAppPanel) {
|
||||
$formsAndMore[] = ['anchor' => 'updater', 'section-name' => $l->t('Updates')];
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
/**
|
||||
* @var array $_
|
||||
* @var \OCP\IL10N $l
|
||||
* @var OC_Defaults $theme
|
||||
*/
|
||||
|
||||
style('settings', 'settings');
|
||||
|
@ -15,32 +16,32 @@ script('core', ['multiselect', 'setupchecks']);
|
|||
vendor_script('select2/select2');
|
||||
vendor_style('select2/select2');
|
||||
|
||||
$levels = array('Debug', 'Info', 'Warning', 'Error', 'Fatal');
|
||||
$levelLabels = array(
|
||||
$levels = ['Debug', 'Info', 'Warning', 'Error', 'Fatal'];
|
||||
$levelLabels = [
|
||||
$l->t( 'Everything (fatal issues, errors, warnings, info, debug)' ),
|
||||
$l->t( 'Info, warnings, errors and fatal issues' ),
|
||||
$l->t( 'Warnings, errors and fatal issues' ),
|
||||
$l->t( 'Errors and fatal issues' ),
|
||||
$l->t( 'Fatal issues only' ),
|
||||
);
|
||||
];
|
||||
|
||||
$mail_smtpauthtype = array(
|
||||
$mail_smtpauthtype = [
|
||||
'' => $l->t('None'),
|
||||
'LOGIN' => $l->t('Login'),
|
||||
'PLAIN' => $l->t('Plain'),
|
||||
'NTLM' => $l->t('NT LAN Manager'),
|
||||
);
|
||||
];
|
||||
|
||||
$mail_smtpsecure = array(
|
||||
$mail_smtpsecure = [
|
||||
'' => $l->t('None'),
|
||||
'ssl' => $l->t('SSL'),
|
||||
'tls' => $l->t('TLS'),
|
||||
);
|
||||
];
|
||||
|
||||
$mail_smtpmode = array(
|
||||
$mail_smtpmode = [
|
||||
'php',
|
||||
'smtp',
|
||||
);
|
||||
];
|
||||
if ($_['sendmail_is_available']) {
|
||||
$mail_smtpmode[] = 'sendmail';
|
||||
}
|
||||
|
@ -137,7 +138,7 @@ if (!$_['isLocaleWorking']) {
|
|||
?>
|
||||
<br>
|
||||
<?php
|
||||
p($l->t('We strongly suggest installing the required packages on your system to support one of the following locales: %s.', array($locales)));
|
||||
p($l->t('We strongly suggest installing the required packages on your system to support one of the following locales: %s.', [$locales]));
|
||||
?>
|
||||
</li>
|
||||
<?php
|
||||
|
@ -266,12 +267,12 @@ if ($_['cronErrors']) {
|
|||
if (time() - $_['lastcron'] <= 3600): ?>
|
||||
<span class="cronstatus success"></span>
|
||||
<span class="crondate" original-title="<?php p($absolute_time);?>">
|
||||
<?php p($l->t("Last cron job execution: %s.", array($relative_time)));?>
|
||||
<?php p($l->t("Last cron job execution: %s.", [$relative_time]));?>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="cronstatus error"></span>
|
||||
<span class="crondate" original-title="<?php p($absolute_time);?>">
|
||||
<?php p($l->t("Last cron job execution: %s. Something seems wrong.", array($relative_time)));?>
|
||||
<?php p($l->t("Last cron job execution: %s. Something seems wrong.", [$relative_time]));?>
|
||||
</span>
|
||||
<?php endif;
|
||||
else: ?>
|
||||
|
@ -527,6 +528,18 @@ if ($_['cronErrors']) {
|
|||
<li><a target="_blank" href="<?php p(link_to_docs('admin-security')); ?>"><?php p($l->t('Hardening and security guidance'));?> ↗</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="server-status">
|
||||
<h2><?php p($l->t('Server Status'));?></h2>
|
||||
<ul>
|
||||
<li>
|
||||
<?php if ($_['fileLockingEnabled']) {
|
||||
p($l->t('Experimental File Lock is enabled.'));
|
||||
} else {
|
||||
p($l->t('Experimental File Lock is disabled.'));
|
||||
} ?>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2><?php p($l->t('Version'));?></h2>
|
||||
|
|
|
@ -11,6 +11,7 @@ use OC\Files\Cache\Watcher;
|
|||
use OC\Files\Storage\Common;
|
||||
use OC\Files\Mount\MountPoint;
|
||||
use OC\Files\Storage\Temporary;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
class TemporaryNoTouch extends \OC\Files\Storage\Temporary {
|
||||
public function touch($path, $mtime = null) {
|
||||
|
@ -1080,4 +1081,30 @@ class View extends \Test\TestCase {
|
|||
public function testNullAsRoot() {
|
||||
new \OC\Files\View(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. reading from a folder that's being renamed
|
||||
*
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testReadFromWriteLockedPath() {
|
||||
$view = new \OC\Files\View();
|
||||
$storage = new Temporary(array());
|
||||
\OC\Files\Filesystem::mount($storage, [], '/');
|
||||
$view->lockFile('/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$view->lockFile('/foo/bar/asd', ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. writing a file that's being downloaded
|
||||
*
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testWriteToReadLockedFile() {
|
||||
$view = new \OC\Files\View();
|
||||
$storage = new Temporary(array());
|
||||
\OC\Files\Filesystem::mount($storage, [], '/');
|
||||
$view->lockFile('/foo/bar', ILockingProvider::LOCK_SHARED);
|
||||
$view->lockFile('/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,30 @@ abstract class LockingProvider extends TestCase {
|
|||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
}
|
||||
|
||||
public function testReleaseAll() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->acquireLock('bar', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->acquireLock('asd', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
$this->instance->releaseAll();
|
||||
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->assertFalse($this->instance->isLocked('bar', ILockingProvider::LOCK_SHARED));
|
||||
$this->assertFalse($this->instance->isLocked('asd', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
}
|
||||
|
||||
public function testReleaseAfterReleaseAll() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$this->instance->releaseAll();
|
||||
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
|
||||
$this->instance->releaseLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
|
@ -134,4 +158,57 @@ abstract class LockingProvider extends TestCase {
|
|||
$this->assertEquals('foo', $e->getPath());
|
||||
}
|
||||
}
|
||||
|
||||
public function testChangeLockToExclusive() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
}
|
||||
|
||||
public function testChangeLockToShared() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->assertFalse($this->instance->isLocked('foo', ILockingProvider::LOCK_EXCLUSIVE));
|
||||
$this->assertTrue($this->instance->isLocked('foo', ILockingProvider::LOCK_SHARED));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testChangeLockToExclusiveDoubleShared() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testChangeLockToExclusiveNoShared() {
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testChangeLockToExclusiveFromExclusive() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testChangeLockToSharedNoExclusive() {
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCP\Lock\LockedException
|
||||
*/
|
||||
public function testChangeLockToSharedFromShared() {
|
||||
$this->instance->acquireLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
$this->instance->changeLock('foo', ILockingProvider::LOCK_SHARED);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ abstract class TestCase extends \PHPUnit_Framework_TestCase {
|
|||
protected function tearDown() {
|
||||
$hookExceptions = \OC_Hook::$thrownExceptions;
|
||||
\OC_Hook::$thrownExceptions = [];
|
||||
\OC::$server->getLockingProvider()->releaseAll();
|
||||
if(!empty($hookExceptions)) {
|
||||
throw $hookExceptions[0];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue