Merge pull request #4467 from owncloud/storage-wrapper-quota
Move quota logic from filesystem proxy to storage wrapperremotes/origin/stable6
commit
d7dde3cfbc
@ -1,114 +0,0 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* ownCloud |
||||
* |
||||
* @author Robin Appelman |
||||
* @copyright 2011 Robin Appelman icewind1991@gmail.com |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
* License as published by the Free Software Foundation; either |
||||
* version 3 of the License, or any later version. |
||||
* |
||||
* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
/** |
||||
* user quota management |
||||
*/ |
||||
|
||||
class OC_FileProxy_Quota extends OC_FileProxy{ |
||||
static $rootView; |
||||
private $userQuota=array(); |
||||
|
||||
/** |
||||
* get the quota for the user |
||||
* @param user |
||||
* @return int |
||||
*/ |
||||
private function getQuota($user) { |
||||
if(in_array($user, $this->userQuota)) { |
||||
return $this->userQuota[$user]; |
||||
} |
||||
$userQuota=OC_Preferences::getValue($user, 'files', 'quota', 'default'); |
||||
if($userQuota=='default') { |
||||
$userQuota=OC_AppConfig::getValue('files', 'default_quota', 'none'); |
||||
} |
||||
if($userQuota=='none') { |
||||
$this->userQuota[$user]=-1; |
||||
}else{ |
||||
$this->userQuota[$user]=OC_Helper::computerFileSize($userQuota); |
||||
} |
||||
return $this->userQuota[$user]; |
||||
|
||||
} |
||||
|
||||
/** |
||||
* get the free space in the path's owner home folder |
||||
* @param path |
||||
* @return int |
||||
*/ |
||||
private function getFreeSpace($path) { |
||||
/** |
||||
* @var \OC\Files\Storage\Storage $storage |
||||
* @var string $internalPath |
||||
*/ |
||||
list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); |
||||
$owner = $storage->getOwner($internalPath); |
||||
if (!$owner) { |
||||
return -1; |
||||
} |
||||
|
||||
$totalSpace = $this->getQuota($owner); |
||||
if($totalSpace == -1) { |
||||
return -1; |
||||
} |
||||
|
||||
$view = new \OC\Files\View("/".$owner."/files"); |
||||
|
||||
$rootInfo = $view->getFileInfo('/'); |
||||
$usedSpace = isset($rootInfo['size'])?$rootInfo['size']:0; |
||||
return $totalSpace - $usedSpace; |
||||
} |
||||
|
||||
public function postFree_space($path, $space) { |
||||
$free=$this->getFreeSpace($path); |
||||
if($free==-1) { |
||||
return $space; |
||||
} |
||||
if ($space < 0){ |
||||
return $free; |
||||
} |
||||
return min($free, $space); |
||||
} |
||||
|
||||
public function preFile_put_contents($path, $data) { |
||||
if (is_resource($data)) { |
||||
$data = '';//TODO: find a way to get the length of the stream without emptying it |
||||
} |
||||
return (strlen($data)<$this->getFreeSpace($path) or $this->getFreeSpace($path)==-1); |
||||
} |
||||
|
||||
public function preCopy($path1, $path2) { |
||||
if(!self::$rootView) { |
||||
self::$rootView = new \OC\Files\View(''); |
||||
} |
||||
return (self::$rootView->filesize($path1)<$this->getFreeSpace($path2) or $this->getFreeSpace($path2)==-1); |
||||
} |
||||
|
||||
public function preFromTmpFile($tmpfile, $path) { |
||||
return (filesize($tmpfile)<$this->getFreeSpace($path) or $this->getFreeSpace($path)==-1); |
||||
} |
||||
|
||||
public function preFromUploadedFile($tmpfile, $path) { |
||||
return (filesize($tmpfile)<$this->getFreeSpace($path) or $this->getFreeSpace($path)==-1); |
||||
} |
||||
} |
@ -0,0 +1,104 @@ |
||||
<?php |
||||
/** |
||||
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> |
||||
* This file is licensed under the Affero General Public License version 3 or |
||||
* later. |
||||
* See the COPYING-README file. |
||||
*/ |
||||
|
||||
namespace OC\Files\Storage\Wrapper; |
||||
|
||||
class Quota extends Wrapper { |
||||
|
||||
/** |
||||
* @var int $quota |
||||
*/ |
||||
protected $quota; |
||||
|
||||
/** |
||||
* @param array $parameters |
||||
*/ |
||||
public function __construct($parameters) { |
||||
$this->storage = $parameters['storage']; |
||||
$this->quota = $parameters['quota']; |
||||
} |
||||
|
||||
protected function getSize($path) { |
||||
$cache = $this->getCache(); |
||||
$data = $cache->get($path); |
||||
if (is_array($data) and isset($data['size'])) { |
||||
return $data['size']; |
||||
} else { |
||||
return \OC\Files\SPACE_NOT_COMPUTED; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get free space as limited by the quota |
||||
* |
||||
* @param string $path |
||||
* @return int |
||||
*/ |
||||
public function free_space($path) { |
||||
if ($this->quota < 0) { |
||||
return $this->storage->free_space($path); |
||||
} else { |
||||
$used = $this->getSize(''); |
||||
if ($used < 0) { |
||||
return \OC\Files\SPACE_NOT_COMPUTED; |
||||
} else { |
||||
$free = $this->storage->free_space($path); |
||||
return min($free, (max($this->quota - $used, 0))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* see http://php.net/manual/en/function.file_put_contents.php |
||||
* |
||||
* @param string $path |
||||
* @param string $data |
||||
* @return bool |
||||
*/ |
||||
public function file_put_contents($path, $data) { |
||||
$free = $this->free_space(''); |
||||
if ($free < 0 or strlen($data) < $free) { |
||||
return $this->storage->file_put_contents($path, $data); |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* see http://php.net/manual/en/function.copy.php |
||||
* |
||||
* @param string $source |
||||
* @param string $target |
||||
* @return bool |
||||
*/ |
||||
public function copy($source, $target) { |
||||
$free = $this->free_space(''); |
||||
if ($free < 0 or $this->getSize($source) < $free) { |
||||
return $this->storage->copy($source, $target); |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* see http://php.net/manual/en/function.fopen.php |
||||
* |
||||
* @param string $path |
||||
* @param string $mode |
||||
* @return resource |
||||
*/ |
||||
public function fopen($path, $mode) { |
||||
$source = $this->storage->fopen($path, $mode); |
||||
$free = $this->free_space(''); |
||||
if ($free >= 0) { |
||||
return \OC\Files\Stream\Quota::wrap($source, $free); |
||||
} else { |
||||
return $source; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,128 @@ |
||||
<?php |
||||
/** |
||||
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> |
||||
* This file is licensed under the Affero General Public License version 3 or |
||||
* later. |
||||
* See the COPYING-README file. |
||||
*/ |
||||
|
||||
namespace OC\Files\Stream; |
||||
|
||||
/** |
||||
* stream wrapper limits the amount of data that can be written to a stream |
||||
* |
||||
* usage: void \OC\Files\Stream\Quota::register($id, $stream, $limit) |
||||
* or: resource \OC\Files\Stream\Quota::wrap($stream, $limit) |
||||
*/ |
||||
class Quota { |
||||
private static $streams = array(); |
||||
|
||||
/** |
||||
* @var resource $source |
||||
*/ |
||||
private $source; |
||||
|
||||
/** |
||||
* @var int $limit |
||||
*/ |
||||
private $limit; |
||||
|
||||
/** |
||||
* @param string $id |
||||
* @param resource $stream |
||||
* @param int $limit |
||||
*/ |
||||
public static function register($id, $stream, $limit) { |
||||
self::$streams[$id] = array($stream, $limit); |
||||
} |
||||
|
||||
/** |
||||
* remove all registered streams |
||||
*/ |
||||
public static function clear() { |
||||
self::$streams = array(); |
||||
} |
||||
|
||||
/** |
||||
* @param resource $stream |
||||
* @param int $limit |
||||
* @return resource |
||||
*/ |
||||
static public function wrap($stream, $limit) { |
||||
$id = uniqid(); |
||||
self::register($id, $stream, $limit); |
||||
$meta = stream_get_meta_data($stream); |
||||
return fopen('quota://' . $id, $meta['mode']); |
||||
} |
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) { |
||||
$id = substr($path, strlen('quota://')); |
||||
if (isset(self::$streams[$id])) { |
||||
list($this->source, $this->limit) = self::$streams[$id]; |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public function stream_seek($offset, $whence = SEEK_SET) { |
||||
if ($whence === SEEK_SET) { |
||||
$this->limit += $this->stream_tell() - $offset; |
||||
} else { |
||||
$this->limit -= $offset; |
||||
} |
||||
fseek($this->source, $offset, $whence); |
||||
} |
||||
|
||||
public function stream_tell() { |
||||
return ftell($this->source); |
||||
} |
||||
|
||||
public function stream_read($count) { |
||||
$this->limit -= $count; |
||||
return fread($this->source, $count); |
||||
} |
||||
|
||||
public function stream_write($data) { |
||||
$size = strlen($data); |
||||
if ($size > $this->limit) { |
||||
$data = substr($data, 0, $this->limit); |
||||
$size = $this->limit; |
||||
} |
||||
$this->limit -= $size; |
||||
return fwrite($this->source, $data); |
||||
} |
||||
|
||||
public function stream_set_option($option, $arg1, $arg2) { |
||||
switch ($option) { |
||||
case STREAM_OPTION_BLOCKING: |
||||
stream_set_blocking($this->source, $arg1); |
||||
break; |
||||
case STREAM_OPTION_READ_TIMEOUT: |
||||
stream_set_timeout($this->source, $arg1, $arg2); |
||||
break; |
||||
case STREAM_OPTION_WRITE_BUFFER: |
||||
stream_set_write_buffer($this->source, $arg1, $arg2); |
||||
} |
||||
} |
||||
|
||||
public function stream_stat() { |
||||
return fstat($this->source); |
||||
} |
||||
|
||||
public function stream_lock($mode) { |
||||
flock($this->source, $mode); |
||||
} |
||||
|
||||
public function stream_flush() { |
||||
return fflush($this->source); |
||||
} |
||||
|
||||
public function stream_eof() { |
||||
return feof($this->source); |
||||
} |
||||
|
||||
public function stream_close() { |
||||
fclose($this->source); |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
<?php |
||||
/** |
||||
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> |
||||
* This file is licensed under the Affero General Public License version 3 or |
||||
* later. |
||||
* See the COPYING-README file. |
||||
*/ |
||||
|
||||
namespace Test\Files\Storage\Wrapper; |
||||
|
||||
//ensure the constants are loaded |
||||
\OC::$loader->load('\OC\Files\Filesystem'); |
||||
|
||||
class Quota extends \Test\Files\Storage\Storage { |
||||
/** |
||||
* @var string tmpDir |
||||
*/ |
||||
private $tmpDir; |
||||
|
||||
public function setUp() { |
||||
$this->tmpDir = \OC_Helper::tmpFolder(); |
||||
$storage = new \OC\Files\Storage\Local(array('datadir' => $this->tmpDir)); |
||||
$this->instance = new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => 10000000)); |
||||
} |
||||
|
||||
public function tearDown() { |
||||
\OC_Helper::rmdirr($this->tmpDir); |
||||
} |
||||
|
||||
protected function getLimitedStorage($limit) { |
||||
$storage = new \OC\Files\Storage\Local(array('datadir' => $this->tmpDir)); |
||||
$storage->getScanner()->scan(''); |
||||
return new \OC\Files\Storage\Wrapper\Quota(array('storage' => $storage, 'quota' => $limit)); |
||||
} |
||||
|
||||
public function testFilePutContentsNotEnoughSpace() { |
||||
$instance = $this->getLimitedStorage(3); |
||||
$this->assertFalse($instance->file_put_contents('foo', 'foobar')); |
||||
} |
||||
|
||||
public function testCopyNotEnoughSpace() { |
||||
$instance = $this->getLimitedStorage(9); |
||||
$this->assertEquals(6, $instance->file_put_contents('foo', 'foobar')); |
||||
$instance->getScanner()->scan(''); |
||||
$this->assertFalse($instance->copy('foo', 'bar')); |
||||
} |
||||
|
||||
public function testFreeSpace() { |
||||
$instance = $this->getLimitedStorage(9); |
||||
$this->assertEquals(9, $instance->free_space('')); |
||||
} |
||||
|
||||
public function testFWriteNotEnoughSpace() { |
||||
$instance = $this->getLimitedStorage(9); |
||||
$stream = $instance->fopen('foo', 'w+'); |
||||
$this->assertEquals(6, fwrite($stream, 'foobar')); |
||||
$this->assertEquals(3, fwrite($stream, 'qwerty')); |
||||
fclose($stream); |
||||
$this->assertEquals('foobarqwe', $instance->file_get_contents('foo')); |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
<?php |
||||
/** |
||||
* Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> |
||||
* This file is licensed under the Affero General Public License version 3 or |
||||
* later. |
||||
* See the COPYING-README file. |
||||
*/ |
||||
|
||||
namespace Test\Files\Stream; |
||||
|
||||
class Quota extends \PHPUnit_Framework_TestCase { |
||||
public function tearDown() { |
||||
\OC\Files\Stream\Quota::clear(); |
||||
} |
||||
|
||||
protected function getStream($mode, $limit) { |
||||
$source = fopen('php://temp', $mode); |
||||
return \OC\Files\Stream\Quota::wrap($source, $limit); |
||||
} |
||||
|
||||
public function testWriteEnoughSpace() { |
||||
$stream = $this->getStream('w+', 100); |
||||
$this->assertEquals(6, fwrite($stream, 'foobar')); |
||||
rewind($stream); |
||||
$this->assertEquals('foobar', fread($stream, 100)); |
||||
} |
||||
|
||||
public function testWriteNotEnoughSpace() { |
||||
$stream = $this->getStream('w+', 3); |
||||
$this->assertEquals(3, fwrite($stream, 'foobar')); |
||||
rewind($stream); |
||||
$this->assertEquals('foo', fread($stream, 100)); |
||||
} |
||||
|
||||
public function testWriteNotEnoughSpaceSecondTime() { |
||||
$stream = $this->getStream('w+', 9); |
||||
$this->assertEquals(6, fwrite($stream, 'foobar')); |
||||
$this->assertEquals(3, fwrite($stream, 'qwerty')); |
||||
rewind($stream); |
||||
$this->assertEquals('foobarqwe', fread($stream, 100)); |
||||
} |
||||
|
||||
public function testWriteEnoughSpaceRewind() { |
||||
$stream = $this->getStream('w+', 6); |
||||
$this->assertEquals(6, fwrite($stream, 'foobar')); |
||||
rewind($stream); |
||||
$this->assertEquals(3, fwrite($stream, 'qwe')); |
||||
rewind($stream); |
||||
$this->assertEquals('qwebar', fread($stream, 100)); |
||||
} |
||||
|
||||
public function testWriteNotEnoughSpaceRead() { |
||||
$stream = $this->getStream('w+', 6); |
||||
$this->assertEquals(6, fwrite($stream, 'foobar')); |
||||
rewind($stream); |
||||
$this->assertEquals('foobar', fread($stream, 6)); |
||||
$this->assertEquals(0, fwrite($stream, 'qwe')); |
||||
} |
||||
|
||||
public function testWriteNotEnoughSpaceExistingStream() { |
||||
$source = fopen('php://temp', 'w+'); |
||||
fwrite($source, 'foobar'); |
||||
$stream = \OC\Files\Stream\Quota::wrap($source, 3); |
||||
$this->assertEquals(3, fwrite($stream, 'foobar')); |
||||
rewind($stream); |
||||
$this->assertEquals('foobarfoo', fread($stream, 100)); |
||||
} |
||||
|
||||
public function testWriteNotEnoughSpaceExistingStreamRewind() { |
||||
$source = fopen('php://temp', 'w+'); |
||||
fwrite($source, 'foobar'); |
||||
$stream = \OC\Files\Stream\Quota::wrap($source, 3); |
||||
rewind($stream); |
||||
$this->assertEquals(6, fwrite($stream, 'qwerty')); |
||||
rewind($stream); |
||||
$this->assertEquals('qwerty', fread($stream, 100)); |
||||
} |
||||
} |
Loading…
Reference in new issue