2014-05-21 01:44:57 +04:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copyright (c) 2013 ownCloud, Inc.
|
|
|
|
* This file is licensed under the Affero General Public License version 3 or
|
|
|
|
* later.
|
|
|
|
* See the COPYING-README file.
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace OC\Files\Storage\Wrapper;
|
|
|
|
|
|
|
|
use OC\Files\Filesystem;
|
|
|
|
use OCP\Files\LockNotAcquiredException;
|
|
|
|
use OCP\Files\Lock;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class LockingWrapper
|
|
|
|
* A Storage Wrapper used to lock files at the system level
|
|
|
|
* @package OC\Files\Storage\Wrapper
|
|
|
|
*
|
|
|
|
* Notes: Does the $locks array need to be global to all LockingWrapper instances, such as in the case of two paths
|
|
|
|
* that point to the same physical file? Otherwise accessing the file from a different path the second time would show
|
|
|
|
* the file as locked, even though this process is the one locking it.
|
|
|
|
*/
|
|
|
|
class LockingWrapper extends Wrapper {
|
|
|
|
|
|
|
|
/** @var array $locks Holds an array of lock instances indexed by path for this storage */
|
|
|
|
protected $locks = array();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Acquire a lock on a file
|
|
|
|
* @param string $path Path to file, relative to this storage
|
|
|
|
* @param integer $lockType A Lock class constant, Lock::READ/Lock::WRITE
|
2014-05-24 15:46:44 +04:00
|
|
|
* @param null|resource $existingHandle An existing file handle from an fopen()
|
2014-05-21 01:44:57 +04:00
|
|
|
* @return bool|\OCP\Files\Lock Lock instance on success, false on failure
|
|
|
|
*/
|
2014-05-24 15:46:44 +04:00
|
|
|
protected function getLock($path, $lockType, $existingHandle = null){
|
2014-05-23 18:29:37 +04:00
|
|
|
$path = Filesystem::normalizePath($this->storage->getLocalFile($path));
|
2014-05-21 01:44:57 +04:00
|
|
|
if(!isset($this->locks[$path])) {
|
|
|
|
$this->locks[$path] = new Lock($path);
|
|
|
|
}
|
2014-05-24 15:46:44 +04:00
|
|
|
$this->locks[$path]->addLock($lockType, $existingHandle);
|
2014-05-21 01:44:57 +04:00
|
|
|
return $this->locks[$path];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Release an existing lock
|
|
|
|
* @param string $path Path to file, relative to this storage
|
|
|
|
* @return bool true on success, false on failure
|
|
|
|
*/
|
|
|
|
protected function releaseLock($path, $lockType, $releaseAll = false){
|
2014-05-23 18:29:37 +04:00
|
|
|
$path = Filesystem::normalizePath($this->storage->getLocalFile($path));
|
2014-05-21 01:44:57 +04:00
|
|
|
if(isset($this->locks[$path])) {
|
|
|
|
if($releaseAll) {
|
|
|
|
return $this->locks[$path]->releaseAll();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return $this->locks[$path]->release($lockType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* see http://php.net/manual/en/function.file_get_contents.php
|
|
|
|
* @param string $path
|
|
|
|
* @return string
|
|
|
|
* @throws \Exception
|
|
|
|
*/
|
|
|
|
public function file_get_contents($path) {
|
|
|
|
try {
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->getLock($path, Lock::READ);
|
2014-05-21 01:44:57 +04:00
|
|
|
$result = $this->storage->file_get_contents($path);
|
|
|
|
}
|
|
|
|
catch(\Exception $originalException) {
|
|
|
|
// Need to release the lock before more operations happen in upstream exception handlers
|
|
|
|
$this->releaseLock($path, Lock::READ);
|
|
|
|
throw $originalException;
|
|
|
|
}
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->releaseLock($path, Lock::READ);
|
2014-05-21 01:44:57 +04:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function file_put_contents($path, $data) {
|
|
|
|
try {
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->getLock($path, Lock::WRITE);
|
2014-05-21 01:44:57 +04:00
|
|
|
$result = $this->storage->file_put_contents($path, $data);
|
|
|
|
}
|
|
|
|
catch(\Exception $originalException) {
|
|
|
|
// Release lock, throw original exception
|
|
|
|
$this->releaseLock($path, Lock::WRITE);
|
|
|
|
throw $originalException;
|
|
|
|
}
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->releaseLock($path, Lock::WRITE);
|
2014-05-21 01:44:57 +04:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function copy($path1, $path2) {
|
|
|
|
try {
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->getLock($path1, Lock::READ);
|
|
|
|
$this->getLock($path2, Lock::WRITE);
|
2014-05-21 01:44:57 +04:00
|
|
|
$result = $this->storage->copy($path1, $path2);
|
|
|
|
}
|
|
|
|
catch(\Exception $originalException) {
|
|
|
|
// Release locks, throw original exception
|
|
|
|
$this->releaseLock($path1, Lock::READ);
|
|
|
|
$this->releaseLock($path2, Lock::WRITE);
|
|
|
|
throw $originalException;
|
|
|
|
}
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->releaseLock($path1, Lock::READ);
|
|
|
|
$this->releaseLock($path2, Lock::WRITE);
|
2014-05-21 01:44:57 +04:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function rename($path1, $path2) {
|
|
|
|
try {
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->getLock($path1, Lock::READ);
|
|
|
|
$this->getLock($path2, Lock::WRITE);
|
2014-05-21 01:44:57 +04:00
|
|
|
$result = $this->storage->rename($path1, $path2);
|
|
|
|
}
|
|
|
|
catch(\Exception $originalException) {
|
|
|
|
// Release locks, throw original exception
|
|
|
|
$this->releaseLock($path1, Lock::READ);
|
|
|
|
$this->releaseLock($path2, Lock::WRITE);
|
|
|
|
throw $originalException;
|
|
|
|
}
|
2014-05-23 18:29:37 +04:00
|
|
|
$this->releaseLock($path1, Lock::READ);
|
|
|
|
$this->releaseLock($path2, Lock::WRITE);
|
2014-05-21 01:44:57 +04:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2014-05-24 15:46:44 +04:00
|
|
|
public function fopen($path, $mode) {
|
|
|
|
$lockType = Lock::READ;
|
|
|
|
switch ($mode) {
|
|
|
|
case 'r+':
|
|
|
|
case 'rb+':
|
|
|
|
case 'w+':
|
|
|
|
case 'wb+':
|
|
|
|
case 'x+':
|
|
|
|
case 'xb+':
|
|
|
|
case 'a+':
|
|
|
|
case 'ab+':
|
|
|
|
case 'c+':
|
|
|
|
case 'w':
|
|
|
|
case 'wb':
|
|
|
|
case 'x':
|
|
|
|
case 'xb':
|
|
|
|
case 'a':
|
|
|
|
case 'ab':
|
|
|
|
case 'c':
|
|
|
|
$lockType = Lock::WRITE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// The handle for $this->fopen() is used outside of this class, so the handle/lock can't be closed
|
|
|
|
// Instead, it will be closed when the request goes out of scope
|
|
|
|
// Storage doesn't have an fclose()
|
|
|
|
if($result = $this->storage->fopen($path, $mode)) {
|
|
|
|
$this->getLock($path, $lockType, $result);
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function unlink($path) {
|
|
|
|
try {
|
|
|
|
$this->getLock($path, Lock::WRITE);
|
|
|
|
$result = $this->storage->unlink($path);
|
|
|
|
}
|
|
|
|
catch(\Exception $originalException) {
|
|
|
|
// Need to release the lock before more operations happen in upstream exception handlers
|
|
|
|
$this->releaseLock($path, Lock::WRITE);
|
|
|
|
throw $originalException;
|
|
|
|
}
|
|
|
|
$this->releaseLock($path, Lock::WRITE);
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
2014-05-21 01:44:57 +04:00
|
|
|
|
|
|
|
}
|