From e928a342c452b49ed84e36d3534bb9715f55845b Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Fri, 28 Dec 2012 20:10:01 -0500 Subject: [PATCH 001/247] Check if extra / is necessary for the folder URL --- apps/files/js/fileactions.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index e1d8b60d31..533b11a6e5 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -190,7 +190,11 @@ FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { - window.location = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent($('#dir').val()).replace(/%2F/g, '/') + '/' + encodeURIComponent(filename); + var dir = encodeURIComponent($('#dir').val()).replace(/%2F/g, '/'); + if (dir != '/') { + dir = dir + '/'; + } + window.location = OC.linkTo('files', 'index.php') + '?dir=' + dir + encodeURIComponent(filename); }); FileActions.setDefault('dir', 'Open'); From 855c9480b73d0ea3f6d0562503db5d72f64c14db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Sat, 9 Feb 2013 13:07:44 +0100 Subject: [PATCH 002/247] only encodeURIComponent once --- apps/files/js/fileactions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 533b11a6e5..316acda513 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -190,11 +190,11 @@ FileActions.register('all', 'Rename', OC.PERMISSION_UPDATE, function () { FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename) { - var dir = encodeURIComponent($('#dir').val()).replace(/%2F/g, '/'); + var dir = $('#dir').val() if (dir != '/') { dir = dir + '/'; } - window.location = OC.linkTo('files', 'index.php') + '?dir=' + dir + encodeURIComponent(filename); + window.location = OC.linkTo('files', 'index.php') + '?dir=' + encodeURIComponent(dir + filename); }); FileActions.setDefault('dir', 'Open'); From c6dbb33e1562ad1748feb3cb931ea5016520b867 Mon Sep 17 00:00:00 2001 From: kondou Date: Sat, 13 Apr 2013 14:48:16 +0200 Subject: [PATCH 003/247] Fix textfield label overlapping value. --- core/templates/installation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templates/installation.php b/core/templates/installation.php index c70903cba5..4987e50d1c 100644 --- a/core/templates/installation.php +++ b/core/templates/installation.php @@ -162,7 +162,7 @@

+ value="" />

From ba9db1964053be769b42452fee65ac4720489f81 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 7 May 2013 19:42:46 +0200 Subject: [PATCH 004/247] Add wrapper storage backend --- lib/files/storage/wrapper.php | 420 ++++++++++++++++++++++++++++ tests/lib/files/storage/wrapper.php | 26 ++ 2 files changed, 446 insertions(+) create mode 100644 lib/files/storage/wrapper.php create mode 100644 tests/lib/files/storage/wrapper.php diff --git a/lib/files/storage/wrapper.php b/lib/files/storage/wrapper.php new file mode 100644 index 0000000000..5939faec56 --- /dev/null +++ b/lib/files/storage/wrapper.php @@ -0,0 +1,420 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +class Wrapper implements Storage { + /** + * @var Storage $storage + */ + protected $storage; + + /** + * @param array $parameters + */ + public function __construct($parameters) { + $this->storage = $parameters['storage']; + } + + /** + * Get the identifier for the storage, + * the returned id should be the same for every storage object that is created with the same parameters + * and two storage objects with the same id should refer to two storages that display the same files. + * + * @return string + */ + public function getId() { + return $this->storage->getId(); + } + + /** + * see http://php.net/manual/en/function.mkdir.php + * + * @param string $path + * @return bool + */ + public function mkdir($path) { + return $this->storage->mkdir($path); + } + + /** + * see http://php.net/manual/en/function.rmdir.php + * + * @param string $path + * @return bool + */ + public function rmdir($path) { + return $this->storage->rmdir($path); + } + + /** + * see http://php.net/manual/en/function.opendir.php + * + * @param string $path + * @return resource + */ + public function opendir($path) { + return $this->storage->opendir($path); + } + + /** + * see http://php.net/manual/en/function.is_dir.php + * + * @param string $path + * @return bool + */ + public function is_dir($path) { + return $this->storage->is_dir($path); + } + + /** + * see http://php.net/manual/en/function.is_file.php + * + * @param string $path + * @return bool + */ + public function is_file($path) { + return $this->storage->is_file($path); + } + + /** + * see http://php.net/manual/en/function.stat.php + * only the following keys are required in the result: size and mtime + * + * @param string $path + * @return array + */ + public function stat($path) { + return $this->storage->stat($path); + } + + /** + * see http://php.net/manual/en/function.filetype.php + * + * @param string $path + * @return bool + */ + public function filetype($path) { + return $this->storage->filetype($path); + } + + /** + * see http://php.net/manual/en/function.filesize.php + * The result for filesize when called on a folder is required to be 0 + * + * @param string $path + * @return int + */ + public function filesize($path) { + return $this->storage->filesize($path); + } + + /** + * check if a file can be created in $path + * + * @param string $path + * @return bool + */ + public function isCreatable($path) { + return $this->storage->isCreatable($path); + } + + /** + * check if a file can be read + * + * @param string $path + * @return bool + */ + public function isReadable($path) { + return $this->storage->isReadable($path); + } + + /** + * check if a file can be written to + * + * @param string $path + * @return bool + */ + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + + /** + * check if a file can be deleted + * + * @param string $path + * @return bool + */ + public function isDeletable($path) { + return $this->storage->isDeletable($path); + } + + /** + * check if a file can be shared + * + * @param string $path + * @return bool + */ + public function isSharable($path) { + return $this->storage->isSharable($path); + } + + /** + * get the full permissions of a path. + * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php + * + * @param string $path + * @return int + */ + public function getPermissions($path) { + return $this->storage->getPermissions($path); + } + + /** + * see http://php.net/manual/en/function.file_exists.php + * + * @param string $path + * @return bool + */ + public function file_exists($path) { + return $this->storage->file_exists($path); + } + + /** + * see http://php.net/manual/en/function.filemtime.php + * + * @param string $path + * @return int + */ + public function filemtime($path) { + return $this->storage->filemtime($path); + } + + /** + * see http://php.net/manual/en/function.file_get_contents.php + * + * @param string $path + * @return string + */ + public function file_get_contents($path) { + return $this->storage->file_get_contents($path); + } + + /** + * 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) { + return $this->storage->file_put_contents($path, $data); + } + + /** + * see http://php.net/manual/en/function.unlink.php + * + * @param string $path + * @return bool + */ + public function unlink($path) { + return $this->storage->unlink($path); + } + + /** + * see http://php.net/manual/en/function.rename.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function rename($path1, $path2) { + return $this->storage->rename($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.copy.php + * + * @param string $path1 + * @param string $path2 + * @return bool + */ + public function copy($path1, $path2) { + return $this->storage->copy($path1, $path2); + } + + /** + * see http://php.net/manual/en/function.fopen.php + * + * @param string $path + * @param string $mode + * @return resource + */ + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + + /** + * get the mimetype for a file or folder + * The mimetype for a folder is required to be "httpd/unix-directory" + * + * @param string $path + * @return string + */ + public function getMimeType($path) { + return $this->storage->getMimeType($path); + } + + /** + * see http://php.net/manual/en/function.hash.php + * + * @param string $type + * @param string $path + * @param bool $raw + * @return string + */ + public function hash($type, $path, $raw = false) { + return $this->storage->hash($type, $path, $raw); + } + + /** + * see http://php.net/manual/en/function.free_space.php + * + * @param string $path + * @return int + */ + public function free_space($path) { + return $this->storage->free_space($path); + } + + /** + * search for occurrences of $query in file names + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->storage->search($query); + } + + /** + * see http://php.net/manual/en/function.touch.php + * If the backend does not support the operation, false should be returned + * + * @param string $path + * @param int $mtime + * @return bool + */ + public function touch($path, $mtime = null) { + return $this->storage->touch($path, $mtime); + } + + /** + * get the path to a local version of the file. + * The local version of the file can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + return $this->storage->getLocalFile($path); + } + + /** + * get the path to a local version of the folder. + * The local version of the folder can be temporary and doesn't have to be persistent across requests + * + * @param string $path + * @return string + */ + public function getLocalFolder($path) { + return $this->storage->getLocalFolder($path); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + * + * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. + * returning true for other changes in the folder is optional + */ + public function hasUpdated($path, $time) { + return $this->storage->hasUpdated($path, $time); + } + + /** + * get a cache instance for the storage + * + * @param string $path + * @return \OC\Files\Cache\Cache + */ + public function getCache($path = '') { + return $this->storage->getCache($path); + } + + /** + * get a scanner instance for the storage + * + * @param string $path + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path = '') { + return $this->storage->getScanner($path); + } + + + /** + * get the user id of the owner of a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->storage->getOwner($path); + } + + /** + * get a permissions cache instance for the cache + * + * @param string $path + * @return \OC\Files\Cache\Permissions + */ + public function getPermissionsCache($path = '') { + return $this->storage->getPermissions($path); + } + + /** + * get a watcher instance for the cache + * + * @param string $path + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path = '') { + return $this->storage->getWatcher($path); + } + + /** + * @return \OC\Files\Cache\Storage + */ + public function getStorageCache() { + return $this->storage->getStorageCache(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + return $this->storage->getETag($path); + } +} diff --git a/tests/lib/files/storage/wrapper.php b/tests/lib/files/storage/wrapper.php new file mode 100644 index 0000000000..8452949a72 --- /dev/null +++ b/tests/lib/files/storage/wrapper.php @@ -0,0 +1,26 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Storage; + +class Wrapper extends 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(array('storage' => $storage)); + } + + public function tearDown() { + \OC_Helper::rmdirr($this->tmpDir); + } +} From d97ef0805ba5c5687e0642bb8f7c085966153ac9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 8 May 2013 22:35:10 +0200 Subject: [PATCH 005/247] Add mechanism to allow apps to wraper storage classes --- lib/files/mount/mount.php | 26 +++++++++++++++++++++++++- lib/files/storage/loader.php | 17 +++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 lib/files/storage/loader.php diff --git a/lib/files/mount/mount.php b/lib/files/mount/mount.php index 69b8285ab4..d25a7b3be6 100644 --- a/lib/files/mount/mount.php +++ b/lib/files/mount/mount.php @@ -22,6 +22,11 @@ class Mount { private $arguments = array(); private $mountPoint; + /** + * @var callable[] $storageWrappers + */ + private $storageWrappers = array(); + /** * @param string|\OC\Files\Storage\Storage $storage * @param string $mountpoint @@ -62,7 +67,7 @@ class Mount { private function createStorage() { if (class_exists($this->class)) { try { - return new $this->class($this->arguments); + return $this->loadStorage($this->class, $this->arguments); } catch (\Exception $exception) { \OC_Log::write('core', $exception->getMessage(), \OC_Log::ERROR); return null; @@ -73,6 +78,25 @@ class Mount { } } + /** + * allow modifier storage behaviour by adding wrappers around storages + * + * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage + * + * @param callable $callback + */ + public function addStorageWrapper($callback) { + $this->storageWrappers[] = $callback; + } + + private function loadStorage($class, $arguments) { + $storage = new $class($arguments); + foreach ($this->storageWrappers as $wrapper) { + $storage = $wrapper($this->mountPoint, $storage); + } + return $storage; + } + /** * @return \OC\Files\Storage\Storage */ diff --git a/lib/files/storage/loader.php b/lib/files/storage/loader.php new file mode 100644 index 0000000000..7330cae4cc --- /dev/null +++ b/lib/files/storage/loader.php @@ -0,0 +1,17 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +class Loader { + private function $wrappers + + public function load($class, $arguments) { + return new $class($arguments); + } +} From b41999a2c0628f3241b07afcae0b29bae682583c Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Sat, 9 Mar 2013 21:00:48 +0100 Subject: [PATCH 006/247] Implement OC\Log as proxy to OC_Log OC\Log implements the Psr\Log\LoggerInterface interface. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md for the full interface specification. --- lib/legacy/log.php | 72 +++++++++++++++++++ lib/log.php | 171 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 202 insertions(+), 41 deletions(-) create mode 100644 lib/legacy/log.php diff --git a/lib/legacy/log.php b/lib/legacy/log.php new file mode 100644 index 0000000000..4e6642b6a2 --- /dev/null +++ b/lib/legacy/log.php @@ -0,0 +1,72 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * logging utilities + * + * Log is saved by default at data/owncloud.log using OC_Log_Owncloud. + * Selecting other backend is done with a config option 'log_type'. + */ + +OC_Log::$object = new \OC\Log(); +class OC_Log { + public static $object; + + const DEBUG=0; + const INFO=1; + const WARN=2; + const ERROR=3; + const FATAL=4; + + static public $enabled = true; + static protected $class = null; + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + if (self::$enabled) { + if (!self::$class) { + self::$class = 'OC_Log_'.ucfirst(OC_Config::getValue('log_type', 'owncloud')); + call_user_func(array(self::$class, 'init')); + } + $log_class=self::$class; + $log_class::write($app, $message, $level); + } + } + + //Fatal errors handler + public static function onShutdown() { + $error = error_get_last(); + if($error) { + //ob_end_clean(); + self::write('PHP', $error['message'] . ' at ' . $error['file'] . '#' . $error['line'], self::FATAL); + } else { + return true; + } + } + + // Uncaught exception handler + public static function onException($exception) { + self::write('PHP', + $exception->getMessage() . ' at ' . $exception->getFile() . '#' . $exception->getLine(), + self::FATAL); + } + + //Recoverable errors handler + public static function onError($number, $message, $file, $line) { + if (error_reporting() === 0) { + return; + } + self::write('PHP', $message . ' at ' . $file . '#' . $line, self::WARN); + + } +} diff --git a/lib/log.php b/lib/log.php index 3f3334801e..f7a68c3068 100644 --- a/lib/log.php +++ b/lib/log.php @@ -1,69 +1,158 @@ + * Copyright (c) 2013 Bart Visscher * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ +namespace OC; + /** * logging utilities * - * Log is saved by default at data/owncloud.log using OC_Log_Owncloud. - * Selecting other backend is done with a config option 'log_type'. + * This is a stand in, this should be replaced by a Psr\Log\LoggerInterface + * compatible logger. See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md + * for the full interface specification. + * + * MonoLog is an example implementing this interface. */ -class OC_Log { +class Log { const DEBUG=0; const INFO=1; const WARN=2; const ERROR=3; const FATAL=4; - static public $enabled = true; - static protected $class = null; + const NOTICE=5; + const CRITICAL=6; + const ALERT=7; /** - * write a message in the log - * @param string $app + * System is unusable. + * * @param string $message - * @param int level + * @param array $context + * @return null */ - public static function write($app, $message, $level) { - if (self::$enabled) { - if (!self::$class) { - self::$class = 'OC_Log_'.ucfirst(OC_Config::getValue('log_type', 'owncloud')); - call_user_func(array(self::$class, 'init')); - } - $log_class=self::$class; - $log_class::write($app, $message, $level); - } + public function emergency($message, array $context = array()) + { + $this->log(OC_Log::FATAL, $message, $context); } - //Fatal errors handler - public static function onShutdown() { - $error = error_get_last(); - if($error) { - //ob_end_clean(); - self::write('PHP', $error['message'] . ' at ' . $error['file'] . '#' . $error['line'], self::FATAL); + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * @return null + */ + public function alert($message, array $context = array()) + { + $this->log(self::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * @return null + */ + public function critical($message, array $context = array()) + { + $this->log(self::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * @return null + */ + public function error($message, array $context = array()) + { + $this->log(OC_Log::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * @return null + */ + public function warning($message, array $context = array()) + { + $this->log(OC_Log::WARN, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * @return null + */ + public function notice($message, array $context = array()) + { + $this->log(self::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * @return null + */ + public function info($message, array $context = array()) + { + $this->log(OC_Log::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * @return null + */ + public function debug($message, array $context = array()) + { + $this->log(OC_Log::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + protected function log($level, $message, array $context = array()) + { + if (isset($context['app'])) { + $app = $context['app']; } else { - return true; + $app = 'no app in context'; } - } - - // Uncaught exception handler - public static function onException($exception) { - self::write('PHP', - $exception->getMessage() . ' at ' . $exception->getFile() . '#' . $exception->getLine(), - self::FATAL); - } - - //Recoverable errors handler - public static function onError($number, $message, $file, $line) { - if (error_reporting() === 0) { - return; - } - self::write('PHP', $message . ' at ' . $file . '#' . $line, self::WARN); - + OC_Log::write($app, $message, $level); } } + +require_once __DIR__.'/legacy/'.basename(__FILE__); From 7e5bb96027ee4409d8883afffa2cad50cdc3c782 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Wed, 20 Mar 2013 17:36:57 +0100 Subject: [PATCH 007/247] Fix OC\Log with OC_Log in wrong namespace --- lib/log.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/log.php b/lib/log.php index f7a68c3068..6353693a28 100644 --- a/lib/log.php +++ b/lib/log.php @@ -38,7 +38,7 @@ class Log { */ public function emergency($message, array $context = array()) { - $this->log(OC_Log::FATAL, $message, $context); + $this->log(self::FATAL, $message, $context); } /** @@ -80,7 +80,7 @@ class Log { */ public function error($message, array $context = array()) { - $this->log(OC_Log::ERROR, $message, $context); + $this->log(self::ERROR, $message, $context); } /** @@ -95,7 +95,7 @@ class Log { */ public function warning($message, array $context = array()) { - $this->log(OC_Log::WARN, $message, $context); + $this->log(self::WARN, $message, $context); } /** @@ -121,7 +121,7 @@ class Log { */ public function info($message, array $context = array()) { - $this->log(OC_Log::INFO, $message, $context); + $this->log(self::INFO, $message, $context); } /** @@ -133,7 +133,7 @@ class Log { */ public function debug($message, array $context = array()) { - $this->log(OC_Log::DEBUG, $message, $context); + $this->log(self::DEBUG, $message, $context); } /** @@ -151,7 +151,7 @@ class Log { } else { $app = 'no app in context'; } - OC_Log::write($app, $message, $level); + \OC_Log::write($app, $message, $level); } } From 0b4018e7ff5f2660850338e8ea0aaab57c48209e Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Wed, 8 May 2013 18:21:07 +0200 Subject: [PATCH 008/247] Remove include for loading legacy class --- lib/log.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/log.php b/lib/log.php index 6353693a28..d83f88c1df 100644 --- a/lib/log.php +++ b/lib/log.php @@ -154,5 +154,3 @@ class Log { \OC_Log::write($app, $message, $level); } } - -require_once __DIR__.'/legacy/'.basename(__FILE__); From 31ad43f9221899b2379b3b72e43270b17fa9f881 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 13 May 2013 08:05:53 +0200 Subject: [PATCH 009/247] Code style --- lib/log.php | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/lib/log.php b/lib/log.php index d83f88c1df..3bedcd4ed3 100644 --- a/lib/log.php +++ b/lib/log.php @@ -36,8 +36,7 @@ class Log { * @param array $context * @return null */ - public function emergency($message, array $context = array()) - { + public function emergency($message, array $context = array()) { $this->log(self::FATAL, $message, $context); } @@ -51,8 +50,7 @@ class Log { * @param array $context * @return null */ - public function alert($message, array $context = array()) - { + public function alert($message, array $context = array()) { $this->log(self::ALERT, $message, $context); } @@ -65,8 +63,7 @@ class Log { * @param array $context * @return null */ - public function critical($message, array $context = array()) - { + public function critical($message, array $context = array()) { $this->log(self::CRITICAL, $message, $context); } @@ -78,8 +75,7 @@ class Log { * @param array $context * @return null */ - public function error($message, array $context = array()) - { + public function error($message, array $context = array()) { $this->log(self::ERROR, $message, $context); } @@ -93,8 +89,7 @@ class Log { * @param array $context * @return null */ - public function warning($message, array $context = array()) - { + public function warning($message, array $context = array()) { $this->log(self::WARN, $message, $context); } @@ -105,8 +100,7 @@ class Log { * @param array $context * @return null */ - public function notice($message, array $context = array()) - { + public function notice($message, array $context = array()) { $this->log(self::NOTICE, $message, $context); } @@ -119,8 +113,7 @@ class Log { * @param array $context * @return null */ - public function info($message, array $context = array()) - { + public function info($message, array $context = array()) { $this->log(self::INFO, $message, $context); } @@ -131,8 +124,7 @@ class Log { * @param array $context * @return null */ - public function debug($message, array $context = array()) - { + public function debug($message, array $context = array()) { $this->log(self::DEBUG, $message, $context); } @@ -144,8 +136,7 @@ class Log { * @param array $context * @return null */ - protected function log($level, $message, array $context = array()) - { + protected function log($level, $message, array $context = array()) { if (isset($context['app'])) { $app = $context['app']; } else { From 061fb79e5c26ec1cf3432dda8c439a167bfc4e1d Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 13 May 2013 08:13:31 +0200 Subject: [PATCH 010/247] Use the constants from OC_Log --- lib/log.php | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/log.php b/lib/log.php index 3bedcd4ed3..fc190f1378 100644 --- a/lib/log.php +++ b/lib/log.php @@ -19,12 +19,6 @@ namespace OC; */ class Log { - const DEBUG=0; - const INFO=1; - const WARN=2; - const ERROR=3; - const FATAL=4; - const NOTICE=5; const CRITICAL=6; const ALERT=7; @@ -37,7 +31,7 @@ class Log { * @return null */ public function emergency($message, array $context = array()) { - $this->log(self::FATAL, $message, $context); + $this->log(\OC_Log::FATAL, $message, $context); } /** @@ -76,7 +70,7 @@ class Log { * @return null */ public function error($message, array $context = array()) { - $this->log(self::ERROR, $message, $context); + $this->log(\OC_Log::ERROR, $message, $context); } /** @@ -90,7 +84,7 @@ class Log { * @return null */ public function warning($message, array $context = array()) { - $this->log(self::WARN, $message, $context); + $this->log(\OC_Log::WARN, $message, $context); } /** @@ -114,7 +108,7 @@ class Log { * @return null */ public function info($message, array $context = array()) { - $this->log(self::INFO, $message, $context); + $this->log(\OC_Log::INFO, $message, $context); } /** @@ -125,7 +119,7 @@ class Log { * @return null */ public function debug($message, array $context = array()) { - $this->log(self::DEBUG, $message, $context); + $this->log(\OC_Log::DEBUG, $message, $context); } /** From 009e9559f3a12d7275ab242ececaa7f9452165c1 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 13 May 2013 08:16:41 +0200 Subject: [PATCH 011/247] Remove default return hint --- lib/log.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/log.php b/lib/log.php index fc190f1378..442872af9c 100644 --- a/lib/log.php +++ b/lib/log.php @@ -28,7 +28,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function emergency($message, array $context = array()) { $this->log(\OC_Log::FATAL, $message, $context); @@ -42,7 +41,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function alert($message, array $context = array()) { $this->log(self::ALERT, $message, $context); @@ -55,7 +53,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function critical($message, array $context = array()) { $this->log(self::CRITICAL, $message, $context); @@ -67,7 +64,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function error($message, array $context = array()) { $this->log(\OC_Log::ERROR, $message, $context); @@ -81,7 +77,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function warning($message, array $context = array()) { $this->log(\OC_Log::WARN, $message, $context); @@ -92,7 +87,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function notice($message, array $context = array()) { $this->log(self::NOTICE, $message, $context); @@ -105,7 +99,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function info($message, array $context = array()) { $this->log(\OC_Log::INFO, $message, $context); @@ -116,7 +109,6 @@ class Log { * * @param string $message * @param array $context - * @return null */ public function debug($message, array $context = array()) { $this->log(\OC_Log::DEBUG, $message, $context); @@ -128,7 +120,6 @@ class Log { * @param mixed $level * @param string $message * @param array $context - * @return null */ protected function log($level, $message, array $context = array()) { if (isset($context['app'])) { From 1d799f22c1a8cec5a8dd45a0fdd182c011a6f5f0 Mon Sep 17 00:00:00 2001 From: kondou Date: Tue, 28 May 2013 17:34:57 +0200 Subject: [PATCH 012/247] Default to localhost, if nothing is entered. --- lib/setup.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/setup.php b/lib/setup.php index 7082f0b2af..b0af062052 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -61,7 +61,7 @@ class OC_Setup { $error[] = $l->t("%s you may not use dots in the database name", array($dbprettyname)); } if($dbtype != 'oci' && empty($options['dbhost'])) { - $error[] = $l->t("%s set the database host.", array($dbprettyname)); + $options['dbhost'] = 'localhost'; } } From 3fe46706aff12995a49f5b69d0ea3645dae6de7c Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 2 Jun 2013 19:53:18 +0200 Subject: [PATCH 013/247] Update to jquery.multiselect 1.13 --- core/js/jquery.multiselect.js | 153 ++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 53 deletions(-) diff --git a/core/js/jquery.multiselect.js b/core/js/jquery.multiselect.js index 46aab7ebf0..16ae426417 100644 --- a/core/js/jquery.multiselect.js +++ b/core/js/jquery.multiselect.js @@ -1,6 +1,7 @@ +/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, boss:true, undef:true, curly:true, browser:true, jquery:true */ /* - * jQuery MultiSelect UI Widget 1.11 - * Copyright (c) 2011 Eric Hynds + * jQuery MultiSelect UI Widget 1.13 + * Copyright (c) 2012 Eric Hynds * * http://www.erichynds.com/jquery/jquery-ui-multiselect-widget/ * @@ -34,8 +35,8 @@ $.widget("ech.multiselect", { noneSelectedText: 'Select options', selectedText: '# selected', selectedList: 0, - show: '', - hide: '', + show: null, + hide: null, autoOpen: false, multiple: true, position: {} @@ -62,7 +63,7 @@ $.widget("ech.multiselect", { menu = (this.menu = $('
')) .addClass('ui-multiselect-menu ui-widget ui-widget-content ui-corner-all') .addClass( o.classes ) - .insertAfter( button ), + .appendTo( document.body ), header = (this.header = $('
')) .addClass('ui-widget-header ui-corner-all ui-multiselect-header ui-helper-clearfix') @@ -119,70 +120,72 @@ $.widget("ech.multiselect", { menu = this.menu, checkboxContainer = this.checkboxContainer, optgroups = [], - html = [], + html = "", id = el.attr('id') || multiselectID++; // unique ID for the label & option tags // build items - this.element.find('option').each(function( i ){ + el.find('option').each(function( i ){ var $this = $(this), parent = this.parentNode, title = this.innerHTML, description = this.title, value = this.value, - inputID = this.id || 'ui-multiselect-'+id+'-option-'+i, + inputID = 'ui-multiselect-' + (this.id || id + '-option-' + i), isDisabled = this.disabled, isSelected = this.selected, - labelClasses = ['ui-corner-all'], + labelClasses = [ 'ui-corner-all' ], + liClasses = (isDisabled ? 'ui-multiselect-disabled ' : ' ') + this.className, optLabel; // is this an optgroup? - if( parent.tagName.toLowerCase() === 'optgroup' ){ - optLabel = parent.getAttribute('label'); + if( parent.tagName === 'OPTGROUP' ){ + optLabel = parent.getAttribute( 'label' ); // has this optgroup been added already? if( $.inArray(optLabel, optgroups) === -1 ){ - html.push('
  • ' + optLabel + '
  • '); + html += '
  • ' + optLabel + '
  • '; optgroups.push( optLabel ); } } if( isDisabled ){ - labelClasses.push('ui-state-disabled'); + labelClasses.push( 'ui-state-disabled' ); } // browsers automatically select the first option // by default with single selects if( isSelected && !o.multiple ){ - labelClasses.push('ui-state-active'); + labelClasses.push( 'ui-state-active' ); } - html.push('
  • '); + html += '
  • '; // create the label - html.push('
  • '); + html += ' />' + title + ''; }); // insert into the DOM - checkboxContainer.html( html.join('') ); + checkboxContainer.html( html ); // cache some moar useful elements this.labels = menu.find('label'); + this.inputs = this.labels.children('input'); // set widths this._setButtonWidth(); @@ -197,10 +200,10 @@ $.widget("ech.multiselect", { } }, - // updates the button text. call refresh() to rebuild + // updates the button text. call refresh() to rebuild update: function(){ var o = this.options, - $inputs = this.labels.find('input'), + $inputs = this.inputs, $checked = $inputs.filter(':checked'), numChecked = $checked.length, value; @@ -211,7 +214,7 @@ $.widget("ech.multiselect", { if($.isFunction( o.selectedText )){ value = o.selectedText.call(this, numChecked, $inputs.length, $checked.get()); } else if( /\d/.test(o.selectedList) && o.selectedList > 0 && numChecked <= o.selectedList){ - value = $checked.map(function(){ return this.title; }).get().join(', '); + value = $checked.map(function(){ return $(this).next().html(); }).get().join(', '); } else { value = o.selectedText.replace('#', numChecked).replace('#', $inputs.length); } @@ -291,8 +294,8 @@ $.widget("ech.multiselect", { var $this = $(this), $inputs = $this.parent().nextUntil('li.ui-multiselect-optgroup-label').find('input:visible:not(:disabled)'), - nodes = $inputs.get(), - label = $this.parent().text(); + nodes = $inputs.get(), + label = $this.parent().text(); // trigger event and bail if the return is false if( self._trigger('beforeoptgrouptoggle', e, { inputs:nodes, label:label }) === false ){ @@ -343,11 +346,15 @@ $.widget("ech.multiselect", { tags = self.element.find('option'); // bail if this input is disabled or the event is cancelled - if( this.disabled || self._trigger('click', e, { value:val, text:this.title, checked:checked }) === false ){ + if( this.disabled || self._trigger('click', e, { value: val, text: this.title, checked: checked }) === false ){ e.preventDefault(); return; } + // make sure the input has focus. otherwise, the esc key + // won't close the menu after clicking an item. + $this.focus(); + // toggle aria state $this.attr('aria-selected', checked); @@ -389,7 +396,7 @@ $.widget("ech.multiselect", { // handler fires before the form is actually reset. delaying it a bit // gives the form inputs time to clear. $(this.element[0].form).bind('reset.multiselect', function(){ - setTimeout(function(){ self.update(); }, 10); + setTimeout($.proxy(self.refresh, self), 10); }); }, @@ -428,7 +435,7 @@ $.widget("ech.multiselect", { // if at the first/last element if( !$next.length ){ - var $container = this.menu.find('ul:last'); + var $container = this.menu.find('ul').last(); // move to the first/last this.menu.find('label')[ moveToLast ? 'last' : 'first' ]().trigger('mouseover'); @@ -445,27 +452,29 @@ $.widget("ech.multiselect", { // other related attributes of a checkbox. // // The context of this function should be a checkbox; do not proxy it. - _toggleCheckbox: function( prop, flag ){ + _toggleState: function( prop, flag ){ return function(){ - !this.disabled && (this[ prop ] = flag); + if( !this.disabled ) { + this[ prop ] = flag; + } if( flag ){ this.setAttribute('aria-selected', true); } else { this.removeAttribute('aria-selected'); } - } + }; }, _toggleChecked: function( flag, group ){ - var $inputs = (group && group.length) ? - group : - this.labels.find('input'), - + var $inputs = (group && group.length) ? group : this.inputs, self = this; // toggle state on inputs - $inputs.each(this._toggleCheckbox('checked', flag)); + $inputs.each(this._toggleState('checked', flag)); + + // give the first input focus + $inputs.eq(0).focus(); // update button text this.update(); @@ -480,7 +489,7 @@ $.widget("ech.multiselect", { .find('option') .each(function(){ if( !this.disabled && $.inArray(this.value, values) > -1 ){ - self._toggleCheckbox('selected', flag).call( this ); + self._toggleState('selected', flag).call( this ); } }); @@ -494,9 +503,22 @@ $.widget("ech.multiselect", { this.button .attr({ 'disabled':flag, 'aria-disabled':flag })[ flag ? 'addClass' : 'removeClass' ]('ui-state-disabled'); - this.menu - .find('input') - .attr({ 'disabled':flag, 'aria-disabled':flag }) + var inputs = this.menu.find('input'); + var key = "ech-multiselect-disabled"; + + if(flag) { + // remember which elements this widget disabled (not pre-disabled) + // elements, so that they can be restored if the widget is re-enabled. + inputs = inputs.filter(':enabled') + .data(key, true) + } else { + inputs = inputs.filter(function() { + return $.data(this, key) === true; + }).removeData(key); + } + + inputs + .attr({ 'disabled':flag, 'arial-disabled':flag }) .parent()[ flag ? 'addClass' : 'removeClass' ]('ui-state-disabled'); this.element @@ -509,16 +531,17 @@ $.widget("ech.multiselect", { button = this.button, menu = this.menu, speed = this.speed, - o = this.options; + o = this.options, + args = []; // bail if the multiselectopen event returns false, this widget is disabled, or is already open if( this._trigger('beforeopen') === false || button.hasClass('ui-state-disabled') || this._isOpen ){ return; } - var $container = menu.find('ul:last'), + var $container = menu.find('ul').last(), effect = o.show, - pos = button.position(); + pos = button.offset(); // figure out opening effects/speeds if( $.isArray(o.show) ){ @@ -526,6 +549,12 @@ $.widget("ech.multiselect", { speed = o.show[1] || self.speed; } + // if there's an effect, assume jQuery UI is in use + // build the arguments to pass to show() + if( effect ) { + args = [ effect, speed ]; + } + // set the scroll of the checkbox container $container.scrollTop(0).height(o.height); @@ -536,17 +565,19 @@ $.widget("ech.multiselect", { menu .show() .position( o.position ) - .hide() - .show( effect, speed ); + .hide(); // if position utility is not available... } else { menu.css({ - top: pos.top+button.outerHeight(), + top: pos.top + button.outerHeight(), left: pos.left - }).show( effect, speed ); + }); } + // show the menu, maybe with a speed/effect combo + $.fn.show.apply(menu, args); + // select the first option // triggering both mouseover and mouseover because 1.4.2+ has a bug where triggering mouseover // will actually trigger mouseenter. the mouseenter trigger is there for when it's eventually fixed @@ -563,7 +594,10 @@ $.widget("ech.multiselect", { return; } - var o = this.options, effect = o.hide, speed = this.speed; + var o = this.options, + effect = o.hide, + speed = this.speed, + args = []; // figure out opening effects/speeds if( $.isArray(o.hide) ){ @@ -571,7 +605,11 @@ $.widget("ech.multiselect", { speed = o.hide[1] || this.speed; } - this.menu.hide(effect, speed); + if( effect ) { + args = [ effect, speed ]; + } + + $.fn.hide.apply(this.menu, args); this.button.removeClass('ui-state-active').trigger('blur').trigger('mouseleave'); this._isOpen = false; this._trigger('close'); @@ -618,6 +656,10 @@ $.widget("ech.multiselect", { return this.menu; }, + getButton: function(){ + return this.button; + }, + // react to option changes after initialization _setOption: function( key, value ){ var menu = this.menu; @@ -633,7 +675,7 @@ $.widget("ech.multiselect", { menu.find('a.ui-multiselect-none span').eq(-1).text(value); break; case 'height': - menu.find('ul:last').height( parseInt(value,10) ); + menu.find('ul').last().height( parseInt(value,10) ); break; case 'minWidth': this.options[ key ] = parseInt(value,10); @@ -649,6 +691,11 @@ $.widget("ech.multiselect", { case 'classes': menu.add(this.button).removeClass(this.options.classes).addClass(value); break; + case 'multiple': + menu.toggleClass('ui-multiselect-single', !value); + this.options.multiple = value; + this.element[0].multiple = value; + this.refresh(); } $.Widget.prototype._setOption.apply( this, arguments ); From 861fe54f2b33f41b83cf5e714efca72d18ad1b3f Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 4 Jun 2013 00:19:42 +0200 Subject: [PATCH 014/247] Forgot the css. --- core/css/jquery.multiselect.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/css/jquery.multiselect.css b/core/css/jquery.multiselect.css index 156799f086..898786a615 100644 --- a/core/css/jquery.multiselect.css +++ b/core/css/jquery.multiselect.css @@ -11,7 +11,7 @@ .ui-multiselect-header span.ui-icon { float:left } .ui-multiselect-header li.ui-multiselect-close { float:right; text-align:right; padding-right:0 } -.ui-multiselect-menu { display:none; padding:3px; position:absolute; z-index:10000 } +.ui-multiselect-menu { display:none; padding:3px; position:absolute; z-index:10000; text-align: left } .ui-multiselect-checkboxes { position:relative /* fixes bug in IE6/7 */; overflow-y:scroll } .ui-multiselect-checkboxes label { cursor:default; display:block; border:1px solid transparent; padding:3px 1px } .ui-multiselect-checkboxes label input { position:relative; top:1px } From bd675124096707a925faac2774516975ec7049c1 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 7 Jun 2013 17:07:13 +0200 Subject: [PATCH 015/247] manage creating and wrapping storages in it's own class --- lib/files/filesystem.php | 11 +++++++-- lib/files/mount/mount.php | 45 +++++++++++++----------------------- lib/files/storage/loader.php | 27 +++++++++++++++++++--- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index eadd8a93fa..ce89c5c23f 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -30,6 +30,7 @@ namespace OC\Files; +use OC\Files\Storage\Loader; const FREE_SPACE_UNKNOWN = -2; const FREE_SPACE_UNLIMITED = -3; @@ -142,6 +143,11 @@ class Filesystem { */ const signal_param_run = 'run'; + /** + * @var \OC\Files\Storage\Loader $loader + */ + private static $loader; + /** * get the mountpoint of the storage object for a path * ( note: because a storage is not always mounted inside the fakeroot, the @@ -221,6 +227,7 @@ class Filesystem { if (self::$defaultInstance) { return false; } + self::$loader = new Loader(); self::$defaultInstance = new View($root); self::$mounts = new Mount\Manager(); @@ -232,7 +239,7 @@ class Filesystem { return true; } - static public function initMounts(){ + static public function initMounts() { self::$mounts = new Mount\Manager(); } @@ -365,7 +372,7 @@ class Filesystem { * @param string $mountpoint */ static public function mount($class, $arguments, $mountpoint) { - $mount = new Mount\Mount($class, $mountpoint, $arguments); + $mount = new Mount\Mount($class, $mountpoint, $arguments, self::$loader); self::$mounts->addMount($mount); } diff --git a/lib/files/mount/mount.php b/lib/files/mount/mount.php index d25a7b3be6..17b0055ee8 100644 --- a/lib/files/mount/mount.php +++ b/lib/files/mount/mount.php @@ -9,10 +9,10 @@ namespace OC\Files\Mount; use \OC\Files\Filesystem; +use OC\Files\Storage\Loader; +use OC\Files\Storage\Storage; class Mount { - - /** * @var \OC\Files\Storage\Storage $storage */ @@ -23,24 +23,30 @@ class Mount { private $mountPoint; /** - * @var callable[] $storageWrappers + * @var \OC\Files\Storage\Loader $loader */ - private $storageWrappers = array(); + private $loader; /** - * @param string|\OC\Files\Storage\Storage $storage + * @param string | \OC\Files\Storage\Storage $storage * @param string $mountpoint - * @param array $arguments (optional) + * @param array $arguments (optional)\ + * @param \OC\Files\Storage\Loader $loader */ - public function __construct($storage, $mountpoint, $arguments = null) { + public function __construct($storage, $mountpoint, $arguments = null, $loader = null) { if (is_null($arguments)) { $arguments = array(); } + if (is_null($loader)) { + $this->loader = new Loader(); + } else { + $this->loader = $loader; + } $mountpoint = $this->formatPath($mountpoint); - if ($storage instanceof \OC\Files\Storage\Storage) { + if ($storage instanceof Storage) { $this->class = get_class($storage); - $this->storage = $storage; + $this->storage = $this->loader->wrap($mountpoint, $storage); } else { // Update old classes to new namespace if (strpos($storage, 'OC_Filestorage_') !== false) { @@ -67,7 +73,7 @@ class Mount { private function createStorage() { if (class_exists($this->class)) { try { - return $this->loadStorage($this->class, $this->arguments); + return $this->loader->load($this->mountPoint, $this->class, $this->arguments); } catch (\Exception $exception) { \OC_Log::write('core', $exception->getMessage(), \OC_Log::ERROR); return null; @@ -78,25 +84,6 @@ class Mount { } } - /** - * allow modifier storage behaviour by adding wrappers around storages - * - * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage - * - * @param callable $callback - */ - public function addStorageWrapper($callback) { - $this->storageWrappers[] = $callback; - } - - private function loadStorage($class, $arguments) { - $storage = new $class($arguments); - foreach ($this->storageWrappers as $wrapper) { - $storage = $wrapper($this->mountPoint, $storage); - } - return $storage; - } - /** * @return \OC\Files\Storage\Storage */ diff --git a/lib/files/storage/loader.php b/lib/files/storage/loader.php index 7330cae4cc..2572ef443b 100644 --- a/lib/files/storage/loader.php +++ b/lib/files/storage/loader.php @@ -9,9 +9,30 @@ namespace OC\Files\Storage; class Loader { - private function $wrappers + /** + * @var callable[] $storageWrappers + */ + private $storageWrappers = array(); - public function load($class, $arguments) { - return new $class($arguments); + /** + * allow modifier storage behaviour by adding wrappers around storages + * + * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage + * + * @param callable $callback + */ + public function addStorageWrapper($callback) { + $this->storageWrappers[] = $callback; + } + + public function load($mountPoint, $class, $arguments) { + return $this->wrap($mountPoint, new $class($arguments)); + } + + public function wrap($mountPoint, $storage) { + foreach ($this->storageWrappers as $wrapper) { + $storage = $wrapper($mountPoint, $storage); + } + return $storage; } } From 85a9b7f0949932b1def24f86ab7dc3df87933e6f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 7 Jun 2013 17:12:45 +0200 Subject: [PATCH 016/247] Storage wrapper: provide access to the wrapped storage --- lib/files/storage/wrapper.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/files/storage/wrapper.php b/lib/files/storage/wrapper.php index 5939faec56..78892a564c 100644 --- a/lib/files/storage/wrapper.php +++ b/lib/files/storage/wrapper.php @@ -10,7 +10,7 @@ namespace OC\Files\Storage; class Wrapper implements Storage { /** - * @var Storage $storage + * @var \OC\Files\Storage\Storage $storage */ protected $storage; @@ -21,6 +21,13 @@ class Wrapper implements Storage { $this->storage = $parameters['storage']; } + /** + * @return \OC\Files\Storage\Storage + */ + public function getWrapperStorage() { + return $this->storage; + } + /** * Get the identifier for the storage, * the returned id should be the same for every storage object that is created with the same parameters From 2708ab09abf12321df97ed730a83c328d2b360fc Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 7 Jun 2013 17:40:19 +0200 Subject: [PATCH 017/247] storage loader needs to be accessible by apps --- lib/files/filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index 0daa863e79..a7b1da57c1 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -146,7 +146,7 @@ class Filesystem { /** * @var \OC\Files\Storage\Loader $loader */ - private static $loader; + public static $loader; /** * get the mountpoint of the storage object for a path From 31693d393749c94591dc0814cc502eac8d415e11 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 7 Jun 2013 17:40:38 +0200 Subject: [PATCH 018/247] add test cases for Mount --- tests/lib/files/mount/mount.php | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/lib/files/mount/mount.php diff --git a/tests/lib/files/mount/mount.php b/tests/lib/files/mount/mount.php new file mode 100644 index 0000000000..aa98db856f --- /dev/null +++ b/tests/lib/files/mount/mount.php @@ -0,0 +1,46 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Mount; + + +use OC\Files\Storage\Loader; +use OC\Files\Storage\Wrapper; + +class Mount extends \PHPUnit_Framework_TestCase { + public function testFromStorageObject() { + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->disableOriginalConstructor() + ->getMock(); + $mount = new \OC\Files\Mount\Mount($storage, '/foo'); + $this->assertInstanceOf('\OC\Files\Storage\Temporary', $mount->getStorage()); + } + + public function testFromStorageClassname() { + $mount = new \OC\Files\Mount\Mount('\OC\Files\Storage\Temporary', '/foo'); + $this->assertInstanceOf('\OC\Files\Storage\Temporary', $mount->getStorage()); + } + + public function testWrapper() { + $test = $this; + $wrapper = function ($mountPoint, $storage) use (&$test) { + $test->assertEquals('/foo/', $mountPoint); + $test->assertInstanceOf('\OC\Files\Storage\Storage', $storage); + return new Wrapper(array('storage' => $storage)); + }; + + $loader = new Loader(); + $loader->addStorageWrapper($wrapper); + + $storage = $this->getMockBuilder('\OC\Files\Storage\Temporary') + ->disableOriginalConstructor() + ->getMock(); + $mount = new \OC\Files\Mount\Mount($storage, '/foo', array(), $loader); + $this->assertInstanceOf('\OC\Files\Storage\Wrapper', $mount->getStorage()); + } +} From 94ca576c9ac30b7e5878c108a5efe2cad16faa94 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 7 Jun 2013 17:50:10 +0200 Subject: [PATCH 019/247] use a getter for the storage loader to ensure the instance is created --- lib/files/filesystem.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php index a7b1da57c1..3d7d5abf8f 100644 --- a/lib/files/filesystem.php +++ b/lib/files/filesystem.php @@ -146,7 +146,14 @@ class Filesystem { /** * @var \OC\Files\Storage\Loader $loader */ - public static $loader; + private static $loader; + + public static function getLoader(){ + if (!self::$loader) { + self::$loader = new Loader(); + } + return self::$loader; + } /** * get the mountpoint of the storage object for a path @@ -245,7 +252,7 @@ class Filesystem { if (self::$defaultInstance) { return false; } - self::$loader = new Loader(); + self::getLoader(); self::$defaultInstance = new View($root); if (!self::$mounts) { @@ -400,7 +407,7 @@ class Filesystem { if (!self::$mounts) { \OC_Util::setupFS(); } - $mount = new Mount\Mount($class, $mountpoint, $arguments, self::$loader); + $mount = new Mount\Mount($class, $mountpoint, $arguments, self::getLoader()); self::$mounts->addMount($mount); } From 988b539dd7d1fef93219f44852208f49d928e67f Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Mon, 10 Jun 2013 17:27:21 +0200 Subject: [PATCH 020/247] Let's just use '/' as we do almost everywhere - this change fixes two failing unit tests --- lib/autoloader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/autoloader.php b/lib/autoloader.php index 9615838a9a..2117063909 100644 --- a/lib/autoloader.php +++ b/lib/autoloader.php @@ -96,8 +96,8 @@ class Autoloader { } else { foreach ($this->prefixPaths as $prefix => $dir) { if (0 === strpos($class, $prefix)) { - $path = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; - $path = str_replace('_', DIRECTORY_SEPARATOR, $path); + $path = str_replace('\\', '/', $class) . '.php'; + $path = str_replace('_', '/', $path); $paths[] = $dir . '/' . $path; } } From 073306eaa291435a84c86dfeefe8ec4597711b69 Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Mon, 10 Jun 2013 18:35:47 +0200 Subject: [PATCH 021/247] [Fixing Updater Unit Tests on Windows] using $internalPath within call to self::correctFolder() because $path inside of it is not processed properly due to directory separator on Windows. error logging has been added in case the given 4path is not found within self::correctFolder --- lib/files/cache/updater.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index 417a47f383..bf86596dfb 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -7,6 +7,7 @@ */ namespace OC\Files\Cache; +use OCP\Util; /** * listen to filesystem hooks and change the cache accordingly @@ -40,7 +41,7 @@ class Updater { $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); $cache->correctFolderSize($internalPath); - self::correctFolder($path, $storage->filemtime($internalPath)); + self::correctFolder($internalPath, $storage->filemtime($internalPath)); } } @@ -116,6 +117,8 @@ class Updater { if ($id !== -1) { $cache->update($id, array('mtime' => $time, 'etag' => $storage->getETag($internalPath))); self::correctFolder($parent, $time); + } else { + Util::writeLog('core', 'Path not in cache: '.$internalPath, Util::ERROR); } } } From c0b25a43752427df90c4a2e6564e8325d12ee4f4 Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Mon, 10 Jun 2013 18:47:36 +0200 Subject: [PATCH 022/247] [Fixing Updater Unit Tests on Windows] using $internalPath within call to self::correctFolder() because $path inside of it is not processed properly due to directory separator on Windows. --- lib/files/cache/updater.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index bf86596dfb..de74f1634e 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -60,7 +60,7 @@ class Updater { $cache = $storage->getCache($internalPath); $cache->remove($internalPath); $cache->correctFolderSize($internalPath); - self::correctFolder($path, time()); + self::correctFolder($internalPath, time()); } } @@ -85,8 +85,8 @@ class Updater { $cache->move($internalFrom, $internalTo); $cache->correctFolderSize($internalFrom); $cache->correctFolderSize($internalTo); - self::correctFolder($from, time()); - self::correctFolder($to, time()); + self::correctFolder($internalFrom, time()); + self::correctFolder($internalTo, time()); } else { self::deleteUpdate($from); self::writeUpdate($to); From 74a170f2a5e934bca7878fc229ad04cb9cf6e543 Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Mon, 10 Jun 2013 19:28:55 +0200 Subject: [PATCH 023/247] [Fixing unit tests in Windows] on windows open resources will be locked while the stream is open. closing the resource allows deletion below --- lib/files/view.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/files/view.php b/lib/files/view.php index e2fc8d965b..25071709fb 100644 --- a/lib/files/view.php +++ b/lib/files/view.php @@ -386,6 +386,12 @@ class View { $source = $this->fopen($path1 . $postFix1, 'r'); $target = $this->fopen($path2 . $postFix2, 'w'); list($count, $result) = \OC_Helper::streamCopy($source, $target); + + // close open handle - especially $source is necessary because unlink below will + // throw an exception on windows because the file is locked + fclose($source); + fclose($target); + if ($result !== false) { list($storage1, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); $storage1->unlink($internalPath1); From 2a3887a5d75b0b81344af04e02d5958cf438717d Mon Sep 17 00:00:00 2001 From: Michael Gapczynski Date: Wed, 12 Jun 2013 15:32:00 -0400 Subject: [PATCH 024/247] Add tests for the updater with mount points --- tests/lib/files/cache/updater.php | 128 +++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 3 deletions(-) diff --git a/tests/lib/files/cache/updater.php b/tests/lib/files/cache/updater.php index dad3cd7e65..874a35e2c6 100644 --- a/tests/lib/files/cache/updater.php +++ b/tests/lib/files/cache/updater.php @@ -56,7 +56,7 @@ class Updater extends \PHPUnit_Framework_TestCase { \OC_Hook::connect('OC_Filesystem', 'post_write', '\OC\Files\Cache\Updater', 'writeHook'); \OC_Hook::connect('OC_Filesystem', 'post_delete', '\OC\Files\Cache\Updater', 'deleteHook'); \OC_Hook::connect('OC_Filesystem', 'post_rename', '\OC\Files\Cache\Updater', 'renameHook'); - + \OC_Hook::connect('OC_Filesystem', 'post_touch', '\OC\Files\Cache\Updater', 'touchHook'); } public function tearDown() { @@ -96,6 +96,27 @@ class Updater extends \PHPUnit_Framework_TestCase { $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $mtime); } + public function testWriteWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + $folderCachedData = $this->cache->get('folder'); + $substorageCachedData = $cache2->get(''); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $this->assertTrue($cache2->inCache('foo.txt')); + $cachedData = $cache2->get('foo.txt'); + $this->assertEquals(3, $cachedData['size']); + $mtime = $cachedData['mtime']; + + $cachedData = $cache2->get(''); + $this->assertNotEquals($substorageCachedData['etag'], $cachedData['etag']); + $this->assertEquals($mtime, $cachedData['mtime']); + + $cachedData = $this->cache->get('folder'); + $this->assertNotEquals($folderCachedData['etag'], $cachedData['etag']); + $this->assertEquals($mtime, $cachedData['mtime']); + } + public function testDelete() { $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); @@ -103,7 +124,7 @@ class Updater extends \PHPUnit_Framework_TestCase { $this->assertEquals(3 * $textSize + $imageSize, $rootCachedData['size']); $this->assertTrue($this->cache->inCache('foo.txt')); - Filesystem::unlink('foo.txt', 'asd'); + Filesystem::unlink('foo.txt'); $this->assertFalse($this->cache->inCache('foo.txt')); $cachedData = $this->cache->get(''); $this->assertEquals(2 * $textSize + $imageSize, $cachedData['size']); @@ -123,6 +144,26 @@ class Updater extends \PHPUnit_Framework_TestCase { $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $cachedData['mtime']); } + public function testDeleteWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $this->assertTrue($cache2->inCache('foo.txt')); + $folderCachedData = $this->cache->get('folder'); + $substorageCachedData = $cache2->get(''); + Filesystem::unlink('folder/substorage/foo.txt'); + $this->assertFalse($cache2->inCache('foo.txt')); + + $cachedData = $cache2->get(''); + $this->assertNotEquals($substorageCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($substorageCachedData['mtime'], $cachedData['mtime']); + + $cachedData = $this->cache->get('folder'); + $this->assertNotEquals($folderCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($folderCachedData['mtime'], $cachedData['mtime']); + } + public function testRename() { $textSize = strlen("dummy file data\n"); $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo.png'); @@ -142,4 +183,85 @@ class Updater extends \PHPUnit_Framework_TestCase { $this->assertEquals(3 * $textSize + $imageSize, $cachedData['size']); $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); } -} + + public function testRenameWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $this->assertTrue($cache2->inCache('foo.txt')); + $folderCachedData = $this->cache->get('folder'); + $substorageCachedData = $cache2->get(''); + $fooCachedData = $cache2->get('foo.txt'); + Filesystem::rename('folder/substorage/foo.txt', 'folder/substorage/bar.txt'); + $this->assertFalse($cache2->inCache('foo.txt')); + $this->assertTrue($cache2->inCache('bar.txt')); + $cachedData = $cache2->get('bar.txt'); + $this->assertEquals($fooCachedData['fileid'], $cachedData['fileid']); + $mtime = $cachedData['mtime']; + + $cachedData = $cache2->get(''); + $this->assertNotEquals($substorageCachedData['etag'], $cachedData['etag']); + $this->assertEquals($mtime, $cachedData['mtime']); + + $cachedData = $this->cache->get('folder'); + $this->assertNotEquals($folderCachedData['etag'], $cachedData['etag']); + $this->assertEquals($mtime, $cachedData['mtime']); + } + + public function testTouch() { + $rootCachedData = $this->cache->get(''); + $fooCachedData = $this->cache->get('foo.txt'); + Filesystem::touch('foo.txt'); + $cachedData = $this->cache->get('foo.txt'); + $this->assertNotEquals($fooCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($fooCachedData['mtime'], $cachedData['mtime']); + + $cachedData = $this->cache->get(''); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); + $this->assertGreaterThanOrEqual($rootCachedData['mtime'], $cachedData['mtime']); + $rootCachedData = $cachedData; + + $time = 1371006070; + $barCachedData = $this->cache->get('folder/bar.txt'); + $folderCachedData = $this->cache->get('folder'); + Filesystem::touch('folder/bar.txt', $time); + $cachedData = $this->cache->get('folder/bar.txt'); + $this->assertNotEquals($barCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + + $cachedData = $this->cache->get('folder'); + $this->assertNotEquals($folderCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + + $cachedData = $this->cache->get(''); + $this->assertNotEquals($rootCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + } + + public function testTouchWithMountPoints() { + $storage2 = new \OC\Files\Storage\Temporary(array()); + $cache2 = $storage2->getCache(); + Filesystem::mount($storage2, array(), '/' . self::$user . '/files/folder/substorage'); + Filesystem::file_put_contents('folder/substorage/foo.txt', 'asd'); + $this->assertTrue($cache2->inCache('foo.txt')); + $folderCachedData = $this->cache->get('folder'); + $substorageCachedData = $cache2->get(''); + $fooCachedData = $cache2->get('foo.txt'); + $cachedData = $cache2->get('foo.txt'); + $time = 1371006070; + Filesystem::touch('folder/substorage/foo.txt', $time); + $cachedData = $cache2->get('foo.txt'); + $this->assertNotEquals($fooCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + + $cachedData = $cache2->get(''); + $this->assertNotEquals($substorageCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + + $cachedData = $this->cache->get('folder'); + $this->assertNotEquals($folderCachedData['etag'], $cachedData['etag']); + $this->assertEquals($time, $cachedData['mtime']); + } + +} \ No newline at end of file From 84a8aea410053686678f8efa6ba00ac63d31133e Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Thu, 13 Jun 2013 00:09:52 +0200 Subject: [PATCH 025/247] restore Updater functionality on non-Windows platforms --- lib/files/cache/updater.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index de74f1634e..6fee0d5845 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -41,7 +41,7 @@ class Updater { $scanner = $storage->getScanner($internalPath); $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); $cache->correctFolderSize($internalPath); - self::correctFolder($internalPath, $storage->filemtime($internalPath)); + self::correctFolder($path, $storage->filemtime($internalPath)); } } @@ -60,7 +60,7 @@ class Updater { $cache = $storage->getCache($internalPath); $cache->remove($internalPath); $cache->correctFolderSize($internalPath); - self::correctFolder($internalPath, time()); + self::correctFolder($path, time()); } } @@ -85,8 +85,8 @@ class Updater { $cache->move($internalFrom, $internalTo); $cache->correctFolderSize($internalFrom); $cache->correctFolderSize($internalTo); - self::correctFolder($internalFrom, time()); - self::correctFolder($internalTo, time()); + self::correctFolder($from, time()); + self::correctFolder($to, time()); } else { self::deleteUpdate($from); self::writeUpdate($to); From 87521f6c6e19bb2cff21ad0792604d24a6d6403e Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Fri, 14 Jun 2013 11:59:30 +0200 Subject: [PATCH 026/247] dirname('/test.txt') returns '\' on windows whereas on linux we get back '.' --- lib/files/cache/updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php index 6fee0d5845..87c33a313a 100644 --- a/lib/files/cache/updater.php +++ b/lib/files/cache/updater.php @@ -103,7 +103,7 @@ class Updater { static public function correctFolder($path, $time) { if ($path !== '' && $path !== '/') { $parent = dirname($path); - if ($parent === '.') { + if ($parent === '.' || $parent === '\\') { $parent = ''; } /** From f91b02e9b68e5478e93ec059a5439b493d1a1d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 14 Jun 2013 15:07:06 +0200 Subject: [PATCH 027/247] only escape glob pattern --- apps/files_trashbin/lib/trash.php | 12 +++++++----- apps/files_versions/lib/versions.php | 7 ++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index 2d1830a38f..e06e959f8e 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -193,10 +193,11 @@ class Trashbin { $rootView->rename($sharekeys, $user.'/files_trashbin/share-keys/' . $filename . '.d' . $timestamp); } else { // get local path to share-keys - $localShareKeysPath = $rootView->getLocalFile($sharekeys); + $localShareKeysPath = $rootView->getLocalFile($sharekeys); + $escapedLocalShareKeysPath = preg_replace('/(\*|\?|\[)/', '[$1]', $localShareKeysPath); // handle share-keys - $matches = glob(preg_quote($localShareKeysPath).'*.shareKey'); + $matches = glob($escapedLocalShareKeysPath.'*.shareKey'); foreach ($matches as $src) { // get source file parts $pathinfo = pathinfo($src); @@ -737,14 +738,15 @@ class Trashbin { */ private static function getVersionsFromTrash($filename, $timestamp) { $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files_trashbin/versions'); - $versionsName = $view->getLocalFile($filename); + $versionsName = $view->getLocalFile($filename).'.v'; + $escapedVersionsName = preg_replace('/(\*|\?|\[)/', '[$1]', $versionsName); $versions = array(); if ($timestamp ) { // fetch for old versions - $matches = glob( $versionsName.'.v*.d'.$timestamp ); + $matches = glob( $escapedVersionsName.'*.d'.$timestamp ); $offset = -strlen($timestamp)-2; } else { - $matches = glob( $versionsName.'.v*' ); + $matches = glob( $escapedVersionsName.'*' ); } foreach( $matches as $ma ) { diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index 4beb9e0fe5..757926cd77 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -241,11 +241,12 @@ class Storage { public static function getVersions($uid, $filename, $count = 0 ) { if( \OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true' ) { $versions_fileview = new \OC\Files\View('/' . $uid . '/files_versions'); - $versionsName = $versions_fileview->getLocalFile($filename); - + $versionsName = \OC_Filesystem::normalizePath($versions_fileview->getLocalFile($filename).'.v'); + $escapedVersionName = preg_replace('/(\*|\?|\[)/', '[$1]', $versionsName); + $versions = array(); // fetch for old versions - $matches = glob(preg_quote($versionsName).'.v*' ); + $matches = glob($escapedVersionName.'*'); if ( !$matches ) { return $versions; From c3371812a0dfcd706413e26327f9bbfde33e95c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Fri, 14 Jun 2013 15:14:23 +0200 Subject: [PATCH 028/247] fix indention --- apps/files_trashbin/lib/trash.php | 629 +++++++++++++++--------------- 1 file changed, 310 insertions(+), 319 deletions(-) diff --git a/apps/files_trashbin/lib/trash.php b/apps/files_trashbin/lib/trash.php index e06e959f8e..2e9c00039d 100644 --- a/apps/files_trashbin/lib/trash.php +++ b/apps/files_trashbin/lib/trash.php @@ -24,17 +24,18 @@ namespace OCA\Files_Trashbin; class Trashbin { // how long do we keep files in the trash bin if no other value is defined in the config file (unit: days) - const DEFAULT_RETENTION_OBLIGATION=180; + + const DEFAULT_RETENTION_OBLIGATION = 180; // unit: percentage; 50% of available disk space/quota - const DEFAULTMAXSIZE=50; + const DEFAULTMAXSIZE = 50; public static function getUidAndFilename($filename) { $uid = \OC\Files\Filesystem::getOwner($filename); \OC\Files\Filesystem::initMountPoints($uid); - if ( $uid != \OCP\User::getUser() ) { + if ($uid != \OCP\User::getUser()) { $info = \OC\Files\Filesystem::getFileInfo($filename); - $ownerView = new \OC\Files\View('/'.$uid.'/files'); + $ownerView = new \OC\Files\View('/' . $uid . '/files'); $filename = $ownerView->getPath($info['fileid']); } return array($uid, $filename); @@ -47,13 +48,13 @@ class Trashbin { */ public static function move2trash($file_path) { $user = \OCP\User::getUser(); - $view = new \OC\Files\View('/'. $user); + $view = new \OC\Files\View('/' . $user); if (!$view->is_dir('files_trashbin')) { $view->mkdir('files_trashbin'); $view->mkdir('files_trashbin/files'); $view->mkdir('files_trashbin/versions'); $view->mkdir('files_trashbin/keyfiles'); - $view->mkdir('files_trashbin/share-keys'); + $view->mkdir('files_trashbin/share-keys'); } $path_parts = pathinfo($file_path); @@ -61,101 +62,98 @@ class Trashbin { $filename = $path_parts['basename']; $location = $path_parts['dirname']; $timestamp = time(); - $mime = $view->getMimeType('files'.$file_path); + $mime = $view->getMimeType('files' . $file_path); - if ( $view->is_dir('files'.$file_path) ) { + if ($view->is_dir('files' . $file_path)) { $type = 'dir'; } else { $type = 'file'; } - + $trashbinSize = self::getTrashbinSize($user); - if ( $trashbinSize === false || $trashbinSize < 0 ) { - $trashbinSize = self::calculateSize(new \OC\Files\View('/'. $user.'/files_trashbin')); + if ($trashbinSize === false || $trashbinSize < 0) { + $trashbinSize = self::calculateSize(new \OC\Files\View('/' . $user . '/files_trashbin')); } // disable proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - $sizeOfAddedFiles = self::copy_recursive($file_path, 'files_trashbin/files/'.$filename.'.d'.$timestamp, $view); + $sizeOfAddedFiles = self::copy_recursive($file_path, 'files_trashbin/files/' . $filename . '.d' . $timestamp, $view); \OC_FileProxy::$enabled = $proxyStatus; - if ( $view->file_exists('files_trashbin/files/'.$filename.'.d'.$timestamp) ) { + if ($view->file_exists('files_trashbin/files/' . $filename . '.d' . $timestamp)) { $trashbinSize += $sizeOfAddedFiles; $query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`type`,`mime`,`user`) VALUES (?,?,?,?,?,?)"); $result = $query->execute(array($filename, $timestamp, $location, $type, $mime, $user)); - if ( !$result ) { // if file couldn't be added to the database than also don't store it in the trash bin. - $view->deleteAll('files_trashbin/files/'.$filename.'.d'.$timestamp); + if (!$result) { // if file couldn't be added to the database than also don't store it in the trash bin. + $view->deleteAll('files_trashbin/files/' . $filename . '.d' . $timestamp); \OC_Log::write('files_trashbin', 'trash bin database couldn\'t be updated', \OC_log::ERROR); return; } - \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', - array('filePath' => \OC\Files\Filesystem::normalizePath($file_path), - 'trashPath' => \OC\Files\Filesystem::normalizePath($filename.'.d'.$timestamp))); + \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => \OC\Files\Filesystem::normalizePath($file_path), + 'trashPath' => \OC\Files\Filesystem::normalizePath($filename . '.d' . $timestamp))); $trashbinSize += self::retainVersions($view, $file_path, $filename, $timestamp); $trashbinSize += self::retainEncryptionKeys($view, $file_path, $filename, $timestamp); - } else { - \OC_Log::write('files_trashbin', 'Couldn\'t move '.$file_path.' to the trash bin', \OC_log::ERROR); + \OC_Log::write('files_trashbin', 'Couldn\'t move ' . $file_path . ' to the trash bin', \OC_log::ERROR); } $trashbinSize -= self::expire($trashbinSize); - - self::setTrashbinSize($user, $trashbinSize); + self::setTrashbinSize($user, $trashbinSize); } - /** - * Move file versions to trash so that they can be restored later - * - * @param \OC\Files\View $view - * @param $file_path path to original file - * @param $filename of deleted file - * @param $timestamp when the file was deleted - * - * @return size of stored versions - */ + /** + * Move file versions to trash so that they can be restored later + * + * @param \OC\Files\View $view + * @param $file_path path to original file + * @param $filename of deleted file + * @param $timestamp when the file was deleted + * + * @return size of stored versions + */ private static function retainVersions($view, $file_path, $filename, $timestamp) { $size = 0; - if (\OCP\App::isEnabled('files_versions')) { + if (\OCP\App::isEnabled('files_versions')) { - // disable proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - $user = \OCP\User::getUser(); + $user = \OCP\User::getUser(); $rootView = new \OC\Files\View('/'); list($owner, $ownerPath) = self::getUidAndFilename($file_path); - if ($rootView->is_dir($owner.'/files_versions/' . $ownerPath)) { - $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); - $rootView->rename($owner.'/files_versions/' . $ownerPath, $user.'/files_trashbin/versions/' . $filename . '.d' . $timestamp); + if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) { + $size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath)); + $rootView->rename($owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp); } else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) { - foreach ($versions as $v) { - $size += $rootView->filesize($owner.'/files_versions' . $v['path'] . '.v' . $v['version']); - $rootView->rename($owner.'/files_versions' . $v['path'] . '.v' . $v['version'], $user.'/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); + foreach ($versions as $v) { + $size += $rootView->filesize($owner . '/files_versions' . $v['path'] . '.v' . $v['version']); + $rootView->rename($owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp); } } - // enable proxy - \OC_FileProxy::$enabled = $proxyStatus; + // enable proxy + \OC_FileProxy::$enabled = $proxyStatus; } return $size; } - /** - * Move encryption keys to trash so that they can be restored later - * - * @param \OC\Files\View $view - * @param $file_path path to original file - * @param $filename of deleted file - * @param $timestamp when the file was deleted - * - * @return size of encryption keys - */ + /** + * Move encryption keys to trash so that they can be restored later + * + * @param \OC\Files\View $view + * @param $file_path path to original file + * @param $filename of deleted file + * @param $timestamp when the file was deleted + * + * @return size of encryption keys + */ private static function retainEncryptionKeys($view, $file_path, $filename, $timestamp) { $size = 0; @@ -167,66 +165,65 @@ class Trashbin { list($owner, $ownerPath) = self::getUidAndFilename($file_path); - // disable proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - // retain key files - $keyfile = \OC\Files\Filesystem::normalizePath($owner.'/files_encryption/keyfiles/' . $ownerPath); + // retain key files + $keyfile = \OC\Files\Filesystem::normalizePath($owner . '/files_encryption/keyfiles/' . $ownerPath); - if ($rootView->is_dir($keyfile) || $rootView->file_exists($keyfile . '.key')) { - // move keyfiles - if ($rootView->is_dir($keyfile)) { - $size += self::calculateSize(new \OC\Files\View($keyfile)); - $rootView->rename($keyfile, $user.'/files_trashbin/keyfiles/' . $filename . '.d' . $timestamp); + if ($rootView->is_dir($keyfile) || $rootView->file_exists($keyfile . '.key')) { + // move keyfiles + if ($rootView->is_dir($keyfile)) { + $size += self::calculateSize(new \OC\Files\View($keyfile)); + $rootView->rename($keyfile, $user . '/files_trashbin/keyfiles/' . $filename . '.d' . $timestamp); } else { $size += $rootView->filesize($keyfile . '.key'); - $rootView->rename($keyfile . '.key', $user.'/files_trashbin/keyfiles/' . $filename . '.key.d' . $timestamp); + $rootView->rename($keyfile . '.key', $user . '/files_trashbin/keyfiles/' . $filename . '.key.d' . $timestamp); } } - // retain share keys - $sharekeys = \OC\Files\Filesystem::normalizePath($owner.'/files_encryption/share-keys/' . $ownerPath); + // retain share keys + $sharekeys = \OC\Files\Filesystem::normalizePath($owner . '/files_encryption/share-keys/' . $ownerPath); if ($rootView->is_dir($sharekeys)) { $size += self::calculateSize(new \OC\Files\View($sharekeys)); - $rootView->rename($sharekeys, $user.'/files_trashbin/share-keys/' . $filename . '.d' . $timestamp); + $rootView->rename($sharekeys, $user . '/files_trashbin/share-keys/' . $filename . '.d' . $timestamp); } else { - // get local path to share-keys + // get local path to share-keys $localShareKeysPath = $rootView->getLocalFile($sharekeys); $escapedLocalShareKeysPath = preg_replace('/(\*|\?|\[)/', '[$1]', $localShareKeysPath); - // handle share-keys - $matches = glob($escapedLocalShareKeysPath.'*.shareKey'); - foreach ($matches as $src) { - // get source file parts - $pathinfo = pathinfo($src); + // handle share-keys + $matches = glob($escapedLocalShareKeysPath . '*.shareKey'); + foreach ($matches as $src) { + // get source file parts + $pathinfo = pathinfo($src); - // we only want to keep the owners key so we can access the private key - $ownerShareKey = $filename . '.' . $user. '.shareKey'; + // we only want to keep the owners key so we can access the private key + $ownerShareKey = $filename . '.' . $user . '.shareKey'; - // if we found the share-key for the owner, we need to move it to files_trashbin - if($pathinfo['basename'] == $ownerShareKey) { + // if we found the share-key for the owner, we need to move it to files_trashbin + if ($pathinfo['basename'] == $ownerShareKey) { - // calculate size - $size += $rootView->filesize($sharekeys. '.' . $user. '.shareKey'); + // calculate size + $size += $rootView->filesize($sharekeys . '.' . $user . '.shareKey'); - // move file - $rootView->rename($sharekeys. '.' . $user. '.shareKey', $user.'/files_trashbin/share-keys/' . $ownerShareKey . '.d' . $timestamp); - } else { + // move file + $rootView->rename($sharekeys . '.' . $user . '.shareKey', $user . '/files_trashbin/share-keys/' . $ownerShareKey . '.d' . $timestamp); + } else { - // calculate size - $size += filesize($src); - - // don't keep other share-keys - unlink($src); - } - } + // calculate size + $size += filesize($src); - } + // don't keep other share-keys + unlink($src); + } + } + } - // enable proxy - \OC_FileProxy::$enabled = $proxyStatus; + // enable proxy + \OC_FileProxy::$enabled = $proxyStatus; } return $size; } @@ -236,95 +233,94 @@ class Trashbin { * @param $file path to the deleted file * @param $filename name of the file * @param $timestamp time when the file was deleted - * - * @return bool - */ + * + * @return bool + */ public static function restore($file, $filename, $timestamp) { - $user = \OCP\User::getUser(); - $view = new \OC\Files\View('/'.$user); - + $user = \OCP\User::getUser(); + $view = new \OC\Files\View('/' . $user); + $trashbinSize = self::getTrashbinSize($user); - if ( $trashbinSize === false || $trashbinSize < 0 ) { - $trashbinSize = self::calculateSize(new \OC\Files\View('/'. $user.'/files_trashbin')); + if ($trashbinSize === false || $trashbinSize < 0) { + $trashbinSize = self::calculateSize(new \OC\Files\View('/' . $user . '/files_trashbin')); } - if ( $timestamp ) { + if ($timestamp) { $query = \OC_DB::prepare('SELECT `location`,`type` FROM `*PREFIX*files_trash`' - .' WHERE `user`=? AND `id`=? AND `timestamp`=?'); - $result = $query->execute(array($user,$filename,$timestamp))->fetchAll(); - if ( count($result) != 1 ) { + . ' WHERE `user`=? AND `id`=? AND `timestamp`=?'); + $result = $query->execute(array($user, $filename, $timestamp))->fetchAll(); + if (count($result) != 1) { \OC_Log::write('files_trashbin', 'trash bin database inconsistent!', \OC_Log::ERROR); return false; } // if location no longer exists, restore file in the root directory $location = $result[0]['location']; - if ( $result[0]['location'] != '/' && - (!$view->is_dir('files'.$result[0]['location']) || - !$view->isUpdatable('files'.$result[0]['location'])) ) { + if ($result[0]['location'] != '/' && + (!$view->is_dir('files' . $result[0]['location']) || + !$view->isUpdatable('files' . $result[0]['location']))) { $location = ''; } } else { $path_parts = pathinfo($file); $result[] = array( - 'location' => $path_parts['dirname'], - 'type' => $view->is_dir('/files_trashbin/files/'.$file) ? 'dir' : 'files', - ); + 'location' => $path_parts['dirname'], + 'type' => $view->is_dir('/files_trashbin/files/' . $file) ? 'dir' : 'files', + ); $location = ''; } - - $source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/'.$file); - $target = \OC\Files\Filesystem::normalizePath('files/'.$location.'/'.$filename); + + $source = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $file); + $target = \OC\Files\Filesystem::normalizePath('files/' . $location . '/' . $filename); // we need a extension in case a file/dir with the same name already exists $ext = self::getUniqueExtension($location, $filename, $view); $mtime = $view->filemtime($source); - // disable proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - // restore file - $restoreResult = $view->rename($source, $target.$ext); + // restore file + $restoreResult = $view->rename($source, $target . $ext); - // handle the restore result - if( $restoreResult ) { + // handle the restore result + if ($restoreResult) { $fakeRoot = $view->getRoot(); - $view->chroot('/'.$user.'/files'); - $view->touch('/'.$location.'/'.$filename.$ext, $mtime); + $view->chroot('/' . $user . '/files'); + $view->touch('/' . $location . '/' . $filename . $ext, $mtime); $view->chroot($fakeRoot); - \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', - array('filePath' => \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext), - 'trashPath' => \OC\Files\Filesystem::normalizePath($file))); - if ($view->is_dir($target.$ext)) { - $trashbinSize -= self::calculateSize(new \OC\Files\View('/'.$user.'/'.$target.$ext)); + \OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', array('filePath' => \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $filename . $ext), + 'trashPath' => \OC\Files\Filesystem::normalizePath($file))); + if ($view->is_dir($target . $ext)) { + $trashbinSize -= self::calculateSize(new \OC\Files\View('/' . $user . '/' . $target . $ext)); } else { - $trashbinSize -= $view->filesize($target.$ext); + $trashbinSize -= $view->filesize($target . $ext); } - $trashbinSize -= self::restoreVersions($view, $file, $filename, $ext, $location, $timestamp); + $trashbinSize -= self::restoreVersions($view, $file, $filename, $ext, $location, $timestamp); $trashbinSize -= self::restoreEncryptionKeys($view, $file, $filename, $ext, $location, $timestamp); - if ( $timestamp ) { + if ($timestamp) { $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?'); - $query->execute(array($user,$filename,$timestamp)); + $query->execute(array($user, $filename, $timestamp)); } self::setTrashbinSize($user, $trashbinSize); - // enable proxy - \OC_FileProxy::$enabled = $proxyStatus; + // enable proxy + \OC_FileProxy::$enabled = $proxyStatus; return true; } - // enable proxy - \OC_FileProxy::$enabled = $proxyStatus; + // enable proxy + \OC_FileProxy::$enabled = $proxyStatus; return false; } - /** + /** * @brief restore versions from trash bin * * @param \OC\Files\View $view file view @@ -333,20 +329,20 @@ class Trashbin { * @param $ext file extension in case a file with the same $filename already exists * @param $location location if file * @param $timestamp deleteion time - * + * * @return size of restored versions */ private static function restoreVersions($view, $file, $filename, $ext, $location, $timestamp) { $size = 0; if (\OCP\App::isEnabled('files_versions')) { - // disable proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - $user = \OCP\User::getUser(); + $user = \OCP\User::getUser(); $rootView = new \OC\Files\View('/'); - $target = \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext); + $target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $filename . $ext); list($owner, $ownerPath) = self::getUidAndFilename($target); @@ -356,190 +352,188 @@ class Trashbin { $versionedFile = $file; } - if ($view->is_dir('/files_trashbin/versions/'.$file)) { + if ($view->is_dir('/files_trashbin/versions/' . $file)) { $size += self::calculateSize(new \OC\Files\View('/' . $user . '/' . 'files_trashbin/versions/' . $file)); - $rootView->rename(\OC\Files\Filesystem::normalizePath($user.'/files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath($owner.'/files_versions/' . $ownerPath)); + $rootView->rename(\OC\Files\Filesystem::normalizePath($user . '/files_trashbin/versions/' . $file), \OC\Files\Filesystem::normalizePath($owner . '/files_versions/' . $ownerPath)); } else if ($versions = self::getVersionsFromTrash($versionedFile, $timestamp)) { - foreach ($versions as $v) { - if ($timestamp) { - $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp); - $rootView->rename($user.'/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner.'/files_versions/' . $ownerPath . '.v' . $v); + foreach ($versions as $v) { + if ($timestamp) { + $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp); + $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v . '.d' . $timestamp, $owner . '/files_versions/' . $ownerPath . '.v' . $v); } else { - $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v); - $rootView->rename($user.'/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner.'/files_versions/' . $ownerPath . '.v' . $v); + $size += $view->filesize('files_trashbin/versions/' . $versionedFile . '.v' . $v); + $rootView->rename($user . '/files_trashbin/versions/' . $versionedFile . '.v' . $v, $owner . '/files_versions/' . $ownerPath . '.v' . $v); } } } - // enable proxy - \OC_FileProxy::$enabled = $proxyStatus; + // enable proxy + \OC_FileProxy::$enabled = $proxyStatus; } return $size; } - - /** - * @brief restore encryption keys from trash bin - * - * @param \OC\Files\View $view - * @param $file complete path to file - * @param $filename name of file - * @param $ext file extension in case a file with the same $filename already exists - * @param $location location of file - * @param $timestamp deleteion time - * - * @return size of restored encrypted file - */ - private static function restoreEncryptionKeys($view, $file, $filename, $ext, $location, $timestamp) { + /** + * @brief restore encryption keys from trash bin + * + * @param \OC\Files\View $view + * @param $file complete path to file + * @param $filename name of file + * @param $ext file extension in case a file with the same $filename already exists + * @param $location location of file + * @param $timestamp deleteion time + * + * @return size of restored encrypted file + */ + private static function restoreEncryptionKeys($view, $file, $filename, $ext, $location, $timestamp) { // Take care of encryption keys TODO! Get '.key' in file between file name and delete date (also for permanent delete!) $size = 0; if (\OCP\App::isEnabled('files_encryption')) { $user = \OCP\User::getUser(); $rootView = new \OC\Files\View('/'); - $target = \OC\Files\Filesystem::normalizePath('/'.$location.'/'.$filename.$ext); + $target = \OC\Files\Filesystem::normalizePath('/' . $location . '/' . $filename . $ext); list($owner, $ownerPath) = self::getUidAndFilename($target); - $path_parts = pathinfo($file); - $source_location = $path_parts['dirname']; - - if ($view->is_dir('/files_trashbin/keyfiles/'.$file)) { - if($source_location != '.') { - $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $source_location . '/' . $filename); - $sharekey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $source_location . '/' . $filename); - } else { - $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $filename); - $sharekey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $filename); - } - } else { - $keyfile = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/keyfiles/' . $source_location . '/' . $filename . '.key'); - } - - if ($timestamp) { - $keyfile .= '.d' . $timestamp; - } - - // disable proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - if ($rootView->file_exists($keyfile)) { - // handle directory - if ($rootView->is_dir($keyfile)) { - - // handle keyfiles - $size += self::calculateSize(new \OC\Files\View($keyfile)); - $rootView->rename($keyfile, $owner.'/files_encryption/keyfiles/' . $ownerPath); - - // handle share-keys - if ($timestamp) { - $sharekey .= '.d' . $timestamp; - } - $size += self::calculateSize(new \OC\Files\View($sharekey)); - $rootView->rename($sharekey, $owner.'/files_encryption/share-keys/' . $ownerPath); + $path_parts = pathinfo($file); + $source_location = $path_parts['dirname']; + if ($view->is_dir('/files_trashbin/keyfiles/' . $file)) { + if ($source_location != '.') { + $keyfile = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/keyfiles/' . $source_location . '/' . $filename); + $sharekey = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/share-keys/' . $source_location . '/' . $filename); } else { - // handle keyfiles + $keyfile = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/keyfiles/' . $filename); + $sharekey = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/share-keys/' . $filename); + } + } else { + $keyfile = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/keyfiles/' . $source_location . '/' . $filename . '.key'); + } + + if ($timestamp) { + $keyfile .= '.d' . $timestamp; + } + + // disable proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + if ($rootView->file_exists($keyfile)) { + // handle directory + if ($rootView->is_dir($keyfile)) { + + // handle keyfiles + $size += self::calculateSize(new \OC\Files\View($keyfile)); + $rootView->rename($keyfile, $owner . '/files_encryption/keyfiles/' . $ownerPath); + + // handle share-keys + if ($timestamp) { + $sharekey .= '.d' . $timestamp; + } + $size += self::calculateSize(new \OC\Files\View($sharekey)); + $rootView->rename($sharekey, $owner . '/files_encryption/share-keys/' . $ownerPath); + } else { + // handle keyfiles $size += $rootView->filesize($keyfile); - $rootView->rename($keyfile, $owner.'/files_encryption/keyfiles/' . $ownerPath . '.key'); + $rootView->rename($keyfile, $owner . '/files_encryption/keyfiles/' . $ownerPath . '.key'); - // handle share-keys - $ownerShareKey = \OC\Files\Filesystem::normalizePath($user.'/files_trashbin/share-keys/' . $source_location . '/' . $filename . '.' . $user. '.shareKey'); - if ($timestamp) { - $ownerShareKey .= '.d' . $timestamp; - } + // handle share-keys + $ownerShareKey = \OC\Files\Filesystem::normalizePath($user . '/files_trashbin/share-keys/' . $source_location . '/' . $filename . '.' . $user . '.shareKey'); + if ($timestamp) { + $ownerShareKey .= '.d' . $timestamp; + } - $size += $rootView->filesize($ownerShareKey); + $size += $rootView->filesize($ownerShareKey); - // move only owners key - $rootView->rename($ownerShareKey, $owner.'/files_encryption/share-keys/' . $ownerPath . '.' . $user. '.shareKey'); + // move only owners key + $rootView->rename($ownerShareKey, $owner . '/files_encryption/share-keys/' . $ownerPath . '.' . $user . '.shareKey'); - // try to re-share if file is shared - $filesystemView = new \OC_FilesystemView('/'); - $session = new \OCA\Encryption\Session($filesystemView); - $util = new \OCA\Encryption\Util($filesystemView, $user); + // try to re-share if file is shared + $filesystemView = new \OC_FilesystemView('/'); + $session = new \OCA\Encryption\Session($filesystemView); + $util = new \OCA\Encryption\Util($filesystemView, $user); - // fix the file size - $absolutePath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files/'. $ownerPath); - $util->fixFileSize($absolutePath); + // fix the file size + $absolutePath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files/' . $ownerPath); + $util->fixFileSize($absolutePath); - // get current sharing state - $sharingEnabled = \OCP\Share::isEnabled(); + // get current sharing state + $sharingEnabled = \OCP\Share::isEnabled(); - // get the final filename - $target = \OC\Files\Filesystem::normalizePath($location.'/'.$filename); + // get the final filename + $target = \OC\Files\Filesystem::normalizePath($location . '/' . $filename); - // get users sharing this file - $usersSharing = $util->getSharingUsersArray($sharingEnabled, $target.$ext, $user); + // get users sharing this file + $usersSharing = $util->getSharingUsersArray($sharingEnabled, $target . $ext, $user); - // Attempt to set shareKey - $util->setSharedFileKeyfiles($session, $usersSharing, $target.$ext); + // Attempt to set shareKey + $util->setSharedFileKeyfiles($session, $usersSharing, $target . $ext); } } - // enable proxy - \OC_FileProxy::$enabled = $proxyStatus; + // enable proxy + \OC_FileProxy::$enabled = $proxyStatus; } return $size; } /** * @brief delete file from trash bin permanently - * + * * @param $filename path to the file * @param $timestamp of deletion time - * + * * @return size of deleted files */ - public static function delete($filename, $timestamp=null) { + public static function delete($filename, $timestamp = null) { $user = \OCP\User::getUser(); - $view = new \OC\Files\View('/'.$user); + $view = new \OC\Files\View('/' . $user); $size = 0; - + $trashbinSize = self::getTrashbinSize($user); - if ( $trashbinSize === false || $trashbinSize < 0 ) { - $trashbinSize = self::calculateSize(new \OC\Files\View('/'. $user.'/files_trashbin')); + if ($trashbinSize === false || $trashbinSize < 0) { + $trashbinSize = self::calculateSize(new \OC\Files\View('/' . $user . '/files_trashbin')); } - if ( $timestamp ) { + if ($timestamp) { $query = \OC_DB::prepare('DELETE FROM `*PREFIX*files_trash` WHERE `user`=? AND `id`=? AND `timestamp`=?'); - $query->execute(array($user,$filename,$timestamp)); - $file = $filename.'.d'.$timestamp; + $query->execute(array($user, $filename, $timestamp)); + $file = $filename . '.d' . $timestamp; } else { $file = $filename; } $size += self::deleteVersions($view, $file, $filename, $timestamp); $size += self::deleteEncryptionKeys($view, $file, $filename, $timestamp); - - if ($view->is_dir('/files_trashbin/files/'.$file)) { - $size += self::calculateSize(new \OC\Files\View('/'.$user.'/files_trashbin/files/'.$file)); + + if ($view->is_dir('/files_trashbin/files/' . $file)) { + $size += self::calculateSize(new \OC\Files\View('/' . $user . '/files_trashbin/files/' . $file)); } else { - $size += $view->filesize('/files_trashbin/files/'.$file); + $size += $view->filesize('/files_trashbin/files/' . $file); } - $view->unlink('/files_trashbin/files/'.$file); + $view->unlink('/files_trashbin/files/' . $file); $trashbinSize -= $size; self::setTrashbinSize($user, $trashbinSize); - + return $size; } private static function deleteVersions($view, $file, $filename, $timestamp) { $size = 0; - if ( \OCP\App::isEnabled('files_versions') ) { + if (\OCP\App::isEnabled('files_versions')) { $user = \OCP\User::getUser(); - if ($view->is_dir('files_trashbin/versions/'.$file)) { - $size += self::calculateSize(new \OC\Files\view('/'.$user.'/files_trashbin/versions/'.$file)); - $view->unlink('files_trashbin/versions/'.$file); - } else if ( $versions = self::getVersionsFromTrash($filename, $timestamp) ) { + if ($view->is_dir('files_trashbin/versions/' . $file)) { + $size += self::calculateSize(new \OC\Files\view('/' . $user . '/files_trashbin/versions/' . $file)); + $view->unlink('files_trashbin/versions/' . $file); + } else if ($versions = self::getVersionsFromTrash($filename, $timestamp)) { foreach ($versions as $v) { - if ($timestamp ) { - $size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp); - $view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v.'.d'.$timestamp); + if ($timestamp) { + $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp); + $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v . '.d' . $timestamp); } else { - $size += $view->filesize('/files_trashbin/versions/'.$filename.'.v'.$v); - $view->unlink('/files_trashbin/versions/'.$filename.'.v'.$v); + $size += $view->filesize('/files_trashbin/versions/' . $filename . '.v' . $v); + $view->unlink('/files_trashbin/versions/' . $filename . '.v' . $v); } } } @@ -554,10 +548,10 @@ class Trashbin { if ($view->is_dir('/files_trashbin/files/' . $file)) { $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename); - $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename); + $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename); } else { $keyfile = \OC\Files\Filesystem::normalizePath('files_trashbin/keyfiles/' . $filename . '.key'); - $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename . '.' . $user . '.shareKey'); + $sharekeys = \OC\Files\Filesystem::normalizePath('files_trashbin/share-keys/' . $filename . '.' . $user . '.shareKey'); } if ($timestamp) { $keyfile .= '.d' . $timestamp; @@ -584,17 +578,17 @@ class Trashbin { * @param $timestamp of deletion time * @return true if file exists, otherwise false */ - public static function file_exists($filename, $timestamp=null) { + public static function file_exists($filename, $timestamp = null) { $user = \OCP\User::getUser(); - $view = new \OC\Files\View('/'.$user); + $view = new \OC\Files\View('/' . $user); if ($timestamp) { - $filename = $filename.'.d'.$timestamp; + $filename = $filename . '.d' . $timestamp; } else { $filename = $filename; } - $target = \OC\Files\Filesystem::normalizePath('files_trashbin/files/'.$filename); + $target = \OC\Files\Filesystem::normalizePath('files_trashbin/files/' . $filename); return $view->file_exists($target); } @@ -624,11 +618,11 @@ class Trashbin { $softQuota = true; $user = \OCP\User::getUser(); $quota = \OC_Preferences::getValue($user, 'files', 'quota'); - $view = new \OC\Files\View('/'.$user); - if ( $quota === null || $quota === 'default') { + $view = new \OC\Files\View('/' . $user); + if ($quota === null || $quota === 'default') { $quota = \OC_Appconfig::getValue('files', 'default_quota'); } - if ( $quota === null || $quota === 'none' ) { + if ($quota === null || $quota === 'none') { $quota = \OC\Files\Filesystem::free_space('/'); $softQuota = false; } else { @@ -639,11 +633,11 @@ class Trashbin { // subtract size of files and current trash bin size from quota if ($softQuota) { $rootInfo = $view->getFileInfo('/files/'); - $free = $quota-$rootInfo['size']; // remaining free space for user - if ( $free > 0 ) { + $free = $quota - $rootInfo['size']; // remaining free space for user + if ($free > 0) { $availableSpace = ($free * self::DEFAULTMAXSIZE / 100) - $trashbinSize; // how much space can be used for versions } else { - $availableSpace = $free-$trashbinSize; + $availableSpace = $free - $trashbinSize; } } else { $availableSpace = $quota; @@ -659,43 +653,40 @@ class Trashbin { private static function expire($trashbinSize) { $user = \OCP\User::getUser(); - $view = new \OC\Files\View('/'.$user); + $view = new \OC\Files\View('/' . $user); $availableSpace = self::calculateFreeSpace($trashbinSize); $size = 0; $query = \OC_DB::prepare('SELECT `location`,`type`,`id`,`timestamp` FROM `*PREFIX*files_trash` WHERE `user`=?'); $result = $query->execute(array($user))->fetchAll(); - $retention_obligation = \OC_Config::getValue('trashbin_retention_obligation', - self::DEFAULT_RETENTION_OBLIGATION); + $retention_obligation = \OC_Config::getValue('trashbin_retention_obligation', self::DEFAULT_RETENTION_OBLIGATION); $limit = time() - ($retention_obligation * 86400); - foreach ( $result as $r ) { + foreach ($result as $r) { $timestamp = $r['timestamp']; $filename = $r['id']; - if ( $r['timestamp'] < $limit ) { + if ($r['timestamp'] < $limit) { $size += self::delete($filename, $timestamp); - \OC_Log::write('files_trashbin', 'remove "'.$filename.'" fom trash bin because it is older than '.$retention_obligation, \OC_log::INFO); + \OC_Log::write('files_trashbin', 'remove "' . $filename . '" fom trash bin because it is older than ' . $retention_obligation, \OC_log::INFO); } } $availableSpace = $availableSpace + $size; // if size limit for trash bin reached, delete oldest files in trash bin if ($availableSpace < 0) { $query = \OC_DB::prepare('SELECT `location`,`type`,`id`,`timestamp` FROM `*PREFIX*files_trash`' - .' WHERE `user`=? ORDER BY `timestamp` ASC'); + . ' WHERE `user`=? ORDER BY `timestamp` ASC'); $result = $query->execute(array($user))->fetchAll(); $length = count($result); $i = 0; - while ( $i < $length && $availableSpace < 0 ) { + while ($i < $length && $availableSpace < 0) { $tmp = self::delete($result[$i]['id'], $result[$i]['timestamp']); - \OC_Log::write('files_trashbin', 'remove "'.$result[$i]['id'].'" ('.$tmp.'B) to meet the limit of trash bin size (50% of available quota)', \OC_log::INFO); + \OC_Log::write('files_trashbin', 'remove "' . $result[$i]['id'] . '" (' . $tmp . 'B) to meet the limit of trash bin size (50% of available quota)', \OC_log::INFO); $availableSpace += $tmp; $size += $tmp; $i++; } - - } return $size; @@ -708,25 +699,25 @@ class Trashbin { * @param $destination destination path relative to the users root directoy * @param $view file view for the users root directory */ - private static function copy_recursive( $source, $destination, $view ) { + private static function copy_recursive($source, $destination, $view) { $size = 0; - if ( $view->is_dir( 'files'.$source ) ) { - $view->mkdir( $destination ); - $view->touch($destination, $view->filemtime('files'.$source)); - foreach ( \OC_Files::getDirectoryContent($source) as $i ) { - $pathDir = $source.'/'.$i['name']; - if ( $view->is_dir('files'.$pathDir) ) { - $size += self::copy_recursive($pathDir, $destination.'/'.$i['name'], $view); + if ($view->is_dir('files' . $source)) { + $view->mkdir($destination); + $view->touch($destination, $view->filemtime('files' . $source)); + foreach (\OC_Files::getDirectoryContent($source) as $i) { + $pathDir = $source . '/' . $i['name']; + if ($view->is_dir('files' . $pathDir)) { + $size += self::copy_recursive($pathDir, $destination . '/' . $i['name'], $view); } else { - $size += $view->filesize('files'.$pathDir); - $view->copy( 'files'.$pathDir, $destination . '/' . $i['name'] ); - $view->touch($destination . '/' . $i['name'], $view->filemtime('files'.$pathDir)); + $size += $view->filesize('files' . $pathDir); + $view->copy('files' . $pathDir, $destination . '/' . $i['name']); + $view->touch($destination . '/' . $i['name'], $view->filemtime('files' . $pathDir)); } } } else { - $size += $view->filesize('files'.$source); - $view->copy( 'files'.$source, $destination ); - $view->touch($destination, $view->filemtime('files'.$source)); + $size += $view->filesize('files' . $source); + $view->copy('files' . $source, $destination); + $view->touch($destination, $view->filemtime('files' . $source)); } return $size; } @@ -737,25 +728,25 @@ class Trashbin { * @param $timestamp timestamp when the file was deleted */ private static function getVersionsFromTrash($filename, $timestamp) { - $view = new \OC\Files\View('/'.\OCP\User::getUser().'/files_trashbin/versions'); - $versionsName = $view->getLocalFile($filename).'.v'; + $view = new \OC\Files\View('/' . \OCP\User::getUser() . '/files_trashbin/versions'); + $versionsName = $view->getLocalFile($filename) . '.v'; $escapedVersionsName = preg_replace('/(\*|\?|\[)/', '[$1]', $versionsName); $versions = array(); - if ($timestamp ) { + if ($timestamp) { // fetch for old versions - $matches = glob( $escapedVersionsName.'*.d'.$timestamp ); - $offset = -strlen($timestamp)-2; + $matches = glob($escapedVersionsName . '*.d' . $timestamp); + $offset = -strlen($timestamp) - 2; } else { - $matches = glob( $escapedVersionsName.'*' ); + $matches = glob($escapedVersionsName . '*'); } - foreach( $matches as $ma ) { - if ( $timestamp ) { - $parts = explode( '.v', substr($ma, 0, $offset) ); - $versions[] = ( end( $parts ) ); + foreach ($matches as $ma) { + if ($timestamp) { + $parts = explode('.v', substr($ma, 0, $offset)); + $versions[] = ( end($parts) ); } else { - $parts = explode( '.v', $ma ); - $versions[] = ( end( $parts ) ); + $parts = explode('.v', $ma); + $versions[] = ( end($parts) ); } } return $versions; @@ -770,12 +761,12 @@ class Trashbin { */ private static function getUniqueExtension($location, $filename, $view) { $ext = ''; - if ( $view->file_exists('files'.$location.'/'.$filename) ) { + if ($view->file_exists('files' . $location . '/' . $filename)) { $tmpext = '.restored'; $ext = $tmpext; $i = 1; - while ( $view->file_exists('files'.$location.'/'.$filename.$ext) ) { - $ext = $tmpext.$i; + while ($view->file_exists('files' . $location . '/' . $filename . $ext)) { + $ext = $tmpext . $i; $i++; } } @@ -788,17 +779,16 @@ class Trashbin { * @return size of the folder */ private static function calculateSize($view) { - $root = \OCP\Config::getSystemValue('datadirectory').$view->getAbsolutePath(''); + $root = \OCP\Config::getSystemValue('datadirectory') . $view->getAbsolutePath(''); if (!file_exists($root)) { return 0; } - $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), - \RecursiveIteratorIterator::CHILD_FIRST); + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($root), \RecursiveIteratorIterator::CHILD_FIRST); $size = 0; foreach ($iterator as $path) { - $relpath = substr($path, strlen($root)-1); - if ( !$view->is_dir($relpath) ) { + $relpath = substr($path, strlen($root) - 1); + if (!$view->is_dir($relpath)) { $size += $view->filesize($relpath); } } @@ -820,7 +810,7 @@ class Trashbin { } return false; } - + /** * write to the database how much space is in use for the trash bin * @@ -828,9 +818,9 @@ class Trashbin { * @param $size size of the trash bin */ private static function setTrashbinSize($user, $size) { - if ( self::getTrashbinSize($user) === false) { + if (self::getTrashbinSize($user) === false) { $query = \OC_DB::prepare('INSERT INTO `*PREFIX*files_trashsize` (`size`, `user`) VALUES (?, ?)'); - }else { + } else { $query = \OC_DB::prepare('UPDATE `*PREFIX*files_trashsize` SET `size`=? WHERE `user`=?'); } $query->execute(array($size, $user)); @@ -845,4 +835,5 @@ class Trashbin { //Listen to delete user signal \OCP\Util::connectHook('OC_User', 'pre_deleteUser', "OCA\Files_Trashbin\Hooks", "deleteUser_hook"); } + } From 30c09d0c8bd8827dc3175f6590a5549dadf0773e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 14 Jun 2013 15:30:41 +0200 Subject: [PATCH 029/247] split of scanning the childs of a folder --- lib/files/cache/scanner.php | 41 ++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 8f9a792195..816c856ac2 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -8,6 +8,8 @@ namespace OC\Files\Cache; +use OC\Files\Filesystem; + class Scanner { /** * @var \OC\Files\Storage\Storage $storage @@ -63,8 +65,8 @@ class Scanner { * @return array with metadata of the scanned file */ public function scanFile($file, $checkExisting = false) { - if ( ! self::isPartialFile($file) - and ! \OC\Files\Filesystem::isFileBlacklisted($file) + if (!self::isPartialFile($file) + and !Filesystem::isFileBlacklisted($file) ) { \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); $data = $this->getData($file); @@ -84,7 +86,8 @@ class Scanner { $data['size'] = $cacheData['size']; } if ($data['mtime'] === $cacheData['mtime'] && - $data['size'] === $cacheData['size']) { + $data['size'] === $cacheData['size'] + ) { $data['etag'] = $cacheData['etag']; } // Only update metadata that has changed @@ -100,38 +103,42 @@ class Scanner { } /** - * scan all the files in a folder and store them in the cache + * scan a folder and all it's children * * @param string $path * @param bool $recursive - * @param bool $onlyChilds * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - public function scan($path, $recursive = self::SCAN_RECURSIVE, $onlyChilds = false) { - \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_folder', array('path' => $path, 'storage' => $this->storageId)); - $childQueue = array(); - if (!$onlyChilds) { - $this->scanFile($path); - } + public function scan($path, $recursive = self::SCAN_RECURSIVE) { + $this->scanFile($path); + return $this->scanChildren($path, $recursive); + } + /** + * scan all the files and folders in a folder + * + * @param string $path + * @param bool $recursive + * @return int the size of the scanned folder or -1 if the size is unknown at this stage + */ + public function scanChildren($path, $recursive = self::SCAN_RECURSIVE) { + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_folder', array('path' => $path, 'storage' => $this->storageId)); $size = 0; + $childQueue = array(); if ($this->storage->is_dir($path) && ($dh = $this->storage->opendir($path))) { \OC_DB::beginTransaction(); while ($file = readdir($dh)) { $child = ($path) ? $path . '/' . $file : $file; - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if (!Filesystem::isIgnoredDir($file)) { $data = $this->scanFile($child, $recursive === self::SCAN_SHALLOW); if ($data) { if ($data['size'] === -1) { if ($recursive === self::SCAN_RECURSIVE) { $childQueue[] = $child; - $data['size'] = 0; } else { $size = -1; } - } - - if ($size !== -1) { + } else if ($size !== -1) { $size += $data['size']; } } @@ -139,7 +146,7 @@ class Scanner { } \OC_DB::commit(); foreach ($childQueue as $child) { - $childSize = $this->scan($child, self::SCAN_RECURSIVE, true); + $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE); if ($childSize === -1) { $size = -1; } else { From f10a4db88997b3848a8c149d35dbb68c1b8c5f60 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 14 Jun 2013 16:53:08 +0200 Subject: [PATCH 030/247] scanner: give more percision about what data is reused during scanning --- lib/files/cache/scanner.php | 46 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 816c856ac2..061778cd85 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -29,6 +29,9 @@ class Scanner { const SCAN_RECURSIVE = true; const SCAN_SHALLOW = false; + const REUSE_ETAG = 1; + const REUSE_SIZE = 2; + public function __construct(\OC\Files\Storage\Storage $storage) { $this->storage = $storage; $this->storageId = $this->storage->getId(); @@ -61,10 +64,10 @@ class Scanner { * scan a single file and store it in the cache * * @param string $file - * @param bool $checkExisting check existing folder sizes in the cache instead of always using -1 for folder size + * @param int $reuseExisting * @return array with metadata of the scanned file */ - public function scanFile($file, $checkExisting = false) { + public function scanFile($file, $reuseExisting = 0) { if (!self::isPartialFile($file) and !Filesystem::isFileBlacklisted($file) ) { @@ -81,21 +84,22 @@ class Scanner { } } $newData = $data; - if ($cacheData = $this->cache->get($file)) { - if ($checkExisting && $data['size'] === -1) { - $data['size'] = $cacheData['size']; - } - if ($data['mtime'] === $cacheData['mtime'] && - $data['size'] === $cacheData['size'] - ) { - $data['etag'] = $cacheData['etag']; + if ($reuseExisting and $cacheData = $this->cache->get($file)) { + // only reuse data if the file hasn't explicitly changed + if ($data['mtime'] === $cacheData['mtime']) { + if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) { + $data['size'] = $cacheData['size']; + } + if ($reuseExisting & self::REUSE_ETAG) { + $data['etag'] = $cacheData['etag']; + } } // Only update metadata that has changed $newData = array_diff($data, $cacheData); } - if (!empty($newData)) { - $this->cache->put($file, $newData); - } + } + if (!empty($newData)) { + $this->cache->put($file, $newData); } return $data; } @@ -107,10 +111,14 @@ class Scanner { * * @param string $path * @param bool $recursive + * @param int $reuse * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - public function scan($path, $recursive = self::SCAN_RECURSIVE) { - $this->scanFile($path); + public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0; + } + $this->scanFile($path, $reuse); return $this->scanChildren($path, $recursive); } @@ -119,9 +127,13 @@ class Scanner { * * @param string $path * @param bool $recursive + * @param int $reuse * @return int the size of the scanned folder or -1 if the size is unknown at this stage */ - public function scanChildren($path, $recursive = self::SCAN_RECURSIVE) { + public function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1) { + if ($reuse === -1) { + $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0; + } \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_folder', array('path' => $path, 'storage' => $this->storageId)); $size = 0; $childQueue = array(); @@ -130,7 +142,7 @@ class Scanner { while ($file = readdir($dh)) { $child = ($path) ? $path . '/' . $file : $file; if (!Filesystem::isIgnoredDir($file)) { - $data = $this->scanFile($child, $recursive === self::SCAN_SHALLOW); + $data = $this->scanFile($child, $reuse); if ($data) { if ($data['size'] === -1) { if ($recursive === self::SCAN_RECURSIVE) { From 398fe8bf3255df7ac9d301522401c4a746a0e7f9 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 14 Jun 2013 17:04:17 +0200 Subject: [PATCH 031/247] reuse etag when doing a forced rescan --- apps/files/ajax/scan.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files/ajax/scan.php b/apps/files/ajax/scan.php index 391b98608b..6659cd459c 100644 --- a/apps/files/ajax/scan.php +++ b/apps/files/ajax/scan.php @@ -24,7 +24,7 @@ foreach ($mountPoints as $mountPoint) { ScanListener::$mountPoints[$storage->getId()] = $mountPoint; $scanner = $storage->getScanner(); if ($force) { - $scanner->scan(''); + $scanner->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG); } else { $scanner->backgroundScan(); } From e0547a25ab9c5a3d9454611f72e00e3bc667e2d2 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Fri, 3 May 2013 00:15:28 +0200 Subject: [PATCH 032/247] if rename of file fails, the rename is undone in the view - #fix 2820 Changes: * OC.dialog -> OC.Notification * Added test * Fixed OC.Notification.show() issue for queued items * Highlight failed item and show notification --- apps/files/js/filelist.js | 41 ++++++++++++++++++++++++++++---- apps/files/lib/app.php | 2 +- apps/files/tests/ajax_rename.php | 2 +- core/js/js.js | 4 ++-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index e19a35bbc5..c6663836fd 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -208,13 +208,44 @@ var FileList={ if (FileList.checkName(name, newname, false)) { newname = name; } else { - $.get(OC.filePath('files','ajax','rename.php'), { dir : $('#dir').val(), newname: newname, file: name },function(result) { - if (!result || result.status == 'error') { - OC.dialogs.alert(result.data.message, 'Error moving file'); - newname = name; + // save background image, because it's replaced by a spinner while async request + var oldBackgroundImage = td.css('background-image'); + // mark as loading + td.css('background-image', 'url('+ OC.imagePath('core', 'loading.gif') + ')'); + $.ajax({ + url: OC.filePath('files','ajax','rename.php'), + data: { + dir : $('#dir').val(), + newname: newname, + file: name + }, + success: function(result) { + if (!result || result.status === 'error') { + OC.Notification.show(result.data.message); + newname = name; + // revert changes + tr.attr('data-file', newname); + var path = td.children('a.name').attr('href'); + td.children('a.name').attr('href', path.replace(encodeURIComponent(name), encodeURIComponent(newname))); + if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { + var basename=newname.substr(0,newname.lastIndexOf('.')); + } else { + var basename=newname; + } + td.find('a.name span.nametext').text(basename); + if (newname.indexOf('.') > 0 && tr.data('type') !== 'dir') { + if (td.find('a.name span.extension').length === 0 ) { + td.find('a.name span.nametext').append(''); + } + td.find('a.name span.extension').text(newname.substr(newname.lastIndexOf('.'))); + } + tr.find('.fileactions').effect('highlight', {}, 5000); + tr.effect('highlight', {}, 5000); + } + // remove loading mark and recover old image + td.css('background-image', oldBackgroundImage); } }); - } } tr.data('renaming',false); diff --git a/apps/files/lib/app.php b/apps/files/lib/app.php index c2a4b9c267..f7052ef80b 100644 --- a/apps/files/lib/app.php +++ b/apps/files/lib/app.php @@ -70,7 +70,7 @@ class App { } else { // rename failed $result['data'] = array( - 'message' => $this->l10n->t('Unable to rename file') + 'message' => $this->l10n->t('%s could not be renamed', array($oldname)) ); } return $result; diff --git a/apps/files/tests/ajax_rename.php b/apps/files/tests/ajax_rename.php index 23e5761ddd..2b90a11743 100644 --- a/apps/files/tests/ajax_rename.php +++ b/apps/files/tests/ajax_rename.php @@ -50,7 +50,7 @@ class Test_OC_Files_App_Rename extends \PHPUnit_Framework_TestCase { $result = $this->files->rename($dir, $oldname, $newname); $expected = array( 'success' => false, - 'data' => array('message' => 'Unable to rename file') + 'data' => array('message' => '%s could not be renamed') ); $this->assertEquals($expected, $result); diff --git a/core/js/js.js b/core/js/js.js index 3cb4d3dd15..55db625a33 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -349,10 +349,10 @@ OC.Notification={ }, show: function(text) { if(($('#notification').filter('span.undo').length == 1) || OC.Notification.isHidden()){ - $('#notification').html(text); + $('#notification').text(text); $('#notification').fadeIn().css("display","inline"); }else{ - OC.Notification.queuedNotifications.push($(text).html()); + OC.Notification.queuedNotifications.push($('
    ').text(text).html()); } }, isHidden: function() { From 383e4c62b578996b343b540e03c7afed61be644e Mon Sep 17 00:00:00 2001 From: Thomas Mueller Date: Mon, 17 Jun 2013 00:02:42 +0200 Subject: [PATCH 033/247] in case $_SERVER['HTTP_HOST']) is not set let's return localhost - better than nothing --- lib/request.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/request.php b/lib/request.php index 4d8380eb9a..aa5f53c08e 100755 --- a/lib/request.php +++ b/lib/request.php @@ -19,7 +19,7 @@ class OC_Request { /** * @brief Returns the server host - * @returns the server host + * @returns string the server host * * Returns the server host, even if the website uses one or more * reverse proxies @@ -40,7 +40,7 @@ class OC_Request { } } else{ - $host = $_SERVER['HTTP_HOST']; + $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost'; } return $host; } @@ -48,7 +48,7 @@ class OC_Request { /** * @brief Returns the server protocol - * @returns the server protocol + * @returns string the server protocol * * Returns the server protocol. It respects reverse proxy servers and load balancers */ @@ -70,7 +70,7 @@ class OC_Request { /** * @brief Returns the request uri - * @returns the request uri + * @returns string the request uri * * Returns the request uri, even if the website uses one or more * reverse proxies @@ -85,7 +85,7 @@ class OC_Request { /** * @brief Returns the script name - * @returns the script name + * @returns string the script name * * Returns the script name, even if the website uses one or more * reverse proxies @@ -139,7 +139,7 @@ class OC_Request { /** * @brief Check if this is a no-cache request - * @returns true for no-cache + * @returns boolean true for no-cache */ static public function isNoCache() { if (!isset($_SERVER['HTTP_CACHE_CONTROL'])) { @@ -150,7 +150,7 @@ class OC_Request { /** * @brief Check if the requestor understands gzip - * @returns true for gzip encoding supported + * @returns boolean true for gzip encoding supported */ static public function acceptGZip() { if (!isset($_SERVER['HTTP_ACCEPT_ENCODING'])) { From 9e78e6b2e5bc9231265e5cb8e8ba1d7cdebab94b Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 12:14:36 +0200 Subject: [PATCH 034/247] differentiate delete from close icon --- core/css/styles.css | 1 - core/img/actions/delete-hover.png | Bin 254 -> 0 bytes core/img/actions/delete-hover.svg | 6 --- core/img/actions/delete.png | Bin 240 -> 334 bytes core/img/actions/delete.svg | 67 ++++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 11 deletions(-) delete mode 100644 core/img/actions/delete-hover.png delete mode 100644 core/img/actions/delete-hover.svg diff --git a/core/css/styles.css b/core/css/styles.css index 40a17a4287..7100b8c290 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -655,7 +655,6 @@ div.crumb:active { /* icons */ .folder-icon { background-image: url('../img/places/folder.svg'); } .delete-icon { background-image: url('../img/actions/delete.svg'); } -.delete-icon:hover { background-image: url('../img/actions/delete-hover.svg'); } .edit-icon { background-image: url('../img/actions/rename.svg'); } /* buttons */ diff --git a/core/img/actions/delete-hover.png b/core/img/actions/delete-hover.png deleted file mode 100644 index 048d91cee5143ce826ef9ef3d7619d835bce0877..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFtWs_ aoD4g!3*63oe>4_o2!p4qpUXO@geCyPB2g0n diff --git a/core/img/actions/delete-hover.svg b/core/img/actions/delete-hover.svg deleted file mode 100644 index 3e8d26c978..0000000000 --- a/core/img/actions/delete-hover.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/core/img/actions/delete.png b/core/img/actions/delete.png index fa8e18183ed3d126ab5706d093f7e64d944c835a..6362903937c001f3bd5ebac47fe8ed48195c66a9 100644 GIT binary patch delta 232 zcmV+&}~bf3=zHpZ|7e*Ceq`u^d4TTzUyp%6)=tL3jrL8e=F`1_{v2FgFH?d(zz{22yHp2FkHy i{}pye@U`kQEzujdT3cnj%BXMv0000BD)H2AVO_LHz0xN sSfI!Nio8H^AW|b5DN9I0Gcy4I+7e6GgT+Z{00000NkvXXt^-0~g7-r)8UO$Q diff --git a/core/img/actions/delete.svg b/core/img/actions/delete.svg index ef564bfd48..08ea381f37 100644 --- a/core/img/actions/delete.svg +++ b/core/img/actions/delete.svg @@ -1,6 +1,65 @@ - - - - + + + + + image/svg+xml + + + + + + + + + From 257ebc2830a04272c0b662cce85fea6470e77f7c Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 12:18:45 +0200 Subject: [PATCH 035/247] use consistent icon for 'restore'/versions/history, also SVG --- apps/files_trashbin/js/trash.js | 2 +- core/img/actions/undelete.png | Bin 624 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 core/img/actions/undelete.png diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 691642811b..82119a5951 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -2,7 +2,7 @@ $(document).ready(function() { if (typeof FileActions !== 'undefined') { - FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/undelete.png'), function(filename) { + FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history.svg'), function(filename) { var tr=$('tr').filterAttr('data-file', filename); var spinner = ''; var undeleteAction = $('tr').filterAttr('data-file',filename).children("td.date"); diff --git a/core/img/actions/undelete.png b/core/img/actions/undelete.png deleted file mode 100644 index d712527ef617dca921bcf75b917a1f728bc2102c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 624 zcmV-$0+0QPP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01m_e01m_fl`9S#00007bV*G`2i*Z4 z3Ih^4(F+g&000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0005YNkljtP1&(N=qgyIH-$IORb=2uhU1ruT51DFWl*#dmi_k^F&qomon=Oo)3xb zj>=#_0(psc4$7&g?G1sdimGz4pCVn^hZoMX?U1D;m^xyn5ga2-8>EgMC~^JMc6Ucr zR|pFGxD+(hFimy9A+%|hcwz)YArWV^HN-675Z&{FTb~mR34jVvp?Oz@d)n=NY5oQ~ z`(jKYIP4u7N7eWU&h>KHB?ua7q>ewLq8oiAqomuzR1GaPuD&~{sw*O<+b9ELz}Syv zMp*p#gzxw)ik#KgNBVfP%+gQF;~9XUJIs}JDhE@4vMuzLIiQ2ZxfY*|6Gvsgh~%X| zm;D{Vw`Mv3XiY5nYq778i8U^$D(`R7xjV`$1cz{BhONnAV<;qJ+}Yg341z Date: Mon, 17 Jun 2013 12:22:09 +0200 Subject: [PATCH 036/247] transparent background for lock icon --- core/img/actions/lock.png | Bin 182 -> 295 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/core/img/actions/lock.png b/core/img/actions/lock.png index dbcffa3990f7dace24aaae6836977a142d57fe47..f3121811ea69565b897e2806f7d9aa66ac90bd68 100644 GIT binary patch delta 279 zcmdnSxSVN%WIY=L1B3kM|A|0~rP#?cgaZg_I8r)*e9i)o$YKTt-s>RD=%g{b0w^e1 z;u=vBoS#-wo>-L1P+nfHmzkGcoSayYs+V7sKKq@G6j0F;PZ!4!i{7{A^|_oJC0IV( z?>d+z*ps&Sp!Uh^vi=VTURl~SDcn1fu#v02ZEMZH==uCdk_r+MZc09zJ+U}UD0t0A z{&j}ApUOl$G(&GZ>T&(hlbdl~L$ANk>FR}5T)BU~#LvijGiz1bKIgk;iA`s{&sv*3 z+_{kF#p4@71w7|(H}p?Tnmy%`(B>UQ6_4dznnTZ3^4(=GY>{g_Xx6i?f4%x2<~qd( aOw%K3&I{Q5%Mt>*n8DN4&t;ucLK6V}&2v=% delta 165 zcmZ3^w2g6sWIY2ASj||l7f3M{J9&n1JmZR<3FL4VctjR6Fz_7#VaBQ2e9}O{Xipc% z5Q*^QAN)+|heSn{&o|HHQAkqUe0$2vcRHsNn9OflIi4x&+Z`b4+Lmzi|5vRAOzT_) z8f)}dG}M~9EVv`t?!er?#8ATHgIV+1-t>m%6gev{5wF9JFaQ5%*vHx)6&$7Z6lf2F Mr>mdKI;Vst042>j5dZ)H From 344a73b173c3dc0a1a203e5db4fa64adab7f0e48 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 12:26:32 +0200 Subject: [PATCH 037/247] use mail icon consistent with Mail app icon --- core/img/actions/mail.png | Bin 405 -> 303 bytes core/img/actions/mail.svg | 100 +++++++++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/core/img/actions/mail.png b/core/img/actions/mail.png index 8e884fbc0ea87ee8549053f603bbf9bf55c29817..be6142444ae8052e6f96c4b8afc3d267704d1507 100644 GIT binary patch delta 202 zcmV;*05$)W1Fr&*ZGQl)Nklr_MR^`+ZL{%_JcNRaUK%`xgM%Abc4cF&QKMz?eo)$N(qw--Mjd z!!ZvC*vE}mkRv+S<$8C5ExIL;1KL;@`VYb>8U@G!bxfY#Rc=|2~>PY$^U<*8}*KjdyZyb!vFvP07*qoM6N<$ Ef?jo5dH?_b delta 305 zcmV-10nYxf0+j=hZGQn0NklS$-FB#tb_!SEaWFs;Y9Dro1c*cU{M(X^P;C8Fpa46iAZ9 zQ51={<$2DAVQ?JBJdUFX&X{2b1}q#=;JWU)ZCh@dh8@Sbd9&wvBG)r8VBx@}X_{Wb zFnnv=6|ivNk}9Go4|~PW*<89D44u~8{V^x{_rLKK%>&TsaB3rf00000NkvXXu0mjf DVI7Wk diff --git a/core/img/actions/mail.svg b/core/img/actions/mail.svg index 2c63daac03..6e8a4bd7f6 100644 --- a/core/img/actions/mail.svg +++ b/core/img/actions/mail.svg @@ -1,8 +1,96 @@ - - - - - - + + + + + + + image/svg+xml + + + + + + + + + + + + + + + From 078835ee9dd02e8f4cd8387c0eeace9913dc9349 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 12:27:45 +0200 Subject: [PATCH 038/247] remove unused big images --- core/img/remoteStorage-big.png | Bin 8372 -> 0 bytes core/img/weather-clear.png | Bin 21917 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 core/img/remoteStorage-big.png delete mode 100644 core/img/weather-clear.png diff --git a/core/img/remoteStorage-big.png b/core/img/remoteStorage-big.png deleted file mode 100644 index 7e76e21209e539354805d12d8f92c5a8176c0861..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8372 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 From d9dcba9a39a468a0fe5cfaad99d6a7282ba2702a Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 12:35:25 +0200 Subject: [PATCH 039/247] remove unused loading spinners, just have one --- apps/files_trashbin/js/trash.js | 4 ++-- core/img/loader.gif | Bin 847 -> 0 bytes core/img/loading-dark.gif | Bin 673 -> 0 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 core/img/loader.gif delete mode 100644 core/img/loading-dark.gif diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 82119a5951..307ac743a3 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -4,7 +4,7 @@ $(document).ready(function() { if (typeof FileActions !== 'undefined') { FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history.svg'), function(filename) { var tr=$('tr').filterAttr('data-file', filename); - var spinner = ''; + var spinner = ''; var undeleteAction = $('tr').filterAttr('data-file',filename).children("td.date"); var files = tr.attr('data-file'); undeleteAction[0].innerHTML = undeleteAction[0].innerHTML+spinner; @@ -94,7 +94,7 @@ $(document).ready(function() { $('.undelete').click('click',function(event) { event.preventDefault(); - var spinner = ''; + var spinner = ''; var files=getSelectedFiles('file'); var fileslist = JSON.stringify(files); var dirlisting=getSelectedFiles('dirlisting')[0]; diff --git a/core/img/loader.gif b/core/img/loader.gif deleted file mode 100644 index e192ca895cd00d6b752ec84619b787188f30ee41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 847 zcmZ?wbhEHb6krfw_`<;O|Nnmm28JI$eppyow6wIav9YPCsa?BvZN-WeVq#)tWo2n; zX-}R!nK5I=v17+PJUqg}!hq_D2a5l>{aizWogD*Qjr0td8G$+#|4BI)r6!i7rYMwW zmSiX-W+hhSNI#p{aB=u^kJ9BqzM)+D@@g7D>_ZH z6>Nk>K2^#dec$hd&5{fSg)a9?JsDb3M<1+M;h^GLd*HyqYe$(ldZsj_W{3#!96X@l zAjsu&py5MupnEfu)0U^(0!(Kp*sL-QO$pql{X%Kq;`Av7E5z0lI$B?E`RzKdsAZ)9=nHHN!5+~JF4SY+VADb}iE(C2i8t1nx?>)BhL zPS>_TA<--6ZN7=uLx=rfr*28JR#UU9l!(BR!@3s} qR&*pBVEQRw*vTQWVY)*bLIMx~ diff --git a/core/img/loading-dark.gif b/core/img/loading-dark.gif deleted file mode 100644 index 5fe86acabc41f3cd97b09eb626e5e9020d5ea28e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmZ?wbhEHb6krfw_{0DL|Ns24v9{i`YZs6)XV$Em>gt^AY{h?4&PAz-C8;S2<(VZJ z3W-^X6*>8dDSDZCY55F_KUo;KfLay*bNji51UowhxEkphFf#&$bU;Re3};|A=Gb-T zpTQ~5Y~f@MD-Ovy?0z%SI9)dy9@-@`^WZkUgd>LvFX%=~Sl(U6ZYjaT?v?%A185}J zXhvjnOhD%N^(ZPxxA5%V@T|+F&?zt^!BA2m!N)aPYDJCA*3$cL6D8Oi6s}7=YIBy{ zq^hDN1T}~W*&s8HT}H~XscN4xYMc0GPFQ?v_cG2_MIIJIm-a*%!BuWe z8!pN-Ck4fRwv{)q(2?ptv82e-2j({xWOIx-b`_~>dp%DP`5^Jxr;$gk>~KO%Qpl9n zmYs4LkxrWDPdNxM%e}ObKdc5eCukDP7*=FsfX-1kG{I8*amn*Nx8@m09+!EbsOPk8 z?y2xKiwt?#xJ8N+cW*HLK9#Z2U;}68?)kZzUNCdmkj())=gz+moPsy!gvQQde0Qs` zU}{3g-NZR}O{TRvx*atTnUFAh8zV2vAqRokh7E_Votp?Vh8@EgV9c*hb-FS~^ST@d t$6EjG|h_^oWQ_f4N5p*002a;)W84$ From 0f904b3c9c9542d542ea52bc7683c79e6916fe18 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 12:58:43 +0200 Subject: [PATCH 040/247] use proper style for add icon --- core/img/actions/add.png | Bin 441 -> 195 bytes core/img/actions/add.svg | 68 ++++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/core/img/actions/add.png b/core/img/actions/add.png index 25d472b2dc41bd4412507d422b579ff36abad887..13e20df6626b5c5b907176c9887386b19545c25d 100644 GIT binary patch delta 108 zcmV-y0F(c@1H%E3I|~ih000fw0YWI7c#%dYRscXrL_t(IjqQ>_3IHGs!>suKFUPaE zL1BU*=GxJ*FafQ?$O1`AGoWa$m)5^gAYc>b0_gq;XB>pVeY0UKMn?p36o*~&(ft!qZt?&(m=SZtSlZVCP9h;+%sp+jDi`EpPwHG6cZ!K z3;q55kuU?&)6+o)h~P7T_uad9axnby<42X2mKL}H@$vC-pFe-rL{lRKH{i^fGa8|x zf1znsR#tGVtgM^{GeAa021U-((=%<)o;{Xu1ArVgAb@KighfO|(l&42Yz8-A+qP}W z$TkwfLPA1mt5&Tth8wVd|9+Xu%1XbYq9PvGX z(7+c9tiHa!!7u|%O-*A!F+)l;vN$?AP6ojY(9zKW8Nfk`0nBD*W`QsRR8&-=fnw~W z7{Fv=V&V@B?GzB!(9nnkd5aVS7@!)EF(au45QPDtx=du=nI6{w0000 - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + From 1f518025a3f5c1078b7b94e93f218b8ed58b556c Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 13:20:02 +0200 Subject: [PATCH 041/247] standardize on 32px loading spinner, decrease size in CSS where needed --- core/css/styles.css | 7 +++++++ core/img/loading-dark.gif | Bin 0 -> 3208 bytes core/img/loading.gif | Bin 1849 -> 3208 bytes 3 files changed, 7 insertions(+) create mode 100644 core/img/loading-dark.gif diff --git a/core/css/styles.css b/core/css/styles.css index 7100b8c290..6c9d00114a 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -416,7 +416,13 @@ a.bookmarklet { background-color:#ddd; border:1px solid #ccc; padding:5px;paddin #oc-dialog-filepicker-content .filepicker_element_selected { background-color:lightblue;} .ui-dialog {position:fixed !important;} span.ui-icon {float: left; margin: 3px 7px 30px 0;} + .loading { background: url('../img/loading.gif') no-repeat center; cursor: wait; } +.move2trash { /* decrease spinner size */ + width: 16px; + height: 16px; +} + /* ---- CATEGORIES ---- */ #categoryform .scrollarea { position:absolute; left:10px; top:10px; right:10px; bottom:50px; overflow:auto; border:1px solid #ddd; background:#f8f8f8; } @@ -661,6 +667,7 @@ div.crumb:active { button.loading { background-image: url('../img/loading.gif'); background-position: right 10px center; background-repeat: no-repeat; + background-size: 16px; padding-right: 30px; } diff --git a/core/img/loading-dark.gif b/core/img/loading-dark.gif new file mode 100644 index 0000000000000000000000000000000000000000..13f0f64eab152fa4949476ed83ea24d6fd45a9bc GIT binary patch literal 3208 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 A6951J literal 0 HcmV?d00001 diff --git a/core/img/loading.gif b/core/img/loading.gif index 5b33f7e54f4e55b6b8774d86d96895db9af044b4..f8f3dff6fb955ebc02453352e59c845461723292 100644 GIT binary patch literal 3208 zcmc(ic~Dc=9>*`aH#f_@`t;sl1A!Wph)@ebAZ1k{K!AWO0)i|`j0&QnECN9wEFl3| zL)c7E5=acuiiitnwa8LHivx8*?NfoaD!A9N&-Qhm5A3{|H}m-8&Ageu^XHi}=gjB( z`+mPOhv)C>?2^C)n7~^A`0(L_gM-8P__#`?8WWcJzsWFR^U)MU7j-2%d`YGiylNwVS4G*iLqK zBYQRbEkw0fzh#>cmbh6CvbjboTY|rh#qWOH)t(!crWip*77i}qP8VaxovrnYq%GU7 zzC3$INF!xt@jRj->Mj~ol<6lZF+T`8CH#oH*Is zW!}g>e=eO>~e(~^Lf#6w!I$rJs$;! zs?tgx1GBM${zO}s=N@Uh-sKV#cC0+J;6GOgwwl z_z05>ordh`zpn}_>XhSt7}e3pKJ@JOhazDU7C{6Zq(S*BceAC`O4^=s-mV;W9$4yL z;7%!y(Zom-YqGRzUOPn1Zr8LQ?~t~hua7y(Zn((!ig&h#q1T0!h*a26;>Z>CKx49~ z4@)v_7$Ij@wv1m(JS4iEDCa#Wo{k*UbQH`0FM6KECgM+GIx1fQLv6CMcdP0?t7+MM z^otA5lP1F!goW^5&f#0z&*49@=Q#=EB&+MxVMAvW;cLqky90}J`fs{3@t85E$spR5 zNh*8H#9CrPWd?RHYx5_RyxuDr_0bP)qn(9_`!Q_<6)Aw?chXqo!uU?&@Q>yMI0JRV z2_g|8mt7pipioOUvB4dg=GjoPJ4wZ&91Cu3Ev=>0=tOOX9Ql_g4TstdZRcAxtRAx? zmuQ!LJCe%n`xIc#(v?2;T%&#Bfl6A@NZ!P4Ai!kJ zvcInJOsFFpOq(Mz;~kCsSs@P|k{4$nq01Z`2s!KmILSU-!Xxg=G|SFqfijj=n5r2^ zThs`VQYk6-P#aL)@#Yg~tM<6V(eu1|R*z!;#mj6RwF&PE2uqPT;lm|t{{h|J>|`0# zpEZh;!)04@hgVktl8N}~>1=*;W!gFHxoT!z(pEjI^7hMpzj@rlrte=naMy9i=;uLw zZ40~qASvjD`(*T_ zTiM;yxVTa1Uds$76sAZ0?0&%Nv!z~(7asrH`2q-QpDTbB0W4;7eKk$3^9kKzs6K^# zhrJ3E%FF)tdGskA^<83Nee%;#^&xRF<}{UoT|phA-}}>wjEqmT5TK2V1e+0Qg*c-| z>ZGK;yqa403*{s$Tfq2aT|A4BVwZZ*e2l;XJ%S)s#4aZ=ms6u(IsRtpgDDg4}Q7;JYIp>{*SJ6l)3e&(F@lnvuT+0y3 z1ez20aD51H4Mh~nU`GI%80+=9`4;*~u8e$UN$-AqZK;tEAOwu9w8kWV*&n&Iuh(+H zCV2L5I!NZMz%*8e^=;W$Y7=5(bYjZwwq;0;jbdYA?~7QUvFe{K$ocjW!D}CQQ<}D> za{PZ0P}OZe-5T&~IO0t4YH*{RIl01$Y*20a{(kypxxh@!U&*!N%Sv&uyn>jSyIxkI z0Bhv@I4>Da1VmweJGn3OJxH76pm(0RSV+llU$1tLff zPSkgX76@0PcL2(Dq?5lHyMtoa5V$PJYZ8aAgEIfKGZ=JV7Ub~;BVBJ}q~Y(UyDvL? zCm0nG)d93^8v=RILQVMU&*0~Y=ySlbD(75Lsk}mwSEgh|74r!o=we>vs?is)bK6)H zq8=p9lS8l;IDm5?3;* z*wbz<`ye-$&VUe|t|TKChdWTXi$MSra|K!*LiFx4g+Q=BZn+0hr^iVMqKD@^Fboz% zTHi!W?!XxJei?joY#JvHyKupqKVZ+iAM>9g)xbOK#aDctM3YfLxquDXn6cT_n?41v zr7zrxe@YPMj6g)t>Y7rK6SbxRtyNT1HI^OYN?a-6ybuUN{3rx$Fw6@<>Ox2t7(R14 zv>RX!Kpr%Wp^=w+Kn86$3~Rv&U~|?g>szqN#CQHKGSaP+z|@8mF7Xx#pABEnfB4PJ zU0nD*;}z}axc67RzEJl9g)JO1ee8tyf`S#y|JlQD=mzyc9q!?VfcV+ zPyvHV>geERi1UC8-K0`epTw@@QynA@KmjiU^D1oZ=qv(3PKdi*1o<8q?dL0>a#@cB z^|7c^yzAf2aHo>r0tfYsdvDg+#{I^jwZFEIS}*>D5Gng_5)gs@(SnqIx+0)=_xp`& z)A*b!YNrwv9`;`%9yYc{OsGo&@A{qItmdH{3gOx({3yB(D|+72DYxZlgs1h4JOV7L zO}y7fBV}ZeQtdd*C?~kc-)(D&Svo=tUg<;0305j)E|DZy)2ce^(yDiCK1zqw?W#^? zgIi=h*NzH;;vNp2LB7=Am}hetv+5_7nFggS@5U}(B8vCj7!oXx>H}S|ytvOIZASMR z{{)%vw@wC+r}BX}T^l#p0<+3-;jcj6jRRx4AD80=W|+5z literal 1849 zcma*odr(tX9tZI2z31lM+(&YVk%mZ}5P~KlG2s=WSbGzm0!x7^P##Mnh7t-jP!X0Q zk_SQ}Po-L1tlDK;6l?(>v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw From 2a5776354251ddfeccc854d2d0af157575afa28d Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 13:30:57 +0200 Subject: [PATCH 042/247] use history icon in Deleted Files template as well --- apps/files_trashbin/js/trash.js | 2 +- apps/files_trashbin/templates/index.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files_trashbin/js/trash.js b/apps/files_trashbin/js/trash.js index 307ac743a3..87dfea491e 100644 --- a/apps/files_trashbin/js/trash.js +++ b/apps/files_trashbin/js/trash.js @@ -2,7 +2,7 @@ $(document).ready(function() { if (typeof FileActions !== 'undefined') { - FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history.svg'), function(filename) { + FileActions.register('all', 'Restore', OC.PERMISSION_READ, OC.imagePath('core', 'actions/history'), function(filename) { var tr=$('tr').filterAttr('data-file', filename); var spinner = ''; var undeleteAction = $('tr').filterAttr('data-file',filename).children("td.date"); diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php index cb5edaa2c9..66ec36df86 100644 --- a/apps/files_trashbin/templates/index.php +++ b/apps/files_trashbin/templates/index.php @@ -18,7 +18,7 @@ <?php p($l->t( 'Restore' )); ?>" /> + src="" /> t('Restore'))?> From efb026c664376c1091dfad59a506c076e69f0640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Schie=C3=9Fle?= Date: Mon, 17 Jun 2013 14:42:18 +0200 Subject: [PATCH 043/247] don't normalize absolute local path --- apps/files_versions/lib/versions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php index 757926cd77..2f8262475b 100644 --- a/apps/files_versions/lib/versions.php +++ b/apps/files_versions/lib/versions.php @@ -241,7 +241,7 @@ class Storage { public static function getVersions($uid, $filename, $count = 0 ) { if( \OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true' ) { $versions_fileview = new \OC\Files\View('/' . $uid . '/files_versions'); - $versionsName = \OC_Filesystem::normalizePath($versions_fileview->getLocalFile($filename).'.v'); + $versionsName = $versions_fileview->getLocalFile($filename).'.v'; $escapedVersionName = preg_replace('/(\*|\?|\[)/', '[$1]', $versionsName); $versions = array(); From 63c898c064233d08823e1b18ec7cb20185b1fe05 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 6 Jun 2013 20:47:20 +0200 Subject: [PATCH 044/247] Make rmdir recursive for local storage --- lib/files/storage/local.php | 22 +++++++++++++++++++++- tests/lib/files/storage/storage.php | 14 +++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/files/storage/local.php b/lib/files/storage/local.php index d684905bf9..b08fd73ce1 100644 --- a/lib/files/storage/local.php +++ b/lib/files/storage/local.php @@ -39,7 +39,27 @@ if (\OC_Util::runningOnWindows()) { } public function rmdir($path) { - return @rmdir($this->datadir . $path); + try { + $it = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($this->datadir . $path), + \RecursiveIteratorIterator::CHILD_FIRST + ); + foreach ($it as $file) { + /** + * @var \SplFileInfo $file + */ + if (in_array($file->getBasename(), array('.', '..'))) { + continue; + } elseif ($file->isDir()) { + rmdir($file->getPathname()); + } elseif ($file->isFile() || $file->isLink()) { + unlink($file->getPathname()); + } + } + return rmdir($this->datadir . $path); + } catch (\UnexpectedValueException $e) { + return false; + } } public function opendir($path) { diff --git a/tests/lib/files/storage/storage.php b/tests/lib/files/storage/storage.php index 0e22f26ae8..fb3e05e66b 100644 --- a/tests/lib/files/storage/storage.php +++ b/tests/lib/files/storage/storage.php @@ -258,9 +258,21 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $this->assertEquals(file_get_contents($textFile), $content); } - public function testTouchCreateFile(){ + public function testTouchCreateFile() { $this->assertFalse($this->instance->file_exists('foo')); $this->instance->touch('foo'); $this->assertTrue($this->instance->file_exists('foo')); } + + public function testRecursiveRmdir() { + $this->instance->mkdir('folder'); + $this->instance->mkdir('folder/bar'); + $this->instance->file_put_contents('folder/asd.txt', 'foobar'); + $this->instance->file_put_contents('folder/bar/foo.txt', 'asd'); + $this->instance->rmdir('folder'); + $this->assertFalse($this->instance->file_exists('folder/asd.txt')); + $this->assertFalse($this->instance->file_exists('folder/bar/foo.txt')); + $this->assertFalse($this->instance->file_exists('folder/bar')); + $this->assertFalse($this->instance->file_exists('folder')); + } } From 2ed0e6e91573489b9db0c37d1d4f3ae1c1393f00 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 17 Jun 2013 18:03:57 +0200 Subject: [PATCH 045/247] add tests for reusing existing data in scanner --- lib/files/cache/scanner.php | 6 ++---- tests/lib/files/cache/scanner.php | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php index 061778cd85..e7fbd856d5 100644 --- a/lib/files/cache/scanner.php +++ b/lib/files/cache/scanner.php @@ -119,7 +119,7 @@ class Scanner { $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0; } $this->scanFile($path, $reuse); - return $this->scanChildren($path, $recursive); + return $this->scanChildren($path, $recursive, $reuse); } /** @@ -165,9 +165,7 @@ class Scanner { $size += $childSize; } } - if ($size !== -1) { - $this->cache->put($path, array('size' => $size)); - } + $this->cache->put($path, array('size' => $size)); } return $size; } diff --git a/tests/lib/files/cache/scanner.php b/tests/lib/files/cache/scanner.php index 3885c99e6d..3dacefa2b8 100644 --- a/tests/lib/files/cache/scanner.php +++ b/tests/lib/files/cache/scanner.php @@ -104,7 +104,7 @@ class Scanner extends \PHPUnit_Framework_TestCase { $this->assertNotEquals($cachedDataFolder['size'], -1); } - function testBackgroundScan(){ + function testBackgroundScan() { $this->fillTestFolders(); $this->storage->mkdir('folder2'); $this->storage->file_put_contents('folder2/bar.txt', 'foobar'); @@ -126,6 +126,24 @@ class Scanner extends \PHPUnit_Framework_TestCase { $this->assertFalse($this->cache->getIncomplete()); } + public function testReuseExisting() { + $this->fillTestFolders(); + + $this->scanner->scan(''); + $oldData = $this->cache->get(''); + $this->storage->unlink('folder/bar.txt'); + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_SIZE); + $newData = $this->cache->get(''); + $this->assertNotEquals($oldData['etag'], $newData['etag']); + $this->assertEquals($oldData['size'], $newData['size']); + + $oldData = $newData; + $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW, \OC\Files\Cache\Scanner::REUSE_ETAG); + $newData = $this->cache->get(''); + $this->assertEquals($oldData['etag'], $newData['etag']); + $this->assertEquals(-1, $newData['size']); + } + function setUp() { $this->storage = new \OC\Files\Storage\Temporary(array()); $this->scanner = new \OC\Files\Cache\Scanner($this->storage); From aaefa157fddd115e2d8d78e2894febac14c59f20 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Mon, 17 Jun 2013 20:35:08 +0200 Subject: [PATCH 046/247] hide the logo claim in case the theme is not loaded --- core/templates/layout.guest.php | 2 +- core/templates/layout.user.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php index ba0147e673..4173212dfa 100644 --- a/core/templates/layout.guest.php +++ b/core/templates/layout.guest.php @@ -36,7 +36,7 @@
    diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 44ae4c6ca3..8c82a5c028 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -44,7 +44,7 @@ ownCloud -
    Enterprise Edition
    +