diff options
24 files changed, 565 insertions, 105 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 0bcea2eceaf..decfdbd2cda 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -91,6 +91,11 @@ position: relative; } +/* fit app list view heights */ +.app-files #app-content>.viewcontainer { + height: 100%; +} + /** * Override global #controls styles * to be more flexible / relative diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 68b22207144..1b2a62137e5 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -1529,13 +1529,18 @@ fileUploadStart.on('fileuploaddrop', function(e, data) { OC.Upload.log('filelist handle fileuploaddrop', e, data); - var dropTarget = $(e.originalEvent.target).closest('tr, .crumb'); - // check if dropped inside this list at all - if (dropTarget && !self.$el.has(dropTarget).length) { + var dropTarget = $(e.originalEvent.target); + // check if dropped inside this container and not another one + if (dropTarget.length && !self.$el.is(dropTarget) && !self.$el.has(dropTarget).length) { return false; } - if (dropTarget && (dropTarget.data('type') === 'dir' || dropTarget.hasClass('crumb'))) { // drag&drop upload to folder + // find the closest tr or crumb to use as target + dropTarget = dropTarget.closest('tr, .crumb'); + + // if dropping on tr or crumb, drag&drop upload to folder + if (dropTarget && (dropTarget.data('type') === 'dir' || + dropTarget.hasClass('crumb'))) { // remember as context data.context = dropTarget; @@ -1555,7 +1560,7 @@ } // update folder in form - data.formData = function(form) { + data.formData = function() { return [ {name: 'dir', value: dir}, {name: 'requesttoken', value: oc_requesttoken}, @@ -1563,6 +1568,9 @@ ]; }; } else { + // we are dropping somewhere inside the file list, which will + // upload the file to the current directory + // cancel uploads to current dir if no permission var isCreatable = (self.getDirectoryPermissions() & OC.PERMISSION_CREATE) !== 0; if (!isCreatable) { diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 8cab4ce220b..b52effb1e78 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -2,7 +2,7 @@ <?php $_['appNavigation']->printPage(); ?> <div id="app-content"> <?php foreach ($_['appContents'] as $content) { ?> - <div id="app-content-<?php p($content['id']) ?>" class="hidden"> + <div id="app-content-<?php p($content['id']) ?>" class="hidden viewcontainer"> <?php print_unescaped($content['content']) ?> </div> <?php } ?> diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index 3e9950dfe19..855a5c9af51 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -64,6 +64,8 @@ describe('OCA.Files.FileList tests', function() { ' <div class="actions creatable"></div>' + ' <div class="notCreatable"></div>' + '</div>' + + // uploader + '<input type="file" id="file_upload_start" name="files[]" multiple="multiple">' + // dummy table // TODO: at some point this will be rendered by the fileList class itself! '<table id="filestable">' + @@ -1686,4 +1688,139 @@ describe('OCA.Files.FileList tests', function() { expect(fileList.findFileEl('Three.pdf').index()).toEqual(0); }); }); + /** + * Test upload mostly by testing the code inside the event handlers + * that were registered on the magic upload object + */ + describe('file upload', function() { + var $uploader; + + beforeEach(function() { + // note: this isn't the real blueimp file uploader from jquery.fileupload + // but it makes it possible to simulate the event triggering to + // test the response of the handlers + $uploader = $('#file_upload_start'); + fileList.setupUploadEvents(); + fileList.setFiles(testFiles); + }); + + afterEach(function() { + $uploader = null; + }); + + describe('dropping external files', function() { + var uploadData; + + /** + * Simulate drop event on the given target + * + * @param $target target element to drop on + * @return event object including the result + */ + function dropOn($target, data) { + var eventData = { + originalEvent: { + target: $target + } + }; + var ev = new $.Event('fileuploaddrop', eventData); + // using triggerHandler instead of trigger so we can pass + // extra data + $uploader.triggerHandler(ev, data || {}); + return ev; + } + + /** + * Convert form data to a flat list + * + * @param formData form data array as used by jquery.upload + * @return map based on the array's key values + */ + function decodeFormData(data) { + var map = {}; + _.each(data.formData(), function(entry) { + map[entry.name] = entry.value; + }); + return map; + } + + beforeEach(function() { + // simulate data structure from jquery.upload + uploadData = { + files: [{ + relativePath: 'fileToUpload.txt' + }] + }; + }); + afterEach(function() { + uploadData = null; + }); + it('drop on a tr or crumb outside file list does not trigger upload', function() { + var $anotherTable = $('<table><tbody><tr><td>outside<div class="crumb">crumb</div></td></tr></table>'); + var ev; + $('#testArea').append($anotherTable); + ev = dropOn($anotherTable.find('tr'), uploadData); + expect(ev.result).toEqual(false); + + ev = dropOn($anotherTable.find('.crumb')); + expect(ev.result).toEqual(false); + }); + it('drop on an element outside file list container does not trigger upload', function() { + var $anotherEl = $('<div>outside</div>'); + var ev; + $('#testArea').append($anotherEl); + ev = dropOn($anotherEl); + + expect(ev.result).toEqual(false); + }); + it('drop on an element inside the table triggers upload', function() { + var ev; + ev = dropOn(fileList.$fileList.find('th:first'), uploadData); + + expect(ev.result).not.toEqual(false); + }); + it('drop on an element on the table container triggers upload', function() { + var ev; + ev = dropOn($('#app-content-files'), uploadData); + + expect(ev.result).not.toEqual(false); + }); + it('drop on an element inside the table does not trigger upload if no upload permission', function() { + $('#permissions').val(0); + var ev; + ev = dropOn(fileList.$fileList.find('th:first')); + + expect(ev.result).toEqual(false); + }); + it('drop on a file row inside the table triggers upload to current folder', function() { + var ev; + ev = dropOn(fileList.findFileEl('One.txt').find('td:first'), uploadData); + + expect(ev.result).not.toEqual(false); + }); + it('drop on a folder row inside the table triggers upload to target folder', function() { + var ev, formData; + ev = dropOn(fileList.findFileEl('somedir').find('td:eq(2)'), uploadData); + + expect(ev.result).not.toEqual(false); + expect(uploadData.formData).toBeDefined(); + formData = decodeFormData(uploadData); + expect(formData.dir).toEqual('/subdir/somedir'); + expect(formData.file_directory).toEqual('fileToUpload.txt'); + expect(formData.requesttoken).toBeDefined(); + }); + it('drop on a breadcrumb inside the table triggers upload to target folder', function() { + var ev, formData; + fileList.changeDirectory('a/b/c/d'); + ev = dropOn(fileList.$el.find('.crumb:eq(2)'), uploadData); + + expect(ev.result).not.toEqual(false); + expect(uploadData.formData).toBeDefined(); + formData = decodeFormData(uploadData); + expect(formData.dir).toEqual('/a/b'); + expect(formData.file_directory).toEqual('fileToUpload.txt'); + expect(formData.requesttoken).toBeDefined(); + }); + }); + }); }); diff --git a/apps/files_sharing/lib/proxy.php b/apps/files_sharing/lib/proxy.php index c899a4b4dd3..f595328cc63 100644 --- a/apps/files_sharing/lib/proxy.php +++ b/apps/files_sharing/lib/proxy.php @@ -57,7 +57,7 @@ class Proxy extends \OC_FileProxy { $mountManager = \OC\Files\Filesystem::getMountManager(); $mountedShares = $mountManager->findIn($path); foreach ($mountedShares as $mount) { - if ($mount->getStorage() instanceof \OC\Files\Storage\Shared) { + if ($mount->getStorage()->instanceOfStorage('\OC\Files\Storage\Shared')) { $mountPoint = $mount->getMountPoint(); $mountPointName = $mount->getMountPointName(); $target = \OCA\Files_Sharing\Helper::generateUniqueTarget(dirname($path) . '/' . $mountPointName, array(), $view); diff --git a/apps/files_sharing/lib/sharedstorage.php b/apps/files_sharing/lib/sharedstorage.php index 67ccbd13403..a7dd2b3afa1 100644 --- a/apps/files_sharing/lib/sharedstorage.php +++ b/apps/files_sharing/lib/sharedstorage.php @@ -300,7 +300,7 @@ class Shared extends \OC\Files\Storage\Common { // it shouldn't be possible to move a Shared storage into another one list($targetStorage, ) = \OC\Files\Filesystem::resolvePath($targetPath); - if ($targetStorage instanceof \OC\Files\Storage\Shared) { + if ($targetStorage->instanceOfStorage('\OC\Files\Storage\Shared')) { \OCP\Util::writeLog('file sharing', 'It is not allowed to move one mount point into another one', \OCP\Util::DEBUG); diff --git a/config/config.sample.php b/config/config.sample.php index 80694837edc..590aba714eb 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1,5 +1,11 @@ <?php +/** + * This configuration file is only provided to document the different configuration options and their usage. + * DO NOT COMPLETELY BASE YOUR CONFIGURATION FILE ON THIS SAMPLE. THIS MAY BREAK YOUR INSTANCE. + * Instead, manually copy configurations' switches that you consider important for your instance to your configuration. + */ + /* Only enable this for local development and not in productive environments */ /* This will disable the minifier and outputs some additional debug informations */ define("DEBUG", true); diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php index ab9d3e47d0e..8a16ba55e7a 100644 --- a/lib/private/connector/sabre/file.php +++ b/lib/private/connector/sabre/file.php @@ -156,7 +156,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D /** * Returns the size of the node, in bytes * - * @return int + * @return int|float */ public function getSize() { return $this->info->getSize(); diff --git a/lib/private/connector/sabre/server.php b/lib/private/connector/sabre/server.php index 2660b043f46..cf28b11163f 100644 --- a/lib/private/connector/sabre/server.php +++ b/lib/private/connector/sabre/server.php @@ -170,7 +170,7 @@ class OC_Connector_Sabre_Server extends Sabre_DAV_Server { if ($node instanceof Sabre_DAV_IFile) { $size = $node->getSize(); if (!is_null($size)) { - $newProperties[200][$prop] = (int)$node->getSize(); + $newProperties[200][$prop] = 0 + $size; } } break; diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php index 3e4f6dfb132..59963f41e3d 100644 --- a/lib/private/files/cache/cache.php +++ b/lib/private/files/cache/cache.php @@ -142,11 +142,11 @@ class Cache { } else { //fix types $data['fileid'] = (int)$data['fileid']; - $data['size'] = (int)$data['size']; + $data['size'] = 0 + $data['size']; $data['mtime'] = (int)$data['mtime']; $data['storage_mtime'] = (int)$data['storage_mtime']; $data['encrypted'] = (bool)$data['encrypted']; - $data['unencrypted_size'] = (int)$data['unencrypted_size']; + $data['unencrypted_size'] = 0 + $data['unencrypted_size']; $data['storage'] = $this->storageId; $data['mimetype'] = $this->getMimetype($data['mimetype']); $data['mimepart'] = $this->getMimetype($data['mimepart']); @@ -532,9 +532,9 @@ class Cache { $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId())); if ($row = $result->fetchRow()) { list($sum, $min, $unencryptedSum) = array_values($row); - $sum = (int)$sum; - $min = (int)$min; - $unencryptedSum = (int)$unencryptedSum; + $sum = 0 + $sum; + $min = 0 + $min; + $unencryptedSum = 0 + $unencryptedSum; if ($min === -1) { $totalSize = $min; } else { diff --git a/lib/private/files/cache/homecache.php b/lib/private/files/cache/homecache.php index 2326c46e8d0..f61769f0b9b 100644 --- a/lib/private/files/cache/homecache.php +++ b/lib/private/files/cache/homecache.php @@ -36,8 +36,10 @@ class HomeCache extends Cache { $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId())); if ($row = $result->fetchRow()) { list($sum, $unencryptedSum) = array_values($row); - $totalSize = (int)$sum; - $unencryptedSize = (int)$unencryptedSum; + $totalSize = 0 + $sum; + $unencryptedSize = 0 + $unencryptedSum; + $entry['size'] += 0; + $entry['unencrypted_size'] += 0; if ($entry['size'] !== $totalSize) { $this->update($id, array('size' => $totalSize)); } diff --git a/lib/private/files/mount/mount.php b/lib/private/files/mount/mount.php index d4a4e186fb9..7c40853ac95 100644 --- a/lib/private/files/mount/mount.php +++ b/lib/private/files/mount/mount.php @@ -161,6 +161,6 @@ class Mount { * @param callable $wrapper */ public function wrapStorage($wrapper) { - $this->storage = $wrapper($this->mountPoint, $this->storage); + $this->storage = $wrapper($this->mountPoint, $this->getStorage()); } } diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php index 1ed0d79817b..6b11603323a 100644 --- a/lib/private/files/storage/common.php +++ b/lib/private/files/storage/common.php @@ -7,6 +7,7 @@ */ namespace OC\Files\Storage; + use OC\Files\Filesystem; use OC\Files\Cache\Watcher; @@ -21,7 +22,6 @@ use OC\Files\Cache\Watcher; * Some \OC\Files\Storage\Common methods call functions which are first defined * in classes which extend it, e.g. $this->stat() . */ - abstract class Common implements \OC\Files\Storage\Storage { protected $cache; protected $scanner; @@ -46,7 +46,7 @@ abstract class Common implements \OC\Files\Storage\Storage { protected function remove($path) { if ($this->is_dir($path)) { return $this->rmdir($path); - } else if($this->is_file($path)) { + } else if ($this->is_file($path)) { return $this->unlink($path); } else { return false; @@ -412,4 +412,14 @@ abstract class Common implements \OC\Files\Storage\Storage { protected function removeCachedFile($path) { unset($this->cachedFiles[$path]); } + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class) { + return is_a($this, $class); + } } diff --git a/lib/private/files/storage/local.php b/lib/private/files/storage/local.php index 943c4163088..e33747bbd52 100644 --- a/lib/private/files/storage/local.php +++ b/lib/private/files/storage/local.php @@ -89,11 +89,10 @@ if (\OC_Util::runningOnWindows()) { public function stat($path) { $fullPath = $this->datadir . $path; $statResult = stat($fullPath); - - if ($statResult['size'] < 0) { - $size = self::getFileSizeFromOS($fullPath); - $statResult['size'] = $size; - $statResult[7] = $size; + if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { + $filesize = $this->filesize($path); + $statResult['size'] = $filesize; + $statResult[7] = $filesize; } return $statResult; } @@ -109,15 +108,13 @@ if (\OC_Util::runningOnWindows()) { public function filesize($path) { if ($this->is_dir($path)) { return 0; - } else { - $fullPath = $this->datadir . $path; - $fileSize = filesize($fullPath); - if ($fileSize < 0) { - return self::getFileSizeFromOS($fullPath); - } - - return $fileSize; } + $fullPath = $this->datadir . $path; + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper; + return $helper->getFilesize($fullPath); + } + return filesize($fullPath); } public function isReadable($path) { @@ -220,35 +217,6 @@ if (\OC_Util::runningOnWindows()) { return $return; } - /** - * @param string $fullPath - */ - private static function getFileSizeFromOS($fullPath) { - $name = strtolower(php_uname('s')); - // Windows OS: we use COM to access the filesystem - if (strpos($name, 'win') !== false) { - if (class_exists('COM')) { - $fsobj = new \COM("Scripting.FileSystemObject"); - $f = $fsobj->GetFile($fullPath); - return $f->Size; - } - } else if (strpos($name, 'bsd') !== false) { - if (\OC_Helper::is_function_enabled('exec')) { - return (float)exec('stat -f %z ' . escapeshellarg($fullPath)); - } - } else if (strpos($name, 'linux') !== false) { - if (\OC_Helper::is_function_enabled('exec')) { - return (float)exec('stat -c %s ' . escapeshellarg($fullPath)); - } - } else { - \OC_Log::write('core', - 'Unable to determine file size of "' . $fullPath . '". Unknown OS: ' . $name, - \OC_Log::ERROR); - } - - return 0; - } - public function hash($type, $path, $raw = false) { return hash_file($type, $this->datadir . $path, $raw); } diff --git a/lib/private/files/storage/mappedlocal.php b/lib/private/files/storage/mappedlocal.php index 3ebdcf9538f..ea4deaa66e8 100644 --- a/lib/private/files/storage/mappedlocal.php +++ b/lib/private/files/storage/mappedlocal.php @@ -111,11 +111,10 @@ class MappedLocal extends \OC\Files\Storage\Common { public function stat($path) { $fullPath = $this->buildPath($path); $statResult = stat($fullPath); - - if ($statResult['size'] < 0) { - $size = self::getFileSizeFromOS($fullPath); - $statResult['size'] = $size; - $statResult[7] = $size; + if (PHP_INT_SIZE === 4 && !$this->is_dir($path)) { + $filesize = $this->filesize($path); + $statResult['size'] = $filesize; + $statResult[7] = $filesize; } return $statResult; } @@ -131,15 +130,13 @@ class MappedLocal extends \OC\Files\Storage\Common { public function filesize($path) { if ($this->is_dir($path)) { return 0; - } else { - $fullPath = $this->buildPath($path); - $fileSize = filesize($fullPath); - if ($fileSize < 0) { - return self::getFileSizeFromOS($fullPath); - } - - return $fileSize; } + $fullPath = $this->buildPath($path); + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper; + return $helper->getFilesize($fullPath); + } + return filesize($fullPath); } public function isReadable($path) { @@ -294,35 +291,6 @@ class MappedLocal extends \OC\Files\Storage\Common { return $return; } - /** - * @param string $fullPath - */ - private static function getFileSizeFromOS($fullPath) { - $name = strtolower(php_uname('s')); - // Windows OS: we use COM to access the filesystem - if (strpos($name, 'win') !== false) { - if (class_exists('COM')) { - $fsobj = new \COM("Scripting.FileSystemObject"); - $f = $fsobj->GetFile($fullPath); - return $f->Size; - } - } else if (strpos($name, 'bsd') !== false) { - if (\OC_Helper::is_function_enabled('exec')) { - return (float)exec('stat -f %z ' . escapeshellarg($fullPath)); - } - } else if (strpos($name, 'linux') !== false) { - if (\OC_Helper::is_function_enabled('exec')) { - return (float)exec('stat -c %s ' . escapeshellarg($fullPath)); - } - } else { - \OC_Log::write('core', - 'Unable to determine file size of "' . $fullPath . '". Unknown OS: ' . $name, - \OC_Log::ERROR); - } - - return 0; - } - public function hash($type, $path, $raw = false) { return hash_file($type, $this->buildPath($path), $raw); } diff --git a/lib/private/files/storage/wrapper/wrapper.php b/lib/private/files/storage/wrapper/wrapper.php index 11ea9f71da7..364475a68e0 100644 --- a/lib/private/files/storage/wrapper/wrapper.php +++ b/lib/private/files/storage/wrapper/wrapper.php @@ -440,4 +440,25 @@ class Wrapper implements \OC\Files\Storage\Storage { public function isLocal() { return $this->storage->isLocal(); } + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class) { + return is_a($this, $class) or $this->storage->instanceOfStorage($class); + } + + /** + * Pass any methods custom to specific storage implementations to the wrapped storage + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) { + return call_user_func_array(array($this->storage, $method), $args); + } } diff --git a/lib/private/largefilehelper.php b/lib/private/largefilehelper.php new file mode 100644 index 00000000000..293e09fe2c9 --- /dev/null +++ b/lib/private/largefilehelper.php @@ -0,0 +1,192 @@ +<?php +/** + * Copyright (c) 2014 Andreas Fischer <bantu@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC; + +/** + * Helper class for large files on 32-bit platforms. + */ +class LargeFileHelper { + /** + * pow(2, 53) as a base-10 string. + * @var string + */ + const POW_2_53 = '9007199254740992'; + + /** + * pow(2, 53) - 1 as a base-10 string. + * @var string + */ + const POW_2_53_MINUS_1 = '9007199254740991'; + + /** + * @brief Checks whether our assumptions hold on the PHP platform we are on. + * + * @throws \RunTimeException if our assumptions do not hold on the current + * PHP platform. + */ + public function __construct() { + $pow_2_53 = floatval(self::POW_2_53_MINUS_1) + 1.0; + if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) { + throw new \RunTimeException( + 'This class assumes floats to be double precision or "better".' + ); + } + } + + /** + * @brief Formats a signed integer or float as an unsigned integer base-10 + * string. Passed strings will be checked for being base-10. + * + * @param int|float|string $number Number containing unsigned integer data + * + * @throws \UnexpectedValueException if $number is not a float, not an int + * and not a base-10 string. + * + * @return string Unsigned integer base-10 string + */ + public function formatUnsignedInteger($number) { + if (is_float($number)) { + // Undo the effect of the php.ini setting 'precision'. + return number_format($number, 0, '', ''); + } else if (is_string($number) && ctype_digit($number)) { + return $number; + } else if (is_int($number)) { + // Interpret signed integer as unsigned integer. + return sprintf('%u', $number); + } else { + throw new \UnexpectedValueException( + 'Expected int, float or base-10 string' + ); + } + } + + /** + * @brief Tries to get the size of a file via various workarounds that + * even work for large files on 32-bit platforms. + * + * @param string $filename Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSize($filename) { + $fileSize = $this->getFileSizeViaCurl($filename); + if (!is_null($fileSize)) { + return $fileSize; + } + $fileSize = $this->getFileSizeViaCOM($filename); + if (!is_null($fileSize)) { + return $fileSize; + } + $fileSize = $this->getFileSizeViaExec($filename); + if (!is_null($fileSize)) { + return $fileSize; + } + return $this->getFileSizeNative($filename); + } + + /** + * @brief Tries to get the size of a file via a CURL HEAD request. + * + * @param string $filename Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSizeViaCurl($filename) { + if (function_exists('curl_init')) { + $ch = curl_init("file://$filename"); + curl_setopt($ch, CURLOPT_NOBODY, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + $data = curl_exec($ch); + curl_close($ch); + if ($data !== false) { + $matches = array(); + preg_match('/Content-Length: (\d+)/', $data, $matches); + if (isset($matches[1])) { + return 0 + $matches[1]; + } + } + } + return null; + } + + /** + * @brief Tries to get the size of a file via the Windows DOM extension. + * + * @param string $filename Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSizeViaCOM($filename) { + if (class_exists('COM')) { + $fsobj = new \COM("Scripting.FileSystemObject"); + $file = $fsobj->GetFile($filename); + return 0 + $file->Size; + } + return null; + } + + /** + * @brief Tries to get the size of a file via an exec() call. + * + * @param string $filename Path to the file. + * + * @return null|int|float Number of bytes as number (float or int) or + * null on failure. + */ + public function getFileSizeViaExec($filename) { + if (\OC_Helper::is_function_enabled('exec')) { + $os = strtolower(php_uname('s')); + $arg = escapeshellarg($filename); + $result = ''; + if (strpos($os, 'linux') !== false) { + $result = $this->exec("stat -c %s $arg"); + } else if (strpos($os, 'bsd') !== false) { + $result = $this->exec("stat -f %z $arg"); + } else if (strpos($os, 'win') !== false) { + $result = $this->exec("for %F in ($arg) do @echo %~zF"); + if (is_null($result)) { + // PowerShell + $result = $this->exec("(Get-Item $arg).length"); + } + } + return $result; + } + return null; + } + + /** + * @brief Gets the size of a file via a filesize() call and converts + * negative signed int to positive float. As the result of filesize() + * will wrap around after a file size of 2^32 bytes = 4 GiB, this + * should only be used as a last resort. + * + * @param string $filename Path to the file. + * + * @return int|float Number of bytes as number (float or int). + */ + public function getFileSizeNative($filename) { + $result = filesize($filename); + if ($result < 0) { + // For file sizes between 2 GiB and 4 GiB, filesize() will return a + // negative int, as the PHP data type int is signed. Interpret the + // returned int as an unsigned integer and put it into a float. + return (float) sprintf('%u', $result); + } + return $result; + } + + protected function exec($cmd) { + $result = trim(exec($cmd)); + return ctype_digit($result) ? 0 + $result : null; + } +} diff --git a/lib/private/util.php b/lib/private/util.php index 23c7053002c..bf7f39ebb20 100755 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -57,7 +57,10 @@ class OC_Util { // set up quota for home storages, even for other users // which can happen when using sharing - if ($storage instanceof \OC\Files\Storage\Home) { + /** + * @var \OC\Files\Storage\Storage $storage + */ + if ($storage->instanceOfStorage('\OC\Files\Storage\Home')) { $user = $storage->getUser()->getUID(); $quota = OC_Util::getUserQuota($user); if ($quota !== \OC\Files\SPACE_UNLIMITED) { diff --git a/lib/public/files/storage.php b/lib/public/files/storage.php index 5ec8ac6245c..323d20db564 100644 --- a/lib/public/files/storage.php +++ b/lib/public/files/storage.php @@ -327,4 +327,12 @@ interface Storage { * @return bool true if the files are stored locally, false otherwise */ public function isLocal(); + + /** + * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class + * + * @param string $class + * @return bool + */ + public function instanceOfStorage($class); } diff --git a/tests/lib/files/storage/storage.php b/tests/lib/files/storage/storage.php index 24390f05367..dd73491d7ee 100644 --- a/tests/lib/files/storage/storage.php +++ b/tests/lib/files/storage/storage.php @@ -464,4 +464,10 @@ abstract class Storage extends \PHPUnit_Framework_TestCase { $this->assertEquals('foo', $this->instance->file_get_contents('target/test1.txt')); } + + public function testInstanceOfStorage() { + $this->assertTrue($this->instance->instanceOfStorage('\OCP\Files\Storage')); + $this->assertTrue($this->instance->instanceOfStorage(get_class($this->instance))); + $this->assertFalse($this->instance->instanceOfStorage('\OC')); + } } diff --git a/tests/lib/files/storage/wrapper/quota.php b/tests/lib/files/storage/wrapper/quota.php index 777529fd85e..954fe199cc8 100644 --- a/tests/lib/files/storage/wrapper/quota.php +++ b/tests/lib/files/storage/wrapper/quota.php @@ -155,4 +155,10 @@ class Quota extends \Test\Files\Storage\Storage { $this->assertEquals(1024 - 50, $instance->free_space('')); } + + public function testInstanceOfStorageWrapper() { + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Local')); + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Wrapper')); + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')); + } } diff --git a/tests/lib/files/storage/wrapper/wrapper.php b/tests/lib/files/storage/wrapper/wrapper.php index e31abfc7324..8bcf42035d4 100644 --- a/tests/lib/files/storage/wrapper/wrapper.php +++ b/tests/lib/files/storage/wrapper/wrapper.php @@ -23,4 +23,9 @@ class Wrapper extends \Test\Files\Storage\Storage { public function tearDown() { \OC_Helper::rmdirr($this->tmpDir); } + + public function testInstanceOfStorageWrapper() { + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Local')); + $this->assertTrue($this->instance->instanceOfStorage('\OC\Files\Storage\Wrapper\Wrapper')); + } } diff --git a/tests/lib/largefilehelper.php b/tests/lib/largefilehelper.php new file mode 100644 index 00000000000..5db1f9c5a74 --- /dev/null +++ b/tests/lib/largefilehelper.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright (c) 2014 Andreas Fischer <bantu@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test; + +class LargeFileHelper extends \PHPUnit_Framework_TestCase { + protected $helper; + + public function setUp() { + parent::setUp(); + $this->helper = new \OC\LargeFileHelper; + } + + public function testFormatUnsignedIntegerFloat() { + $this->assertSame( + '9007199254740992', + $this->helper->formatUnsignedInteger((float) 9007199254740992) + ); + } + + public function testFormatUnsignedIntegerInt() { + $this->assertSame( + PHP_INT_SIZE === 4 ? '4294967295' : '18446744073709551615', + $this->helper->formatUnsignedInteger(-1) + ); + } + + public function testFormatUnsignedIntegerString() { + $this->assertSame( + '9007199254740993', + $this->helper->formatUnsignedInteger('9007199254740993') + ); + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testFormatUnsignedIntegerStringException() { + $this->helper->formatUnsignedInteger('900ABCD254740993'); + } +} diff --git a/tests/lib/largefilehelpergetfilesize.php b/tests/lib/largefilehelpergetfilesize.php new file mode 100644 index 00000000000..86ce6d295cf --- /dev/null +++ b/tests/lib/largefilehelpergetfilesize.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright (c) 2014 Andreas Fischer <bantu@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test; + +/** +* Tests whether LargeFileHelper is able to determine file size at all. +* Large files are not considered yet. +*/ +class LargeFileHelperGetFileSize extends \PHPUnit_Framework_TestCase { + protected $filename; + protected $fileSize; + protected $helper; + + public function setUp() { + parent::setUp(); + $this->filename = __DIR__ . '/../data/data.tar.gz'; + $this->fileSize = 4195; + $this->helper = new \OC\LargeFileHelper; + } + + public function testGetFileSizeViaCurl() { + if (!extension_loaded('curl')) { + $this->markTestSkipped( + 'The PHP curl extension is required for this test.' + ); + } + $this->assertSame( + $this->fileSize, + $this->helper->getFileSizeViaCurl($this->filename) + ); + } + + public function testGetFileSizeViaCOM() { + if (!extension_loaded('COM')) { + $this->markTestSkipped( + 'The PHP Windows COM extension is required for this test.' + ); + } + $this->assertSame( + $this->fileSize, + $this->helper->getFileSizeViaCOM($this->filename) + ); + } + + public function testGetFileSizeViaExec() { + if (!\OC_Helper::is_function_enabled('exec')) { + $this->markTestSkipped( + 'The exec() function needs to be enabled for this test.' + ); + } + $this->assertSame( + $this->fileSize, + $this->helper->getFileSizeViaExec($this->filename) + ); + } + + public function testGetFileSizeNative() { + $this->assertSame( + $this->fileSize, + $this->helper->getFileSizeNative($this->filename) + ); + } +} |