diff options
author | Henrik Kjölhede <hkjolhede@gmail.com> | 2013-02-09 14:05:33 +0100 |
---|---|---|
committer | Henrik Kjölhede <hkjolhede@gmail.com> | 2013-02-09 14:05:33 +0100 |
commit | 41fa65e7befb249e260f2dd91e33971261cfd302 (patch) | |
tree | 4ebb4cb10f7869ba3288308ca3a35ce53970ed40 /lib/files | |
parent | b1b2eafa50db54b2613cf2bc52bfab2015d67b2f (diff) | |
parent | ec829bd345045df985f623fa2fe6587d0ce68eb2 (diff) | |
download | nextcloud-server-41fa65e7befb249e260f2dd91e33971261cfd302.tar.gz nextcloud-server-41fa65e7befb249e260f2dd91e33971261cfd302.zip |
Merge branch 'master' of https://github.com/owncloud/core
Conflicts:
apps/files_external/appinfo/app.php
Diffstat (limited to 'lib/files')
-rw-r--r-- | lib/files/cache/cache.php | 527 | ||||
-rw-r--r-- | lib/files/cache/legacy.php | 81 | ||||
-rw-r--r-- | lib/files/cache/permissions.php | 102 | ||||
-rw-r--r-- | lib/files/cache/scanner.php | 146 | ||||
-rw-r--r-- | lib/files/cache/updater.php | 105 | ||||
-rw-r--r-- | lib/files/cache/upgrade.php | 159 | ||||
-rw-r--r-- | lib/files/cache/watcher.php | 72 | ||||
-rw-r--r-- | lib/files/filesystem.php | 637 | ||||
-rw-r--r-- | lib/files/mapper.php | 216 | ||||
-rw-r--r-- | lib/files/mount.php | 188 | ||||
-rw-r--r-- | lib/files/storage/common.php | 304 | ||||
-rw-r--r-- | lib/files/storage/commontest.php | 80 | ||||
-rw-r--r-- | lib/files/storage/local.php | 252 | ||||
-rw-r--r-- | lib/files/storage/mappedlocal.php | 335 | ||||
-rw-r--r-- | lib/files/storage/storage.php | 88 | ||||
-rw-r--r-- | lib/files/storage/temporary.php | 27 | ||||
-rw-r--r-- | lib/files/stream/close.php | 100 | ||||
-rw-r--r-- | lib/files/stream/dir.php | 47 | ||||
-rw-r--r-- | lib/files/stream/oc.php | 129 | ||||
-rw-r--r-- | lib/files/stream/staticstream.php | 191 | ||||
-rw-r--r-- | lib/files/view.php | 974 |
21 files changed, 4760 insertions, 0 deletions
diff --git a/lib/files/cache/cache.php b/lib/files/cache/cache.php new file mode 100644 index 00000000000..dcb6e8fd39a --- /dev/null +++ b/lib/files/cache/cache.php @@ -0,0 +1,527 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * Metadata cache for the filesystem + * + * don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead + */ +class Cache { + const NOT_FOUND = 0; + const PARTIAL = 1; //only partial data available, file not cached in the database + const SHALLOW = 2; //folder in cache, but not all child files are completely scanned + const COMPLETE = 3; + + /** + * @var array partial data for the cache + */ + private $partial = array(); + + /** + * @var string + */ + private $storageId; + + /** + * numeric storage id + * + * @var int $numericId + */ + private $numericId; + + private $mimetypeIds = array(); + private $mimetypes = array(); + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage) { + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->storageId = $storage->getId(); + } else { + $this->storageId = $storage; + } + + $query = \OC_DB::prepare('SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?'); + $result = $query->execute(array($this->storageId)); + if ($row = $result->fetchRow()) { + $this->numericId = $row['numeric_id']; + } else { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*storages`(`id`) VALUES(?)'); + $query->execute(array($this->storageId)); + $this->numericId = \OC_DB::insertid('*PREFIX*filecache'); + } + } + + public function getNumericStorageId() { + return $this->numericId; + } + + /** + * normalize mimetypes + * + * @param string $mime + * @return int + */ + public function getMimetypeId($mime) { + if (!isset($this->mimetypeIds[$mime])) { + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?'); + $result = $query->execute(array($mime)); + if ($row = $result->fetchRow()) { + $this->mimetypeIds[$mime] = $row['id']; + } else { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)'); + $query->execute(array($mime)); + $this->mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes'); + } + $this->mimetypes[$this->mimetypeIds[$mime]] = $mime; + } + return $this->mimetypeIds[$mime]; + } + + public function getMimetype($id) { + if (!isset($this->mimetypes[$id])) { + $query = \OC_DB::prepare('SELECT `mimetype` FROM `*PREFIX*mimetypes` WHERE `id` = ?'); + $result = $query->execute(array($id)); + if ($row = $result->fetchRow()) { + $this->mimetypes[$id] = $row['mimetype']; + } else { + return null; + } + } + return $this->mimetypes[$id]; + } + + /** + * get the stored metadata of a file or folder + * + * @param string/int $file + * @return array + */ + public function get($file) { + if (is_string($file) or $file == '') { + $where = 'WHERE `storage` = ? AND `path_hash` = ?'; + $params = array($this->numericId, md5($file)); + } else { //file id + $where = 'WHERE `fileid` = ?'; + $params = array($file); + } + $query = \OC_DB::prepare( + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` + FROM `*PREFIX*filecache` ' . $where); + $result = $query->execute($params); + $data = $result->fetchRow(); + + //merge partial data + if (!$data and is_string($file)) { + if (isset($this->partial[$file])) { + $data = $this->partial[$file]; + } + } else { + //fix types + $data['fileid'] = (int)$data['fileid']; + $data['size'] = (int)$data['size']; + $data['mtime'] = (int)$data['mtime']; + $data['encrypted'] = (bool)$data['encrypted']; + $data['storage'] = $this->storageId; + $data['mimetype'] = $this->getMimetype($data['mimetype']); + $data['mimepart'] = $this->getMimetype($data['mimepart']); + } + + return $data; + } + + /** + * get the metadata of all files stored in $folder + * + * @param string $folder + * @return array + */ + public function getFolderContents($folder) { + $fileId = $this->getId($folder); + if ($fileId > -1) { + $query = \OC_DB::prepare( + 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` + FROM `*PREFIX*filecache` WHERE parent = ? ORDER BY `name` ASC'); + $result = $query->execute(array($fileId)); + $files = $result->fetchAll(); + foreach ($files as &$file) { + $file['mimetype'] = $this->getMimetype($file['mimetype']); + $file['mimepart'] = $this->getMimetype($file['mimepart']); + } + return $files; + } else { + return array(); + } + } + + /** + * store meta data for a file or folder + * + * @param string $file + * @param array $data + * + * @return int file id + */ + public function put($file, array $data) { + if (($id = $this->getId($file)) > -1) { + $this->update($id, $data); + return $id; + } else { + if (isset($this->partial[$file])) { //add any saved partial data + $data = array_merge($this->partial[$file], $data); + unset($this->partial[$file]); + } + + $requiredFields = array('size', 'mtime', 'mimetype'); + foreach ($requiredFields as $field) { + if (!isset($data[$field])) { //data not complete save as partial and return + $this->partial[$file] = $data; + return -1; + } + } + + $data['path'] = $file; + $data['parent'] = $this->getParentId($file); + $data['name'] = basename($file); + $data['encrypted'] = isset($data['encrypted']) ? ((int)$data['encrypted']) : 0; + + list($queryParts, $params) = $this->buildParts($data); + $queryParts[] = '`storage`'; + $params[] = $this->numericId; + $valuesPlaceholder = array_fill(0, count($queryParts), '?'); + + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`(' . implode(', ', $queryParts) . ') VALUES(' . implode(', ', $valuesPlaceholder) . ')'); + $query->execute($params); + + return (int)\OC_DB::insertid('*PREFIX*filecache'); + } + } + + /** + * update the metadata in the cache + * + * @param int $id + * @param array $data + */ + public function update($id, array $data) { + list($queryParts, $params) = $this->buildParts($data); + $params[] = $id; + + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE fileid = ?'); + $query->execute($params); + } + + /** + * extract query parts and params array from data array + * + * @param array $data + * @return array + */ + function buildParts(array $data) { + $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'encrypted', 'etag'); + $params = array(); + $queryParts = array(); + foreach ($data as $name => $value) { + if (array_search($name, $fields) !== false) { + if ($name === 'path') { + $params[] = md5($value); + $queryParts[] = '`path_hash`'; + } elseif ($name === 'mimetype') { + $params[] = $this->getMimetypeId(substr($value, 0, strpos($value, '/'))); + $queryParts[] = '`mimepart`'; + $value = $this->getMimetypeId($value); + } + $params[] = $value; + $queryParts[] = '`' . $name . '`'; + } + } + return array($queryParts, $params); + } + + /** + * get the file id for a file + * + * @param string $file + * @return int + */ + public function getId($file) { + $pathHash = md5($file); + + $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $result = $query->execute(array($this->numericId, $pathHash)); + + if ($row = $result->fetchRow()) { + return $row['fileid']; + } else { + return -1; + } + } + + /** + * get the id of the parent folder of a file + * + * @param string $file + * @return int + */ + public function getParentId($file) { + if ($file === '') { + return -1; + } else { + $parent = dirname($file); + if ($parent === '.') { + $parent = ''; + } + return $this->getId($parent); + } + } + + /** + * check if a file is available in the cache + * + * @param string $file + * @return bool + */ + public function inCache($file) { + return $this->getId($file) != -1; + } + + /** + * remove a file or folder from the cache + * + * @param string $file + */ + public function remove($file) { + $entry = $this->get($file); + if ($entry['mimetype'] === 'httpd/unix-directory') { + $children = $this->getFolderContents($file); + foreach ($children as $child) { + $this->remove($child['path']); + } + } + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?'); + $query->execute(array($entry['fileid'])); + } + + /** + * Move a file or folder in the cache + * + * @param string $source + * @param string $target + */ + public function move($source, $target) { + $sourceId = $this->getId($source); + $newParentId = $this->getParentId($target); + + //find all child entries + $query = \OC_DB::prepare('SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `path` LIKE ?'); + $result = $query->execute(array($source . '/%')); + $childEntries = $result->fetchAll(); + $sourceLength = strlen($source); + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ? WHERE `fileid` = ?'); + + foreach ($childEntries as $child) { + $targetPath = $target . substr($child['path'], $sourceLength); + $query->execute(array($targetPath, md5($targetPath), $child['fileid'])); + } + + $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `parent` =? WHERE `fileid` = ?'); + $query->execute(array($target, md5($target), $newParentId, $sourceId)); + } + + /** + * remove all entries for files that are stored on the storage from the cache + */ + public function clear() { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*filecache` WHERE storage = ?'); + $query->execute(array($this->numericId)); + + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*storages` WHERE id = ?'); + $query->execute(array($this->storageId)); + } + + /** + * @param string $file + * + * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE + */ + public function getStatus($file) { + $pathHash = md5($file); + $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $result = $query->execute(array($this->numericId, $pathHash)); + if ($row = $result->fetchRow()) { + if ((int)$row['size'] === -1) { + return self::SHALLOW; + } else { + return self::COMPLETE; + } + } else { + if (isset($this->partial[$file])) { + return self::PARTIAL; + } else { + return self::NOT_FOUND; + } + } + } + + /** + * search for files matching $pattern + * + * @param string $pattern + * @return array of file data + */ + public function search($pattern) { + $query = \OC_DB::prepare(' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` + FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?' + ); + $result = $query->execute(array($pattern, $this->numericId)); + $files = array(); + while ($row = $result->fetchRow()) { + $row['mimetype'] = $this->getMimetype($row['mimetype']); + $row['mimepart'] = $this->getMimetype($row['mimepart']); + $files[] = $row; + } + return $files; + } + + /** + * search for files by mimetype + * + * @param string $mimetype + * @return array + */ + public function searchByMime($mimetype) { + if (strpos($mimetype, '/')) { + $where = '`mimetype` = ?'; + } else { + $where = '`mimepart` = ?'; + } + $query = \OC_DB::prepare(' + SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` + FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?' + ); + $mimetype = $this->getMimetypeId($mimetype); + $result = $query->execute(array($mimetype, $this->numericId)); + $files = array(); + while ($row = $result->fetchRow()) { + $row['mimetype'] = $this->getMimetype($row['mimetype']); + $row['mimepart'] = $this->getMimetype($row['mimepart']); + $files[] = $row; + } + return $files; + } + + /** + * update the folder size and the size of all parent folders + * + * @param $path + */ + public function correctFolderSize($path) { + $this->calculateFolderSize($path); + if ($path !== '') { + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + $this->correctFolderSize($parent); + } + } + + /** + * get the size of a folder and set it in the cache + * + * @param string $path + * @return int + */ + public function calculateFolderSize($path) { + $id = $this->getId($path); + if ($id === -1) { + return 0; + } + $query = \OC_DB::prepare('SELECT `size` FROM `*PREFIX*filecache` WHERE `parent` = ? AND `storage` = ?'); + $result = $query->execute(array($id, $this->numericId)); + $totalSize = 0; + $hasChilds = 0; + while ($row = $result->fetchRow()) { + $hasChilds = true; + $size = (int)$row['size']; + if ($size === -1) { + $totalSize = -1; + break; + } else { + $totalSize += $size; + } + } + + if ($hasChilds) { + $this->update($id, array('size' => $totalSize)); + } + return $totalSize; + } + + /** + * get all file ids on the files on the storage + * + * @return int[] + */ + public function getAll() { + $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?'); + $result = $query->execute(array($this->numericId)); + $ids = array(); + while ($row = $result->fetchRow()) { + $ids[] = $row['fileid']; + } + return $ids; + } + + /** + * find a folder in the cache which has not been fully scanned + * + * If multiply incomplete folders are in the cache, the one with the highest id will be returned, + * use the one with the highest id gives the best result with the background scanner, since that is most + * likely the folder where we stopped scanning previously + * + * @return string|bool the path of the folder or false when no folder matched + */ + public function getIncomplete() { + $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC LIMIT 1'); + $query->execute(array($this->numericId)); + if ($row = $query->fetchRow()) { + return $row['path']; + } else { + return false; + } + } + + /** + * get the storage id of the storage for a file and the internal path of the file + * + * @return array, first element holding the storage id, second the path + */ + static public function getById($id) { + $query = \OC_DB::prepare('SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?'); + $result = $query->execute(array($id)); + if ($row = $result->fetchRow()) { + $numericId = $row['storage']; + $path = $row['path']; + } else { + return null; + } + + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?'); + $result = $query->execute(array($numericId)); + if ($row = $result->fetchRow()) { + return array($row['id'], $path); + } else { + return null; + } + } +} diff --git a/lib/files/cache/legacy.php b/lib/files/cache/legacy.php new file mode 100644 index 00000000000..33d4b8e7c9f --- /dev/null +++ b/lib/files/cache/legacy.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * Provide read only support for the old filecache + */ +class Legacy { + private $user; + + private $cacheHasItems = null; + + public function __construct($user) { + $this->user = $user; + } + + function getCount() { + $query = \OC_DB::prepare('SELECT COUNT(`id`) AS `count` FROM `*PREFIX*fscache` WHERE `user` = ?'); + $result = $query->execute(array($this->user)); + if ($row = $result->fetchRow()) { + return $row['count']; + } else { + return 0; + } + } + + /** + * check if a legacy cache is present and holds items + * + * @return bool + */ + function hasItems() { + if (!is_null($this->cacheHasItems)) { + return $this->cacheHasItems; + } + try { + $query = \OC_DB::prepare('SELECT `id` FROM `*PREFIX*fscache` WHERE `user` = ? LIMIT 1'); + } catch (\Exception $e) { + $this->cacheHasItems = false; + return false; + } + try { + $result = $query->execute(array($this->user)); + } catch (\Exception $e) { + $this->cacheHasItems = false; + return false; + } + $this->cacheHasItems = (bool)$result->fetchRow(); + return $this->cacheHasItems; + } + + /** + * @param string|int $path + * @return array + */ + function get($path) { + if (is_numeric($path)) { + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `id` = ?'); + } else { + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `path` = ?'); + } + $result = $query->execute(array($path)); + return $result->fetchRow(); + } + + /** + * @param int $id + * @return array + */ + function getChildren($id) { + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*fscache` WHERE `parent` = ?'); + $result = $query->execute(array($id)); + return $result->fetchAll(); + } +} diff --git a/lib/files/cache/permissions.php b/lib/files/cache/permissions.php new file mode 100644 index 00000000000..d0968337f02 --- /dev/null +++ b/lib/files/cache/permissions.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Permissions { + /** + * @var string $storageId + */ + private $storageId; + + /** + * @param \OC\Files\Storage\Storage|string $storage + */ + public function __construct($storage){ + if($storage instanceof \OC\Files\Storage\Storage){ + $this->storageId = $storage->getId(); + }else{ + $this->storageId = $storage; + } + } + + /** + * get the permissions for a single file + * + * @param int $fileId + * @param string $user + * @return int (-1 if file no permissions set) + */ + public function get($fileId, $user) { + $query = \OC_DB::prepare('SELECT `permissions` FROM `*PREFIX*permissions` WHERE `user` = ? AND `fileid` = ?'); + $result = $query->execute(array($user, $fileId)); + if ($row = $result->fetchRow()) { + return $row['permissions']; + } else { + return -1; + } + } + + /** + * set the permissions of a file + * + * @param int $fileId + * @param string $user + * @param int $permissions + */ + public function set($fileId, $user, $permissions) { + if (self::get($fileId, $user) !== -1) { + $query = \OC_DB::prepare('UPDATE `*PREFIX*permissions` SET `permissions` = ? WHERE `user` = ? AND `fileid` = ?'); + } else { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*permissions`(`permissions`, `user`, `fileid`) VALUES(?, ?,? )'); + } + $query->execute(array($permissions, $user, $fileId)); + } + + /** + * get the permissions of multiply files + * + * @param int[] $fileIds + * @param string $user + * @return int[] + */ + public function getMultiple($fileIds, $user) { + if (count($fileIds) === 0) { + return array(); + } + $params = $fileIds; + $params[] = $user; + $inPart = implode(', ', array_fill(0, count($fileIds), '?')); + + $query = \OC_DB::prepare('SELECT `fileid`, `permissions` FROM `*PREFIX*permissions` WHERE `fileid` IN (' . $inPart . ') AND `user` = ?'); + $result = $query->execute($params); + $filePermissions = array(); + while ($row = $result->fetchRow()) { + $filePermissions[$row['fileid']] = $row['permissions']; + } + return $filePermissions; + } + + /** + * remove the permissions for a file + * + * @param int $fileId + * @param string $user + */ + public function remove($fileId, $user) { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'); + $query->execute(array($fileId, $user)); + } + + public function removeMultiple($fileIds, $user) { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?'); + foreach($fileIds as $fileId){ + $query->execute(array($fileId, $user)); + } + } +} diff --git a/lib/files/cache/scanner.php b/lib/files/cache/scanner.php new file mode 100644 index 00000000000..8d504af6163 --- /dev/null +++ b/lib/files/cache/scanner.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Scanner { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var string $storageId + */ + private $storageId; + + /** + * @var \OC\Files\Cache\Cache $cache + */ + private $cache; + + const SCAN_RECURSIVE = true; + const SCAN_SHALLOW = false; + + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->storageId = $this->storage->getId(); + $this->cache = $storage->getCache(); + } + + /** + * get all the metadata of a file or folder + * * + * + * @param string $path + * @return array with metadata of the file + */ + public function getData($path) { + $data = array(); + if (!$this->storage->isReadable($path)) return null; //cant read, nothing we can do + $data['mimetype'] = $this->storage->getMimeType($path); + $data['mtime'] = $this->storage->filemtime($path); + if ($data['mimetype'] == 'httpd/unix-directory') { + $data['size'] = -1; //unknown + } else { + $data['size'] = $this->storage->filesize($path); + } + $data['etag'] = $this->storage->getETag($path); + return $data; + } + + /** + * scan a single file and store it in the cache + * + * @param string $file + * @return array with metadata of the scanned file + */ + public function scanFile($file) { + \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId)); + $data = $this->getData($file); + if ($data) { + if ($file) { + $parent = dirname($file); + if ($parent === '.') { + $parent = ''; + } + if (!$this->cache->inCache($parent)) { + $this->scanFile($parent); + } + } + $id = $this->cache->put($file, $data); + } + return $data; + } + + /** + * scan all the files in a folder and store them in the cache + * + * @param string $path + * @param SCAN_RECURSIVE/SCAN_SHALLOW $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); + } + + $size = 0; + if ($this->storage->is_dir($path) && ($dh = $this->storage->opendir($path))) { + \OC_DB::beginTransaction(); + while ($file = readdir($dh)) { + if ($file !== '.' and $file !== '..') { + $child = ($path) ? $path . '/' . $file : $file; + $data = $this->scanFile($child); + if ($data) { + if ($data['mimetype'] === 'httpd/unix-directory') { + if ($recursive === self::SCAN_RECURSIVE) { + $childQueue[] = $child; + $data['size'] = 0; + } else { + $data['size'] = -1; + } + } else { + } + if ($data['size'] === -1) { + $size = -1; + } elseif ($size !== -1) { + $size += $data['size']; + } + } + } + } + \OC_DB::commit(); + foreach ($childQueue as $child) { + $childSize = $this->scan($child, self::SCAN_RECURSIVE, true); + if ($childSize === -1) { + $size = -1; + } else { + $size += $childSize; + } + } + if ($size !== -1) { + $this->cache->put($path, array('size' => $size)); + } + } + return $size; + } + + /** + * walk over any folders that are not fully scanned yet and scan them + */ + public function backgroundScan() { + while ($path = $this->cache->getIncomplete()) { + $this->scan($path); + $this->cache->correctFolderSize($path); + } + } +} diff --git a/lib/files/cache/updater.php b/lib/files/cache/updater.php new file mode 100644 index 00000000000..d04541c219f --- /dev/null +++ b/lib/files/cache/updater.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * listen to filesystem hooks and change the cache accordingly + */ +class Updater { + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + $view = \OC\Files\Filesystem::getView(); + return $view->resolvePath($path); + } + + static public function writeUpdate($path) { + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Scanner::SCAN_SHALLOW); + $cache->correctFolderSize($internalPath); + self::correctFolder($path, $storage->filemtime($internalPath)); + } + } + + static public function deleteUpdate($path) { + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $cache->remove($internalPath); + $cache->correctFolderSize($internalPath); + self::correctFolder($path, time()); + } + } + + /** + * Update the mtime and ETag of all parent folders + * + * @param string $path + * @param string $time + */ + static public function correctFolder($path, $time) { + if ($path !== '' && $path !== '/') { + $parent = dirname($path); + if ($parent === '.') { + $parent = ''; + } + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = self::resolvePath($parent); + if ($storage) { + $cache = $storage->getCache(); + $id = $cache->getId($internalPath); + if ($id !== -1) { + $cache->update($id, array('mtime' => $time, 'etag' => $storage->getETag($internalPath))); + self::correctFolder($parent, $time); + } + } + } + } + + /** + * @param array $params + */ + static public function writeHook($params) { + self::writeUpdate($params['path']); + } + + /** + * @param array $params + */ + static public function renameHook($params) { + self::deleteUpdate($params['oldpath']); + self::writeUpdate($params['newpath']); + } + + /** + * @param array $params + */ + static public function deleteHook($params) { + self::deleteUpdate($params['path']); + } +} diff --git a/lib/files/cache/upgrade.php b/lib/files/cache/upgrade.php new file mode 100644 index 00000000000..eb8c7297c3e --- /dev/null +++ b/lib/files/cache/upgrade.php @@ -0,0 +1,159 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +class Upgrade { + /** + * @var Legacy $legacy + */ + private $legacy; + + private $numericIds = array(); + + private $mimeTypeIds = array(); + + /** + * @param Legacy $legacy + */ + public function __construct($legacy) { + $this->legacy = $legacy; + } + + /** + * Preform a shallow upgrade + * + * @param string $path + * @param int $mode + */ + function upgradePath($path, $mode = Scanner::SCAN_RECURSIVE) { + if (!$this->legacy->hasItems()) { + return; + } + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $path); + + if ($row = $this->legacy->get($path)) { + $data = $this->getNewData($row); + $this->insert($data); + + $this->upgradeChilds($data['id'], $mode); + } + } + + /** + * @param int $id + */ + function upgradeChilds($id, $mode = Scanner::SCAN_RECURSIVE) { + $children = $this->legacy->getChildren($id); + foreach ($children as $child) { + $childData = $this->getNewData($child); + \OC_Hook::emit('\OC\Files\Cache\Upgrade', 'migrate_path', $child['path']); + $this->insert($childData); + if ($mode == Scanner::SCAN_RECURSIVE) { + $this->upgradeChilds($child['id']); + } + } + } + + /** + * @param array $data the data for the new cache + */ + function insert($data) { + if (!$this->inCache($data['storage'], $data['path_hash'])) { + $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache` + ( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted` ) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); + + $insertQuery->execute(array($data['id'], $data['storage'], $data['path'], $data['path_hash'], $data['parent'], $data['name'], + $data['mimetype'], $data['mimepart'], $data['size'], $data['mtime'], $data['encrypted'])); + } + } + + /** + * @param string $storage + * @param string $pathHash + * @return bool + */ + function inCache($storage, $pathHash) { + $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?'); + $result = $query->execute(array($storage, $pathHash)); + return (bool)$result->fetchRow(); + } + + /** + * get the new data array from the old one + * + * @param array $data the data from the old cache + * @return array + */ + function getNewData($data) { + $newData = $data; + list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($data['path']); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath; + */ + $newData['path_hash'] = md5($internalPath); + $newData['path'] = $internalPath; + $newData['storage'] = $this->getNumericId($storage); + $newData['parent'] = ($internalPath === '') ? -1 : $data['parent']; + $newData['permissions'] = ($data['writable']) ? \OCP\PERMISSION_ALL : \OCP\PERMISSION_READ; + $newData['storage_object'] = $storage; + $newData['mimetype'] = $this->getMimetypeId($newData['mimetype'], $storage); + $newData['mimepart'] = $this->getMimetypeId($newData['mimepart'], $storage); + return $newData; + } + + /** + * get the numeric storage id + * + * @param \OC\Files\Storage\Storage $storage + * @return int + */ + function getNumericId($storage) { + $storageId = $storage->getId(); + if (!isset($this->numericIds[$storageId])) { + $cache = $storage->getCache(); + $this->numericIds[$storageId] = $cache->getNumericStorageId(); + } + return $this->numericIds[$storageId]; + } + + /** + * @param string $mimetype + * @param \OC\Files\Storage\Storage $storage + * @return int + */ + function getMimetypeId($mimetype, $storage) { + if (!isset($this->mimeTypeIds[$mimetype])) { + $cache = new Cache($storage); + $this->mimeTypeIds[$mimetype] = $cache->getMimetypeId($mimetype); + } + return $this->mimeTypeIds[$mimetype]; + } + + /** + * check if a cache upgrade is required for $user + * + * @param string $user + * @return bool + */ + static function needUpgrade($user) { + $cacheVersion = (int)\OCP\Config::getUserValue($user, 'files', 'cache_version', 4); + return $cacheVersion < 5; + } + + /** + * mark the filecache as upgrade + * + * @param string $user + */ + static function upgradeDone($user) { + \OCP\Config::setUserValue($user, 'files', 'cache_version', 5); + } +} diff --git a/lib/files/cache/watcher.php b/lib/files/cache/watcher.php new file mode 100644 index 00000000000..31059ec7f56 --- /dev/null +++ b/lib/files/cache/watcher.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Cache; + +/** + * check the storage backends for updates and change the cache accordingly + */ +class Watcher { + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage; + + /** + * @var Cache $cache + */ + private $cache; + + /** + * @var Scanner $scanner; + */ + private $scanner; + + /** + * @param \OC\Files\Storage\Storage $storage + */ + public function __construct(\OC\Files\Storage\Storage $storage) { + $this->storage = $storage; + $this->cache = $storage->getCache(); + $this->scanner = $storage->getScanner(); + } + + /** + * check $path for updates + * + * @param string $path + */ + public function checkUpdate($path) { + $cachedEntry = $this->cache->get($path); + if ($this->storage->hasUpdated($path, $cachedEntry['mtime'])) { + if ($this->storage->is_dir($path)) { + $this->scanner->scan($path, Scanner::SCAN_SHALLOW); + } else { + $this->scanner->scanFile($path); + } + if ($cachedEntry['mimetype'] === 'httpd/unix-directory') { + $this->cleanFolder($path); + } + $this->cache->correctFolderSize($path); + } + } + + /** + * remove deleted files in $path from the cache + * + * @param string $path + */ + public function cleanFolder($path) { + $cachedContent = $this->cache->getFolderContents($path); + foreach ($cachedContent as $entry) { + if (!$this->storage->file_exists($entry['path'])) { + $this->cache->remove($entry['path']); + } + } + } +} diff --git a/lib/files/filesystem.php b/lib/files/filesystem.php new file mode 100644 index 00000000000..71bf3d8708d --- /dev/null +++ b/lib/files/filesystem.php @@ -0,0 +1,637 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class for abstraction of filesystem functions + * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object + * this class should also handle all the file permission related stuff + * + * Hooks provided: + * read(path) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emitted in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order) + * post_rename(oldpath,newpath) + * + * the &run parameter can be set to false to prevent the operation from occurring + */ + +namespace OC\Files; + +class Filesystem { + public static $loaded = false; + /** + * @var \OC\Files\View $defaultInstance + */ + static private $defaultInstance; + + + /** + * classname which used for hooks handling + * used as signalclass in OC_Hooks::emit() + */ + const CLASSNAME = 'OC_Filesystem'; + + /** + * signalname emitted before file renaming + * + * @param string $oldpath + * @param string $newpath + */ + const signal_rename = 'rename'; + + /** + * signal emitted after file renaming + * + * @param string $oldpath + * @param string $newpath + */ + const signal_post_rename = 'post_rename'; + + /** + * signal emitted before file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_create = 'create'; + + /** + * signal emitted after file/dir creation + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_post_create = 'post_create'; + + /** + * signal emits before file/dir copy + * + * @param string $oldpath + * @param string $newpath + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_copy = 'copy'; + + /** + * signal emits after file/dir copy + * + * @param string $oldpath + * @param string $newpath + */ + const signal_post_copy = 'post_copy'; + + /** + * signal emits before file/dir save + * + * @param string $path + * @param bool $run changing this flag to false in hook handler will cancel event + */ + const signal_write = 'write'; + + /** + * signal emits after file/dir save + * + * @param string $path + */ + const signal_post_write = 'post_write'; + + /** + * signal emits when reading file/dir + * + * @param string $path + */ + const signal_read = 'read'; + + /** + * signal emits when removing file/dir + * + * @param string $path + */ + const signal_delete = 'delete'; + + /** + * parameters definitions for signals + */ + const signal_param_path = 'path'; + const signal_param_oldpath = 'oldpath'; + const signal_param_newpath = 'newpath'; + + /** + * run - changing this flag to false in hook handler will cancel event + */ + const signal_param_run = 'run'; + + /** + * get the mountpoint of the storage object for a path + ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account + * + * @param string $path + * @return string + */ + static public function getMountPoint($path) { + $mount = Mount::find($path); + if ($mount) { + return $mount->getMountPoint(); + } else { + return ''; + } + } + + /** + * get a list of all mount points in a directory + * + * @param string $path + * @return string[] + */ + static public function getMountPoints($path) { + $result = array(); + $mounts = Mount::findIn($path); + foreach ($mounts as $mount) { + $result[] = $mount->getMountPoint(); + } + return $result; + } + + /** + * get the storage mounted at $mountPoint + * + * @param string $mountPoint + * @return \OC\Files\Storage\Storage + */ + public static function getStorage($mountPoint) { + $mount = Mount::find($mountPoint); + return $mount->getStorage(); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + static public function resolvePath($path) { + $mount = Mount::find($path); + if ($mount) { + return array($mount->getStorage(), $mount->getInternalPath($path)); + } else { + return array(null, null); + } + } + + static public function init($root) { + if (self::$defaultInstance) { + return false; + } + self::$defaultInstance = new View($root); + + //load custom mount config + self::initMountPoints(); + + self::$loaded = true; + + return true; + } + + /** + * Initialize system and personal mount points for a user + * + * @param string $user + */ + public static function initMountPoints($user = '') { + if ($user == '') { + $user = \OC_User::getUser(); + } + // Load system mount points + if (is_file(\OC::$SERVERROOT . '/config/mount.php')) { + $mountConfig = include 'config/mount.php'; + if (isset($mountConfig['global'])) { + foreach ($mountConfig['global'] as $mountPoint => $options) { + self::mount($options['class'], $options['options'], $mountPoint); + } + } + if (isset($mountConfig['group'])) { + foreach ($mountConfig['group'] as $group => $mounts) { + if (\OC_Group::inGroup($user, $group)) { + foreach ($mounts as $mountPoint => $options) { + $mountPoint = self::setUserVars($user, $mountPoint); + foreach ($options as &$option) { + $option = self::setUserVars($user, $option); + } + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + if (isset($mountConfig['user'])) { + foreach ($mountConfig['user'] as $mountUser => $mounts) { + if ($user === 'all' or strtolower($mountUser) === strtolower($user)) { + foreach ($mounts as $mountPoint => $options) { + $mountPoint = self::setUserVars($user, $mountPoint); + foreach ($options as &$option) { + $option = self::setUserVars($user, $option); + } + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + } + // Load personal mount points + $root = \OC_User::getHome($user); + self::mount('\OC\Files\Storage\Local', array('datadir' => $root), $user); + if (is_file($root . '/mount.php')) { + $mountConfig = include $root . '/mount.php'; + if (isset($mountConfig['user'][$user])) { + foreach ($mountConfig['user'][$user] as $mountPoint => $options) { + self::mount($options['class'], $options['options'], $mountPoint); + } + } + } + } + + /** + * fill in the correct values for $user, and $password placeholders + * + * @param string $input + * @param string $input + * @return string + */ + private static function setUserVars($user, $input) { + return str_replace('$user', $user, $input); + } + + /** + * get the default filesystem view + * + * @return View + */ + static public function getView() { + return self::$defaultInstance; + } + + /** + * tear down the filesystem, removing all storage providers + */ + static public function tearDown() { + self::clearMounts(); + } + + /** + * @brief get the relative path of the root data directory for the current user + * @return string + * + * Returns path like /admin/files + */ + static public function getRoot() { + return self::$defaultInstance->getRoot(); + } + + /** + * clear all mounts and storage backends + */ + public static function clearMounts() { + Mount::clear(); + } + + /** + * mount an \OC\Files\Storage\Storage in our virtual filesystem + * + * @param \OC\Files\Storage\Storage|string $class + * @param array $arguments + * @param string $mountpoint + */ + static public function mount($class, $arguments, $mountpoint) { + new Mount($class, $mountpoint, $arguments); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + static public function getLocalFile($path) { + return self::$defaultInstance->getLocalFile($path); + } + + /** + * @param string $path + * @return string + */ + static public function getLocalFolder($path) { + return self::$defaultInstance->getLocalFolder($path); + } + + /** + * return path to file which reflects one visible in browser + * + * @param string $path + * @return string + */ + static public function getLocalPath($path) { + $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files'; + $newpath = $path; + if (strncmp($newpath, $datadir, strlen($datadir)) == 0) { + $newpath = substr($path, strlen($datadir)); + } + return $newpath; + } + + /** + * check if the requested path is valid + * + * @param string $path + * @return bool + */ + static public function isValidPath($path) { + $path = self::normalizePath($path); + if (!$path || $path[0] !== '/') { + $path = '/' . $path; + } + if (strstr($path, '/../') || strrchr($path, '/') === '/..') { + return false; + } + return true; + } + + /** + * checks if a file is blacklisted for storage in the filesystem + * Listens to write and rename hooks + * + * @param array $data from hook + */ + static public function isBlacklisted($data) { + $blacklist = \OC_Config::getValue('blacklisted_files', array('.htaccess')); + if (isset($data['path'])) { + $path = $data['path']; + } else if (isset($data['newpath'])) { + $path = $data['newpath']; + } + if (isset($path)) { + $filename = strtolower(basename($path)); + if (in_array($filename, $blacklist)) { + $data['run'] = false; + } + } + } + + /** + * following functions are equivalent to their php builtin equivalents for arguments/return values. + */ + static public function mkdir($path) { + return self::$defaultInstance->mkdir($path); + } + + static public function rmdir($path) { + return self::$defaultInstance->rmdir($path); + } + + static public function opendir($path) { + return self::$defaultInstance->opendir($path); + } + + static public function readdir($path) { + return self::$defaultInstance->readdir($path); + } + + static public function is_dir($path) { + return self::$defaultInstance->is_dir($path); + } + + static public function is_file($path) { + return self::$defaultInstance->is_file($path); + } + + static public function stat($path) { + return self::$defaultInstance->stat($path); + } + + static public function filetype($path) { + return self::$defaultInstance->filetype($path); + } + + static public function filesize($path) { + return self::$defaultInstance->filesize($path); + } + + static public function readfile($path) { + return self::$defaultInstance->readfile($path); + } + + static public function isCreatable($path) { + return self::$defaultInstance->isCreatable($path); + } + + static public function isReadable($path) { + return self::$defaultInstance->isReadable($path); + } + + static public function isUpdatable($path) { + return self::$defaultInstance->isUpdatable($path); + } + + static public function isDeletable($path) { + return self::$defaultInstance->isDeletable($path); + } + + static public function isSharable($path) { + return self::$defaultInstance->isSharable($path); + } + + static public function file_exists($path) { + return self::$defaultInstance->file_exists($path); + } + + static public function filemtime($path) { + return self::$defaultInstance->filemtime($path); + } + + static public function touch($path, $mtime = null) { + return self::$defaultInstance->touch($path, $mtime); + } + + static public function file_get_contents($path) { + return self::$defaultInstance->file_get_contents($path); + } + + static public function file_put_contents($path, $data) { + return self::$defaultInstance->file_put_contents($path, $data); + } + + static public function unlink($path) { + return self::$defaultInstance->unlink($path); + } + + static public function rename($path1, $path2) { + return self::$defaultInstance->rename($path1, $path2); + } + + static public function copy($path1, $path2) { + return self::$defaultInstance->copy($path1, $path2); + } + + static public function fopen($path, $mode) { + return self::$defaultInstance->fopen($path, $mode); + } + + static public function toTmpFile($path) { + return self::$defaultInstance->toTmpFile($path); + } + + static public function fromTmpFile($tmpFile, $path) { + return self::$defaultInstance->fromTmpFile($tmpFile, $path); + } + + static public function getMimeType($path) { + return self::$defaultInstance->getMimeType($path); + } + + static public function hash($type, $path, $raw = false) { + return self::$defaultInstance->hash($type, $path, $raw); + } + + static public function free_space($path = '/') { + return self::$defaultInstance->free_space($path); + } + + static public function search($query) { + return self::$defaultInstance->search($query); + } + + static public function searchByMime($query) { + return self::$defaultInstance->searchByMime($query); + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + static public function hasUpdated($path, $time) { + return self::$defaultInstance->hasUpdated($path, $time); + } + + /** + * @brief Fix common problems with a file path + * @param string $path + * @param bool $stripTrailingSlash + * @return string + */ + public static function normalizePath($path, $stripTrailingSlash = true) { + if ($path == '') { + return '/'; + } + //no windows style slashes + $path = str_replace('\\', '/', $path); + //add leading slash + if ($path[0] !== '/') { + $path = '/' . $path; + } + //remove duplicate slashes + while (strpos($path, '//') !== false) { + $path = str_replace('//', '/', $path); + } + //remove trailing slash + if ($stripTrailingSlash and strlen($path) > 1 and substr($path, -1, 1) === '/') { + $path = substr($path, 0, -1); + } + //normalize unicode if possible + if (class_exists('Normalizer')) { + $path = \Normalizer::normalize($path); + } + return $path; + } + + /** + * get the filesystem info + * + * @param string $path + * @return array + * + * returns an associative array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public static function getFileInfo($path) { + return self::$defaultInstance->getFileInfo($path); + } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public static function putFileInfo($path, $data) { + return self::$defaultInstance->putFileInfo($path, $data); + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @return array + */ + public static function getDirectoryContent($directory) { + return self::$defaultInstance->getDirectoryContent($directory); + } + + /** + * Get the path of a file by id + * + * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @return string + */ + public static function getPath($id) { + return self::$defaultInstance->getPath($id); + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string + */ + public static function getOwner($path) { + return self::$defaultInstance->getOwner($path); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + static public function getETag($path) { + return self::$defaultInstance->getETag($path); + } +} + +\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_Util::setupFS(); diff --git a/lib/files/mapper.php b/lib/files/mapper.php new file mode 100644 index 00000000000..90e4e1ca669 --- /dev/null +++ b/lib/files/mapper.php @@ -0,0 +1,216 @@ +<?php + +namespace OC\Files; + +/** + * class Mapper is responsible to translate logical paths to physical paths and reverse + */ +class Mapper +{ + /** + * @param string $logicPath + * @param bool $create indicates if the generated physical name shall be stored in the database or not + * @return string the physical path + */ + public function logicToPhysical($logicPath, $create) { + $physicalPath = $this->resolveLogicPath($logicPath); + if ($physicalPath !== null) { + return $physicalPath; + } + + return $this->create($logicPath, $create); + } + + /** + * @param string $physicalPath + * @return string|null + */ + public function physicalToLogic($physicalPath) { + $logicPath = $this->resolvePhysicalPath($physicalPath); + if ($logicPath !== null) { + return $logicPath; + } + + $this->insert($physicalPath, $physicalPath); + return $physicalPath; + } + + /** + * @param string $path + * @param bool $isLogicPath indicates if $path is logical or physical + * @param $recursive + */ + public function removePath($path, $isLogicPath, $recursive) { + if ($recursive) { + $path=$path.'%'; + } + + if ($isLogicPath) { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*file_map` WHERE `logic_path` LIKE ?'); + $query->execute(array($path)); + } else { + $query = \OC_DB::prepare('DELETE FROM `*PREFIX*file_map` WHERE `physic_path` LIKE ?'); + $query->execute(array($path)); + } + } + + /** + * @param $path1 + * @param $path2 + * @throws \Exception + */ + public function copy($path1, $path2) + { + $path1 = $this->stripLast($path1); + $path2 = $this->stripLast($path2); + $physicPath1 = $this->logicToPhysical($path1, true); + $physicPath2 = $this->logicToPhysical($path2, true); + + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*file_map` WHERE `logic_path` LIKE ?'); + $result = $query->execute(array($path1.'%')); + $updateQuery = \OC_DB::prepare('UPDATE `*PREFIX*file_map`' + .' SET `logic_path` = ?' + .' AND `physic_path` = ?' + .' WHERE `logic_path` = ?'); + while( $row = $result->fetchRow()) { + $currentLogic = $row['logic_path']; + $currentPhysic = $row['physic_path']; + $newLogic = $path2.$this->stripRootFolder($currentLogic, $path1); + $newPhysic = $physicPath2.$this->stripRootFolder($currentPhysic, $physicPath1); + if ($path1 !== $currentLogic) { + try { + $updateQuery->execute(array($newLogic, $newPhysic, $currentLogic)); + } catch (\Exception $e) { + error_log('Mapper::Copy failed '.$currentLogic.' -> '.$newLogic.'\n'.$e); + throw $e; + } + } + } + } + + /** + * @param $path + * @param $root + * @return bool|string + */ + public function stripRootFolder($path, $root) { + if (strpos($path, $root) !== 0) { + // throw exception ??? + return false; + } + if (strlen($path) > strlen($root)) { + return substr($path, strlen($root)); + } + + return ''; + } + + private function stripLast($path) { + if (substr($path, -1) == '/') { + $path = substr_replace($path ,'',-1); + } + return $path; + } + + private function resolveLogicPath($logicPath) { + $logicPath = $this->stripLast($logicPath); + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*file_map` WHERE `logic_path` = ?'); + $result = $query->execute(array($logicPath)); + $result = $result->fetchRow(); + + return $result['physic_path']; + } + + private function resolvePhysicalPath($physicalPath) { + $physicalPath = $this->stripLast($physicalPath); + $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*file_map` WHERE `physic_path` = ?'); + $result = $query->execute(array($physicalPath)); + $result = $result->fetchRow(); + + return $result['logic_path']; + } + + private function create($logicPath, $store) { + $logicPath = $this->stripLast($logicPath); + $index = 0; + + // create the slugified path + $physicalPath = $this->slugifyPath($logicPath); + + // detect duplicates + while ($this->resolvePhysicalPath($physicalPath) !== null) { + $physicalPath = $this->slugifyPath($physicalPath, $index++); + } + + // insert the new path mapping if requested + if ($store) { + $this->insert($logicPath, $physicalPath); + } + + return $physicalPath; + } + + private function insert($logicPath, $physicalPath) { + $query = \OC_DB::prepare('INSERT INTO `*PREFIX*file_map`(`logic_path`,`physic_path`) VALUES(?,?)'); + $query->execute(array($logicPath, $physicalPath)); + } + + private function slugifyPath($path, $index=null) { + $pathElements = explode('/', $path); + $sluggedElements = array(); + + // skip slugging the drive letter on windows - TODO: test if local path + if (strpos(strtolower(php_uname('s')), 'win') !== false) { + $sluggedElements[]= $pathElements[0]; + array_shift($pathElements); + } + foreach ($pathElements as $pathElement) { + // TODO: remove file ext before slugify on last element + $sluggedElements[] = self::slugify($pathElement); + } + + // + // TODO: add the index before the file extension + // + if ($index !== null) { + $last= end($sluggedElements); + array_pop($sluggedElements); + array_push($sluggedElements, $last.'-'.$index); + } + return implode(DIRECTORY_SEPARATOR, $sluggedElements); + } + + /** + * Modifies a string to remove all non ASCII characters and spaces. + * + * @param string $text + * @return string + */ + private function slugify($text) + { + // replace non letter or digits by - + $text = preg_replace('~[^\\pL\d]+~u', '-', $text); + + // trim + $text = trim($text, '-'); + + // transliterate + if (function_exists('iconv')) { + $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); + } + + // lowercase + $text = strtolower($text); + + // remove unwanted characters + $text = preg_replace('~[^-\w]+~', '', $text); + + if (empty($text)) + { + // TODO: we better generate a guid in this case + return 'n-a'; + } + + return $text; + } +} diff --git a/lib/files/mount.php b/lib/files/mount.php new file mode 100644 index 00000000000..74ee483b1be --- /dev/null +++ b/lib/files/mount.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files; + +class Mount { + /** + * @var Mount[] + */ + static private $mounts = array(); + + /** + * @var \OC\Files\Storage\Storage $storage + */ + private $storage = null; + private $class; + private $storageId; + private $arguments = array(); + private $mountPoint; + + /** + * @param string|\OC\Files\Storage\Storage $storage + * @param string $mountpoint + * @param array $arguments (optional) + */ + public function __construct($storage, $mountpoint, $arguments = null) { + if (is_null($arguments)) { + $arguments = array(); + } + + $mountpoint = self::formatPath($mountpoint); + if ($storage instanceof \OC\Files\Storage\Storage) { + $this->class = get_class($storage); + $this->storage = $storage; + } else { + // Update old classes to new namespace + if (strpos($storage, 'OC_Filestorage_') !== false) { + $storage = '\OC\Files\Storage\\' . substr($storage, 15); + } + $this->class = $storage; + $this->arguments = $arguments; + } + $this->mountPoint = $mountpoint; + + self::$mounts[$this->mountPoint] = $this; + } + + /** + * @return string + */ + public function getMountPoint() { + return $this->mountPoint; + } + + /** + * @return \OC\Files\Storage\Storage + */ + private function createStorage() { + if (class_exists($this->class)) { + try { + return new $this->class($this->arguments); + } catch (\Exception $exception) { + \OC_Log::write('core', $exception->getMessage(), \OC_Log::ERROR); + return null; + } + } else { + \OC_Log::write('core', 'storage backend ' . $this->class . ' not found', \OC_Log::ERROR); + return null; + } + } + + /** + * @return \OC\Files\Storage\Storage + */ + public function getStorage() { + if (is_null($this->storage)) { + $this->storage = $this->createStorage(); + } + return $this->storage; + } + + /** + * @return string + */ + public function getStorageId() { + if (!$this->storageId) { + if (is_null($this->storage)) { + $this->storage = $this->createStorage(); + } + $this->storageId = $this->storage->getId(); + } + return $this->storageId; + } + + /** + * @param string $path + * @return string + */ + public function getInternalPath($path) { + if ($this->mountPoint === $path or $this->mountPoint . '/' === $path) { + $internalPath = ''; + } else { + $internalPath = substr($path, strlen($this->mountPoint)); + } + return $internalPath; + } + + /** + * @param string $path + * @return string + */ + private static function formatPath($path) { + $path = Filesystem::normalizePath($path); + if (strlen($path) > 1) { + $path .= '/'; + } + return $path; + } + + /** + * Find the mount for $path + * + * @param $path + * @return Mount + */ + public static function find($path) { + $path = self::formatPath($path); + if (isset(self::$mounts[$path])) { + return self::$mounts[$path]; + } + + \OC_Hook::emit('OC_Filesystem', 'get_mountpoint', array('path' => $path)); + $foundMountPoint = ''; + $mountPoints = array_keys(self::$mounts); + foreach ($mountPoints as $mountpoint) { + if (strpos($path, $mountpoint) === 0 and strlen($mountpoint) > strlen($foundMountPoint)) { + $foundMountPoint = $mountpoint; + } + } + if (isset(self::$mounts[$foundMountPoint])) { + return self::$mounts[$foundMountPoint]; + } else { + return null; + } + } + + /** + * Find all mounts in $path + * + * @param $path + * @return Mount[] + */ + public static function findIn($path) { + $path = self::formatPath($path); + $result = array(); + $pathLength = strlen($path); + $mountPoints = array_keys(self::$mounts); + foreach ($mountPoints as $mountPoint) { + if (substr($mountPoint, 0, $pathLength) === $path and strlen($mountPoint) > $pathLength) { + $result[] = self::$mounts[$mountPoint]; + } + } + return $result; + } + + public static function clear() { + self::$mounts = array(); + } + + /** + * @param string $id + * @return \OC\Files\Storage\Storage[] + */ + public static function findById($id) { + $result = array(); + foreach (self::$mounts as $mount) { + if ($mount->getStorageId() === $id) { + $result[] = $mount; + } + } + return $result; + } +} diff --git a/lib/files/storage/common.php b/lib/files/storage/common.php new file mode 100644 index 00000000000..668cb08d7b1 --- /dev/null +++ b/lib/files/storage/common.php @@ -0,0 +1,304 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * Storage backend class for providing common filesystem operation methods + * which are not storage-backend specific. + * + * \OC\Files\Storage\Common is never used directly; it is extended by all other + * storage backends, where its methods may be overridden, and additional + * (backend-specific) methods are defined. + * + * 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 { + + public function __construct($parameters) {} + public function is_dir($path) { + return $this->filetype($path)=='dir'; + } + public function is_file($path) { + return $this->filetype($path)=='file'; + } + public function filesize($path) { + if($this->is_dir($path)) { + return 0;//by definition + }else{ + $stat = $this->stat($path); + return $stat['size']; + } + } + public function isCreatable($path) { + if ($this->is_dir($path) && $this->isUpdatable($path)) { + return true; + } + return false; + } + public function isDeletable($path) { + return $this->isUpdatable($path); + } + public function isSharable($path) { + return $this->isReadable($path); + } + public function getPermissions($path){ + $permissions = 0; + if($this->isCreatable($path)){ + $permissions |= \OCP\PERMISSION_CREATE; + } + if($this->isReadable($path)){ + $permissions |= \OCP\PERMISSION_READ; + } + if($this->isUpdatable($path)){ + $permissions |= \OCP\PERMISSION_UPDATE; + } + if($this->isDeletable($path)){ + $permissions |= \OCP\PERMISSION_DELETE; + } + if($this->isSharable($path)){ + $permissions |= \OCP\PERMISSION_SHARE; + } + return $permissions; + } + public function filemtime($path) { + $stat = $this->stat($path); + return $stat['mtime']; + } + public function file_get_contents($path) { + $handle = $this->fopen($path, "r"); + if(!$handle) { + return false; + } + $size=$this->filesize($path); + if($size==0) { + return ''; + } + return fread($handle, $size); + } + public function file_put_contents($path,$data) { + $handle = $this->fopen($path, "w"); + return fwrite($handle, $data); + } + public function rename($path1,$path2) { + if($this->copy($path1,$path2)) { + return $this->unlink($path1); + }else{ + return false; + } + } + public function copy($path1,$path2) { + $source=$this->fopen($path1,'r'); + $target=$this->fopen($path2,'w'); + $count=\OC_Helper::streamCopy($source,$target); + return $count>0; + } + + /** + * @brief Deletes all files and folders recursively within a directory + * @param string $directory The directory whose contents will be deleted + * @param bool $empty Flag indicating whether directory will be emptied + * @returns bool + * + * @note By default the directory specified by $directory will be + * deleted together with its contents. To avoid this set $empty to true + */ + public function deleteAll( $directory, $empty = false ) { + $directory = trim($directory,'/'); + + if ( !$this->file_exists( \OCP\USER::getUser() . '/' . $directory ) || !$this->is_dir( \OCP\USER::getUser() . '/' . $directory ) ) { + return false; + } elseif( !$this->isReadable( \OCP\USER::getUser() . '/' . $directory ) ) { + return false; + } else { + $directoryHandle = $this->opendir( \OCP\USER::getUser() . '/' . $directory ); + while ( $contents = readdir( $directoryHandle ) ) { + if ( $contents != '.' && $contents != '..') { + $path = $directory . "/" . $contents; + if ( $this->is_dir( $path ) ) { + $this->deleteAll( $path ); + } else { + $this->unlink( \OCP\USER::getUser() .'/' . $path ); // TODO: make unlink use same system path as is_dir + } + } + } + //$this->closedir( $directoryHandle ); // TODO: implement closedir in OC_FSV + if ( $empty == false ) { + if ( !$this->rmdir( $directory ) ) { + return false; + } + } + return true; + } + + } + public function getMimeType($path) { + if(!$this->file_exists($path)) { + return false; + } + if($this->is_dir($path)) { + return 'httpd/unix-directory'; + } + $source=$this->fopen($path,'r'); + if(!$source) { + return false; + } + $head=fread($source,8192);//8kb should suffice to determine a mimetype + if($pos=strrpos($path,'.')) { + $extension=substr($path,$pos); + }else{ + $extension=''; + } + $tmpFile=\OC_Helper::tmpFile($extension); + file_put_contents($tmpFile,$head); + $mime=\OC_Helper::getMimeType($tmpFile); + unlink($tmpFile); + return $mime; + } + public function hash($type,$path,$raw = false) { + $tmpFile=$this->getLocalFile($path); + $hash=hash($type,$tmpFile,$raw); + unlink($tmpFile); + return $hash; + } + public function search($query) { + return $this->searchInDir($query); + } + public function getLocalFile($path) { + return $this->toTmpFile($path); + } + private function toTmpFile($path) {//no longer in the storage api, still useful here + $source=$this->fopen($path,'r'); + if(!$source) { + return false; + } + if($pos=strrpos($path,'.')) { + $extension=substr($path,$pos); + }else{ + $extension=''; + } + $tmpFile=\OC_Helper::tmpFile($extension); + $target=fopen($tmpFile,'w'); + \OC_Helper::streamCopy($source,$target); + return $tmpFile; + } + public function getLocalFolder($path) { + $baseDir=\OC_Helper::tmpFolder(); + $this->addLocalFolder($path,$baseDir); + return $baseDir; + } + private function addLocalFolder($path,$target) { + if($dh=$this->opendir($path)) { + while($file=readdir($dh)) { + if($file!=='.' and $file!=='..') { + if($this->is_dir($path.'/'.$file)) { + mkdir($target.'/'.$file); + $this->addLocalFolder($path.'/'.$file,$target.'/'.$file); + }else{ + $tmp=$this->toTmpFile($path.'/'.$file); + rename($tmp,$target.'/'.$file); + } + } + } + } + } + + protected function searchInDir($query,$dir='') { + $files=array(); + $dh=$this->opendir($dir); + if($dh) { + while($item=readdir($dh)) { + if ($item == '.' || $item == '..') continue; + if(strstr(strtolower($item), strtolower($query))!==false) { + $files[]=$dir.'/'.$item; + } + if($this->is_dir($dir.'/'.$item)) { + $files=array_merge($files,$this->searchInDir($query,$dir.'/'.$item)); + } + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path,$time) { + return $this->filemtime($path)>$time; + } + + public function getCache($path=''){ + return new \OC\Files\Cache\Cache($this); + } + + public function getScanner($path=''){ + return new \OC\Files\Cache\Scanner($this); + } + + public function getPermissionsCache($path=''){ + return new \OC\Files\Cache\Permissions($this); + } + + public function getWatcher($path=''){ + return new \OC\Files\Cache\Watcher($this); + } + + /** + * get the owner of a path + * @param string $path The path to get the owner + * @return string uid or false + */ + public function getOwner($path) { + return \OC_User::getUser(); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path){ + $ETagFunction = \OC_Connector_Sabre_Node::$ETagFunction; + if($ETagFunction) { + $hash = call_user_func($ETagFunction, $path); + return $hash; + }else{ + return uniqid(); + } + } + + /** + * clean a path, i.e. remove all redundant '.' and '..' + * making sure that it can't point to higher than '/' + * @param $path The path to clean + * @return string cleaned path + */ + public function cleanPath($path) { + if (strlen($path) == 0 or $path[0] != '/') { + $path = '/' . $path; + } + + $chunks = explode('/', $path); + $output = array(); + foreach ($chunks as $chunk) { + if ($chunk == '..') { + array_pop($output); + } else if ($chunk == '.') { + } else { + $output[] = $chunk; + } + } + return implode('/', $output); + } +} diff --git a/lib/files/storage/commontest.php b/lib/files/storage/commontest.php new file mode 100644 index 00000000000..fbdb7fbf110 --- /dev/null +++ b/lib/files/storage/commontest.php @@ -0,0 +1,80 @@ +<?php + +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2012 Robin Appelman icewind@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +/** + * test implementation for \OC\Files\Storage\Common with \OC\Files\Storage\Local + */ + +namespace OC\Files\Storage; + +class CommonTest extends \OC\Files\Storage\Common{ + /** + * underlying local storage used for missing functions + * @var \OC\Files\Storage\Local + */ + private $storage; + + public function __construct($params) { + $this->storage=new \OC\Files\Storage\Local($params); + } + + public function getId(){ + return 'test::'.$this->storage->getId(); + } + public function mkdir($path) { + return $this->storage->mkdir($path); + } + public function rmdir($path) { + return $this->storage->rmdir($path); + } + public function opendir($path) { + return $this->storage->opendir($path); + } + public function stat($path) { + return $this->storage->stat($path); + } + public function filetype($path) { + return $this->storage->filetype($path); + } + public function isReadable($path) { + return $this->storage->isReadable($path); + } + public function isUpdatable($path) { + return $this->storage->isUpdatable($path); + } + public function file_exists($path) { + return $this->storage->file_exists($path); + } + public function unlink($path) { + return $this->storage->unlink($path); + } + public function fopen($path, $mode) { + return $this->storage->fopen($path, $mode); + } + public function free_space($path) { + return $this->storage->free_space($path); + } + public function touch($path, $mtime=null) { + return $this->storage->touch($path, $mtime); + } +}
\ No newline at end of file diff --git a/lib/files/storage/local.php b/lib/files/storage/local.php new file mode 100644 index 00000000000..d387a898320 --- /dev/null +++ b/lib/files/storage/local.php @@ -0,0 +1,252 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +if (\OC_Util::runningOnWindows()) { + require_once 'mappedlocal.php'; +} else { + +/** + * for local filestore, we only have to map the paths + */ +class Local extends \OC\Files\Storage\Common{ + protected $datadir; + public function __construct($arguments) { + $this->datadir=$arguments['datadir']; + if(substr($this->datadir, -1)!=='/') { + $this->datadir.='/'; + } + } + public function getId(){ + return 'local::'.$this->datadir; + } + public function mkdir($path) { + return @mkdir($this->datadir.$path); + } + public function rmdir($path) { + return @rmdir($this->datadir.$path); + } + public function opendir($path) { + return opendir($this->datadir.$path); + } + public function is_dir($path) { + if(substr($path,-1)=='/') { + $path=substr($path, 0, -1); + } + return is_dir($this->datadir.$path); + } + public function is_file($path) { + return is_file($this->datadir.$path); + } + 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; + } + return $statResult; + } + public function filetype($path) { + $filetype=filetype($this->datadir.$path); + if($filetype=='link') { + $filetype=filetype(realpath($this->datadir.$path)); + } + return $filetype; + } + 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; + } + } + public function isReadable($path) { + return is_readable($this->datadir.$path); + } + public function isUpdatable($path) { + return is_writable($this->datadir.$path); + } + public function file_exists($path) { + return file_exists($this->datadir.$path); + } + public function filemtime($path) { + return filemtime($this->datadir.$path); + } + public function touch($path, $mtime=null) { + // sets the modification time of the file to the given value. + // If mtime is nil the current time is set. + // note that the access time of the file always changes to the current time. + if(!is_null($mtime)) { + $result=touch( $this->datadir.$path, $mtime ); + }else{ + $result=touch( $this->datadir.$path); + } + if( $result ) { + clearstatcache( true, $this->datadir.$path ); + } + + return $result; + } + public function file_get_contents($path) { + return file_get_contents($this->datadir.$path); + } + public function file_put_contents($path, $data) {//trigger_error("$path = ".var_export($path, 1)); + return file_put_contents($this->datadir.$path, $data); + } + public function unlink($path) { + return $this->delTree($path); + } + public function rename($path1, $path2) { + if (!$this->isUpdatable($path1)) { + \OC_Log::write('core','unable to rename, file is not writable : '.$path1,\OC_Log::ERROR); + return false; + } + if(! $this->file_exists($path1)) { + \OC_Log::write('core','unable to rename, file does not exists : '.$path1,\OC_Log::ERROR); + return false; + } + + if($return=rename($this->datadir.$path1, $this->datadir.$path2)) { + } + return $return; + } + public function copy($path1, $path2) { + if($this->is_dir($path2)) { + if(!$this->file_exists($path2)) { + $this->mkdir($path2); + } + $source=substr($path1, strrpos($path1, '/')+1); + $path2.=$source; + } + return copy($this->datadir.$path1, $this->datadir.$path2); + } + public function fopen($path, $mode) { + if($return=fopen($this->datadir.$path, $mode)) { + switch($mode) { + case 'r': + break; + case 'r+': + case 'w+': + case 'x+': + case 'a+': + break; + case 'w': + case 'x': + case 'a': + break; + } + } + return $return; + } + + public function getMimeType($path) { + if($this->isReadable($path)) { + return \OC_Helper::getMimeType($this->datadir . $path); + }else{ + return false; + } + } + + private function delTree($dir) { + $dirRelative=$dir; + $dir=$this->datadir.$dir; + if (!file_exists($dir)) return true; + if (!is_dir($dir) || is_link($dir)) return unlink($dir); + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') continue; + if(is_file($dir.'/'.$item)) { + if(unlink($dir.'/'.$item)) { + } + }elseif(is_dir($dir.'/'.$item)) { + if (!$this->delTree($dirRelative. "/" . $item)) { + return false; + }; + } + } + if($return=rmdir($dir)) { + } + return $return; + } + + 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($path, $type, $raw=false) { + return hash_file($type, $this->datadir.$path, $raw); + } + + public function free_space($path) { + return @disk_free_space($this->datadir.$path); + } + + public function search($query) { + return $this->searchInDir($query); + } + public function getLocalFile($path) { + return $this->datadir.$path; + } + public function getLocalFolder($path) { + return $this->datadir.$path; + } + + protected function searchInDir($query, $dir='') { + $files=array(); + foreach (scandir($this->datadir.$dir) as $item) { + if ($item == '.' || $item == '..') continue; + if(strstr(strtolower($item), strtolower($query))!==false) { + $files[]=$dir.'/'.$item; + } + if(is_dir($this->datadir.$dir.'/'.$item)) { + $files=array_merge($files, $this->searchInDir($query, $dir.'/'.$item)); + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path)>$time; + } +} +} diff --git a/lib/files/storage/mappedlocal.php b/lib/files/storage/mappedlocal.php new file mode 100644 index 00000000000..80dd79bc41f --- /dev/null +++ b/lib/files/storage/mappedlocal.php @@ -0,0 +1,335 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +namespace OC\Files\Storage; + +/** + * for local filestore, we only have to map the paths + */ +class Local extends \OC\Files\Storage\Common{ + protected $datadir; + private $mapper; + + public function __construct($arguments) { + $this->datadir=$arguments['datadir']; + if(substr($this->datadir, -1)!=='/') { + $this->datadir.='/'; + } + + $this->mapper= new \OC\Files\Mapper(); + } + public function __destruct() { + if (defined('PHPUNIT_RUN')) { + $this->mapper->removePath($this->datadir, true, true); + } + } + public function getId(){ + return 'local::'.$this->datadir; + } + public function mkdir($path) { + return @mkdir($this->buildPath($path)); + } + public function rmdir($path) { + if ($result = @rmdir($this->buildPath($path))) { + $this->cleanMapper($path); + } + return $result; + } + public function opendir($path) { + $files = array('.', '..'); + $physicalPath= $this->buildPath($path); + + $logicalPath = $this->mapper->physicalToLogic($physicalPath); + $dh = opendir($physicalPath); + while ($file = readdir($dh)) { + if ($file === '.' or $file === '..') { + continue; + } + + $logicalFilePath = $this->mapper->physicalToLogic($physicalPath.DIRECTORY_SEPARATOR.$file); + + $file= $this->mapper->stripRootFolder($logicalFilePath, $logicalPath); + $file = $this->stripLeading($file); + $files[]= $file; + } + + \OC\Files\Stream\Dir::register('local-win32'.$path, $files); + return opendir('fakedir://local-win32'.$path); + } + public function is_dir($path) { + if(substr($path,-1)=='/') { + $path=substr($path, 0, -1); + } + return is_dir($this->buildPath($path)); + } + public function is_file($path) { + return is_file($this->buildPath($path)); + } + 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; + } + return $statResult; + } + public function filetype($path) { + $filetype=filetype($this->buildPath($path)); + if($filetype=='link') { + $filetype=filetype(realpath($this->buildPath($path))); + } + return $filetype; + } + 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; + } + } + public function isReadable($path) { + return is_readable($this->buildPath($path)); + } + public function isUpdatable($path) { + return is_writable($this->buildPath($path)); + } + public function file_exists($path) { + return file_exists($this->buildPath($path)); + } + public function filemtime($path) { + return filemtime($this->buildPath($path)); + } + public function touch($path, $mtime=null) { + // sets the modification time of the file to the given value. + // If mtime is nil the current time is set. + // note that the access time of the file always changes to the current time. + if(!is_null($mtime)) { + $result=touch( $this->buildPath($path), $mtime ); + }else{ + $result=touch( $this->buildPath($path)); + } + if( $result ) { + clearstatcache( true, $this->buildPath($path) ); + } + + return $result; + } + public function file_get_contents($path) { + return file_get_contents($this->buildPath($path)); + } + public function file_put_contents($path, $data) {//trigger_error("$path = ".var_export($path, 1)); + return file_put_contents($this->buildPath($path), $data); + } + public function unlink($path) { + return $this->delTree($path); + } + public function rename($path1, $path2) { + if (!$this->isUpdatable($path1)) { + \OC_Log::write('core','unable to rename, file is not writable : '.$path1,\OC_Log::ERROR); + return false; + } + if(! $this->file_exists($path1)) { + \OC_Log::write('core','unable to rename, file does not exists : '.$path1,\OC_Log::ERROR); + return false; + } + + $physicPath1 = $this->buildPath($path1); + $physicPath2 = $this->buildPath($path2); + if($return=rename($physicPath1, $physicPath2)) { + // mapper needs to create copies or all children + $this->copyMapping($path1, $path2); + $this->cleanMapper($physicPath1, false, true); + } + return $return; + } + public function copy($path1, $path2) { + if($this->is_dir($path2)) { + if(!$this->file_exists($path2)) { + $this->mkdir($path2); + } + $source=substr($path1, strrpos($path1, '/')+1); + $path2.=$source; + } + if($return=copy($this->buildPath($path1), $this->buildPath($path2))) { + // mapper needs to create copies or all children + $this->copyMapping($path1, $path2); + } + return $return; + } + public function fopen($path, $mode) { + if($return=fopen($this->buildPath($path), $mode)) { + switch($mode) { + case 'r': + break; + case 'r+': + case 'w+': + case 'x+': + case 'a+': + break; + case 'w': + case 'x': + case 'a': + break; + } + } + return $return; + } + + public function getMimeType($path) { + if($this->isReadable($path)) { + return \OC_Helper::getMimeType($this->buildPath($path)); + }else{ + return false; + } + } + + private function delTree($dir, $isLogicPath=true) { + $dirRelative=$dir; + if ($isLogicPath) { + $dir=$this->buildPath($dir); + } + if (!file_exists($dir)) { + return true; + } + if (!is_dir($dir) || is_link($dir)) { + if($return=unlink($dir)) { + $this->cleanMapper($dir, false); + return $return; + } + } + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if(is_file($dir.'/'.$item)) { + if(unlink($dir.'/'.$item)) { + $this->cleanMapper($dir.'/'.$item, false); + } + }elseif(is_dir($dir.'/'.$item)) { + if (!$this->delTree($dir. "/" . $item, false)) { + return false; + }; + } + } + if($return=rmdir($dir)) { + $this->cleanMapper($dir, false); + } + return $return; + } + + 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($path, $type, $raw=false) { + return hash_file($type, $this->buildPath($path), $raw); + } + + public function free_space($path) { + return @disk_free_space($this->buildPath($path)); + } + + public function search($query) { + return $this->searchInDir($query); + } + public function getLocalFile($path) { + return $this->buildPath($path); + } + public function getLocalFolder($path) { + return $this->buildPath($path); + } + + protected function searchInDir($query, $dir='', $isLogicPath=true) { + $files=array(); + $physicalDir = $this->buildPath($dir); + foreach (scandir($physicalDir) as $item) { + if ($item == '.' || $item == '..') + continue; + $physicalItem = $this->mapper->physicalToLogic($physicalDir.DIRECTORY_SEPARATOR.$item); + $item = substr($physicalItem, strlen($physicalDir)+1); + + if(strstr(strtolower($item), strtolower($query)) !== false) { + $files[]=$dir.'/'.$item; + } + if(is_dir($physicalItem)) { + $files=array_merge($files, $this->searchInDir($query, $physicalItem, false)); + } + } + return $files; + } + + /** + * check if a file or folder has been updated since $time + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->filemtime($path)>$time; + } + + private function buildPath($path, $create=true) { + $path = $this->stripLeading($path); + $fullPath = $this->datadir.$path; + return $this->mapper->logicToPhysical($fullPath, $create); + } + + private function cleanMapper($path, $isLogicPath=true, $recursive=true) { + $fullPath = $path; + if ($isLogicPath) { + $fullPath = $this->datadir.$path; + } + $this->mapper->removePath($fullPath, $isLogicPath, $recursive); + } + + private function copyMapping($path1, $path2) { + $path1 = $this->stripLeading($path1); + $path2 = $this->stripLeading($path2); + + $fullPath1 = $this->datadir.$path1; + $fullPath2 = $this->datadir.$path2; + + $this->mapper->copy($fullPath1, $fullPath2); + } + + private function stripLeading($path) { + if(strpos($path, '/') === 0) { + $path = substr($path, 1); + } + + return $path; + } +} diff --git a/lib/files/storage/storage.php b/lib/files/storage/storage.php new file mode 100644 index 00000000000..2cc835236ba --- /dev/null +++ b/lib/files/storage/storage.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * Provide a common interface to all different storage options + */ +interface Storage{ + public function __construct($parameters); + public function getId(); + public function mkdir($path); + public function rmdir($path); + public function opendir($path); + public function is_dir($path); + public function is_file($path); + public function stat($path); + public function filetype($path); + public function filesize($path); + public function isCreatable($path); + public function isReadable($path); + public function isUpdatable($path); + public function isDeletable($path); + public function isSharable($path); + public function getPermissions($path); + public function file_exists($path); + public function filemtime($path); + public function file_get_contents($path); + public function file_put_contents($path,$data); + public function unlink($path); + public function rename($path1,$path2); + public function copy($path1,$path2); + public function fopen($path,$mode); + public function getMimeType($path); + public function hash($type,$path,$raw = false); + public function free_space($path); + public function search($query); + public function touch($path, $mtime=null); + public function getLocalFile($path);// get a path to a local version of the file, whether the original file is local or remote + public function getLocalFolder($path);// get a path to a local version of the folder, whether the original file is local or remote + /** + * check if a file or folder has been updated since $time + * @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); + + /** + * @param string $path + * @return \OC\Files\Cache\Cache + */ + public function getCache($path=''); + /** + * @param string $path + * @return \OC\Files\Cache\Scanner + */ + public function getScanner($path=''); + + public function getOwner($path); + + /** + * @param string $path + * @return \OC\Files\Cache\Permissions + */ + public function getPermissionsCache($path=''); + + /** + * @param string $path + * @return \OC\Files\Cache\Watcher + */ + public function getWatcher($path=''); + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path); +} diff --git a/lib/files/storage/temporary.php b/lib/files/storage/temporary.php new file mode 100644 index 00000000000..d84dbda2e39 --- /dev/null +++ b/lib/files/storage/temporary.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Storage; + +/** + * local storage backend in temporary folder for testing purpose + */ +class Temporary extends Local{ + public function __construct($arguments) { + parent::__construct(array('datadir' => \OC_Helper::tmpFolder())); + } + + public function cleanUp() { + \OC_Helper::rmdirr($this->datadir); + } + + public function __destruct() { + parent::__destruct(); + $this->cleanUp(); + } +} diff --git a/lib/files/stream/close.php b/lib/files/stream/close.php new file mode 100644 index 00000000000..80de3497c36 --- /dev/null +++ b/lib/files/stream/close.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +/** + * stream wrapper that provides a callback on stream close + */ +class Close { + private static $callBacks = array(); + private $path = ''; + private $source; + private static $open = array(); + + public function stream_open($path, $mode, $options, &$opened_path) { + $path = substr($path, strlen('close://')); + $this->path = $path; + $this->source = fopen($path, $mode); + if (is_resource($this->source)) { + $this->meta = stream_get_meta_data($this->source); + } + self::$open[] = $path; + return is_resource($this->source); + } + + public function stream_seek($offset, $whence = SEEK_SET) { + fseek($this->source, $offset, $whence); + } + + public function stream_tell() { + return ftell($this->source); + } + + public function stream_read($count) { + return fread($this->source, $count); + } + + public function stream_write($data) { + return fwrite($this->source, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->source, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->source, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->source, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->source); + } + + public function stream_lock($mode) { + flock($this->source, $mode); + } + + public function stream_flush() { + return fflush($this->source); + } + + public function stream_eof() { + return feof($this->source); + } + + public function url_stat($path) { + $path = substr($path, strlen('close://')); + if (file_exists($path)) { + return stat($path); + } else { + return false; + } + } + + public function stream_close() { + fclose($this->source); + if (isset(self::$callBacks[$this->path])) { + call_user_func(self::$callBacks[$this->path], $this->path); + } + } + + public function unlink($path) { + $path = substr($path, strlen('close://')); + return unlink($path); + } + + public static function registerCallback($path, $callback) { + self::$callBacks[$path] = $callback; + } +} diff --git a/lib/files/stream/dir.php b/lib/files/stream/dir.php new file mode 100644 index 00000000000..6ca884fc994 --- /dev/null +++ b/lib/files/stream/dir.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +class Dir { + private static $dirs = array(); + private $name; + private $index; + + public function dir_opendir($path, $options) { + $this->name = substr($path, strlen('fakedir://')); + $this->index = 0; + if (!isset(self::$dirs[$this->name])) { + self::$dirs[$this->name] = array(); + } + return true; + } + + public function dir_readdir() { + if ($this->index >= count(self::$dirs[$this->name])) { + return false; + } + $filename = self::$dirs[$this->name][$this->index]; + $this->index++; + return $filename; + } + + public function dir_closedir() { + $this->name = ''; + return true; + } + + public function dir_rewinddir() { + $this->index = 0; + return true; + } + + public static function register($path, $content) { + self::$dirs[$path] = $content; + } +} diff --git a/lib/files/stream/oc.php b/lib/files/stream/oc.php new file mode 100644 index 00000000000..88e7e062df9 --- /dev/null +++ b/lib/files/stream/oc.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +/** + * a stream wrappers for ownCloud's virtual filesystem + */ +class OC { + /** + * @var \OC\Files\View + */ + static private $rootView; + + private $path; + private $dirSource; + private $fileSource; + private $meta; + + private function setup(){ + if (!self::$rootView) { + self::$rootView = new \OC\Files\View(''); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + $this->path = $path; + $this->fileSource = self::$rootView->fopen($path, $mode); + if (is_resource($this->fileSource)) { + $this->meta = stream_get_meta_data($this->fileSource); + } + return is_resource($this->fileSource); + } + + public function stream_seek($offset, $whence = SEEK_SET) { + fseek($this->fileSource, $offset, $whence); + } + + public function stream_tell() { + return ftell($this->fileSource); + } + + public function stream_read($count) { + return fread($this->fileSource, $count); + } + + public function stream_write($data) { + return fwrite($this->fileSource, $data); + } + + public function stream_set_option($option, $arg1, $arg2) { + switch ($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->fileSource, $arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->fileSource, $arg1, $arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->fileSource, $arg1, $arg2); + } + } + + public function stream_stat() { + return fstat($this->fileSource); + } + + public function stream_lock($mode) { + flock($this->fileSource, $mode); + } + + public function stream_flush() { + return fflush($this->fileSource); + } + + public function stream_eof() { + return feof($this->fileSource); + } + + public function url_stat($path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + if (self::$rootView->file_exists($path)) { + return self::$rootView->stat($path); + } else { + return false; + } + } + + public function stream_close() { + fclose($this->fileSource); + } + + public function unlink($path) { + $this->setup(); + $path = substr($path, strlen('oc://')); + return self::$rootView->unlink($path); + } + + public function dir_opendir($path, $options) { + $this->setup(); + $path = substr($path, strlen('oc://')); + $this->path = $path; + $this->dirSource = self::$rootView->opendir($path); + if (is_resource($this->dirSource)) { + $this->meta = stream_get_meta_data($this->dirSource); + } + return is_resource($this->dirSource); + } + + public function dir_readdir() { + return readdir($this->dirSource); + } + + public function dir_closedir() { + closedir($this->dirSource); + } + + public function dir_rewinddir() { + rewinddir($this->dirSource); + } +} diff --git a/lib/files/stream/staticstream.php b/lib/files/stream/staticstream.php new file mode 100644 index 00000000000..7725a6a5a04 --- /dev/null +++ b/lib/files/stream/staticstream.php @@ -0,0 +1,191 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Files\Stream; + +class StaticStream { + public $context; + protected static $data = array(); + + protected $path = ''; + protected $pointer = 0; + protected $writable = false; + + public function stream_close() { + } + + public function stream_eof() { + return $this->pointer >= strlen(self::$data[$this->path]); + } + + public function stream_flush() { + } + + public function stream_open($path, $mode, $options, &$opened_path) { + switch ($mode[0]) { + case 'r': + if (!isset(self::$data[$path])) return false; + $this->path = $path; + $this->writable = isset($mode[1]) && $mode[1] == '+'; + break; + case 'w': + self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + break; + case 'a': + if (!isset(self::$data[$path])) self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + $this->pointer = strlen(self::$data[$path]); + break; + case 'x': + if (isset(self::$data[$path])) return false; + $this->path = $path; + $this->writable = true; + break; + case 'c': + if (!isset(self::$data[$path])) self::$data[$path] = ''; + $this->path = $path; + $this->writable = true; + break; + default: + return false; + } + $opened_path = $this->path; + return true; + } + + public function stream_read($count) { + $bytes = min(strlen(self::$data[$this->path]) - $this->pointer, $count); + $data = substr(self::$data[$this->path], $this->pointer, $bytes); + $this->pointer += $bytes; + return $data; + } + + public function stream_seek($offset, $whence = SEEK_SET) { + $len = strlen(self::$data[$this->path]); + switch ($whence) { + case SEEK_SET: + if ($offset <= $len) { + $this->pointer = $offset; + return true; + } + break; + case SEEK_CUR: + if ($this->pointer + $offset <= $len) { + $this->pointer += $offset; + return true; + } + break; + case SEEK_END: + if ($len + $offset <= $len) { + $this->pointer = $len + $offset; + return true; + } + break; + } + return false; + } + + public function stream_stat() { + $size = strlen(self::$data[$this->path]); + $time = time(); + return array( + 0 => 0, + 'dev' => 0, + 1 => 0, + 'ino' => 0, + 2 => 0777, + 'mode' => 0777, + 3 => 1, + 'nlink' => 1, + 4 => 0, + 'uid' => 0, + 5 => 0, + 'gid' => 0, + 6 => '', + 'rdev' => '', + 7 => $size, + 'size' => $size, + 8 => $time, + 'atime' => $time, + 9 => $time, + 'mtime' => $time, + 10 => $time, + 'ctime' => $time, + 11 => -1, + 'blksize' => -1, + 12 => -1, + 'blocks' => -1, + ); + } + + public function stream_tell() { + return $this->pointer; + } + + public function stream_write($data) { + if (!$this->writable) return 0; + $size = strlen($data); + if ($this->stream_eof()) { + self::$data[$this->path] .= $data; + } else { + self::$data[$this->path] = substr_replace( + self::$data[$this->path], + $data, + $this->pointer + ); + } + $this->pointer += $size; + return $size; + } + + public function unlink($path) { + if (isset(self::$data[$path])) { + unset(self::$data[$path]); + } + return true; + } + + public function url_stat($path) { + if (isset(self::$data[$path])) { + $size = strlen(self::$data[$path]); + $time = time(); + return array( + 0 => 0, + 'dev' => 0, + 1 => 0, + 'ino' => 0, + 2 => 0777, + 'mode' => 0777, + 3 => 1, + 'nlink' => 1, + 4 => 0, + 'uid' => 0, + 5 => 0, + 'gid' => 0, + 6 => '', + 'rdev' => '', + 7 => $size, + 'size' => $size, + 8 => $time, + 'atime' => $time, + 9 => $time, + 'mtime' => $time, + 10 => $time, + 'ctime' => $time, + 11 => -1, + 'blksize' => -1, + 12 => -1, + 'blocks' => -1, + ); + } + return false; + } +} diff --git a/lib/files/view.php b/lib/files/view.php new file mode 100644 index 00000000000..dfcb770328b --- /dev/null +++ b/lib/files/view.php @@ -0,0 +1,974 @@ +<?php +/** + * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class to provide access to ownCloud filesystem via a "view", and methods for + * working with files within that view (e.g. read, write, delete, etc.). Each + * view is restricted to a set of directories via a virtual root. The default view + * uses the currently logged in user's data directory as root (parts of + * OC_Filesystem are merely a wrapper for OC_FilesystemView). + * + * Apps that need to access files outside of the user data folders (to modify files + * belonging to a user other than the one currently logged in, for example) should + * use this class directly rather than using OC_Filesystem, or making use of PHP's + * built-in file manipulation functions. This will ensure all hooks and proxies + * are triggered correctly. + * + * Filesystem functions are not called directly; they are passed to the correct + * \OC\Files\Storage\Storage object + */ + +namespace OC\Files; + +class View { + private $fakeRoot = ''; + private $internal_path_cache = array(); + private $storage_cache = array(); + + public function __construct($root) { + $this->fakeRoot = $root; + } + + public function getAbsolutePath($path = '/') { + if (!$path) { + $path = '/'; + } + if ($path[0] !== '/') { + $path = '/' . $path; + } + return $this->fakeRoot . $path; + } + + /** + * change the root to a fake root + * + * @param string $fakeRoot + * @return bool + */ + public function chroot($fakeRoot) { + if (!$fakeRoot == '') { + if ($fakeRoot[0] !== '/') { + $fakeRoot = '/' . $fakeRoot; + } + } + $this->fakeRoot = $fakeRoot; + } + + /** + * get the fake root + * + * @return string + */ + public function getRoot() { + return $this->fakeRoot; + } + + /** + * get path relative to the root of the view + * + * @param string $path + * @return string + */ + public function getRelativePath($path) { + if ($this->fakeRoot == '') { + return $path; + } + if (strpos($path, $this->fakeRoot) !== 0) { + return null; + } else { + $path = substr($path, strlen($this->fakeRoot)); + if (strlen($path) === 0) { + return '/'; + } else { + return $path; + } + } + } + + /** + * get the mountpoint of the storage object for a path + ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account + * + * @param string $path + * @return string + */ + public function getMountPoint($path) { + return Filesystem::getMountPoint($this->getAbsolutePath($path)); + } + + /** + * resolve a path to a storage and internal path + * + * @param string $path + * @return array consisting of the storage and the internal path + */ + public function resolvePath($path) { + return Filesystem::resolvePath($this->getAbsolutePath($path)); + } + + /** + * return the path to a local version of the file + * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed + * + * @param string $path + * @return string + */ + public function getLocalFile($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFile($internalPath); + } else { + return null; + } + } + + /** + * @param string $path + * @return string + */ + public function getLocalFolder($path) { + $parent = substr($path, 0, strrpos($path, '/')); + $path = $this->getAbsolutePath($path); + list($storage, $internalPath) = Filesystem::resolvePath($path); + if (Filesystem::isValidPath($parent) and $storage) { + return $storage->getLocalFolder($internalPath); + } else { + return null; + } + } + + /** + * the following functions operate with arguments and return values identical + * to those of their PHP built-in equivalents. Mostly they are merely wrappers + * for \OC\Files\Storage\Storage via basicOperation(). + */ + public function mkdir($path) { + return $this->basicOperation('mkdir', $path, array('create', 'write')); + } + + public function rmdir($path) { + return $this->basicOperation('rmdir', $path, array('delete')); + } + + public function opendir($path) { + return $this->basicOperation('opendir', $path, array('read')); + } + + public function readdir($handle) { + $fsLocal = new Storage\Local(array('datadir' => '/')); + return $fsLocal->readdir($handle); + } + + public function is_dir($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('is_dir', $path); + } + + public function is_file($path) { + if ($path == '/') { + return false; + } + return $this->basicOperation('is_file', $path); + } + + public function stat($path) { + return $this->basicOperation('stat', $path); + } + + public function filetype($path) { + return $this->basicOperation('filetype', $path); + } + + public function filesize($path) { + return $this->basicOperation('filesize', $path); + } + + public function readfile($path) { + @ob_end_clean(); + $handle = $this->fopen($path, 'rb'); + if ($handle) { + $chunkSize = 8192; // 8 MB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + flush(); + } + $size = $this->filesize($path); + return $size; + } + return false; + } + + public function isCreatable($path) { + return $this->basicOperation('isCreatable', $path); + } + + public function isReadable($path) { + return $this->basicOperation('isReadable', $path); + } + + public function isUpdatable($path) { + return $this->basicOperation('isUpdatable', $path); + } + + public function isDeletable($path) { + return $this->basicOperation('isDeletable', $path); + } + + public function isSharable($path) { + return $this->basicOperation('isSharable', $path); + } + + public function file_exists($path) { + if ($path == '/') { + return true; + } + return $this->basicOperation('file_exists', $path); + } + + public function filemtime($path) { + return $this->basicOperation('filemtime', $path); + } + + public function touch($path, $mtime = null) { + if (!is_null($mtime) and !is_numeric($mtime)) { + $mtime = strtotime($mtime); + } + return $this->basicOperation('touch', $path, array('write'), $mtime); + } + + public function file_get_contents($path) { + return $this->basicOperation('file_get_contents', $path, array('read')); + } + + public function file_put_contents($path, $data) { + if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && Filesystem::isValidPath($path)) { + $path = $this->getRelativePath($absolutePath); + $exists = $this->file_exists($path); + $run = true; + if ($this->fakeRoot == Filesystem::getRoot()) { + if (!$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_create, + array( + Filesystem::signal_param_path => $path, + Filesystem::signal_param_run => &$run + ) + ); + } + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_write, + array( + Filesystem::signal_param_path => $path, + Filesystem::signal_param_run => &$run + ) + ); + } + if (!$run) { + return false; + } + $target = $this->fopen($path, 'w'); + if ($target) { + $count = \OC_Helper::streamCopy($data, $target); + fclose($target); + fclose($data); + if ($this->fakeRoot == Filesystem::getRoot()) { + if (!$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_create, + array(Filesystem::signal_param_path => $path) + ); + } + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array(Filesystem::signal_param_path => $path) + ); + } + \OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count); + return $count > 0; + } else { + return false; + } + } else { + return false; + } + } else { + return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data); + } + } + + public function unlink($path) { + return $this->basicOperation('unlink', $path, array('delete')); + } + + public function deleteAll($directory, $empty = false) { + return $this->basicOperation('deleteAll', $directory, array('delete'), $empty); + } + + public function rename($path1, $path2) { + $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; + $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + if (\OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and Filesystem::isValidPath($path2)) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + + if ($path1 == null or $path2 == null) { + return false; + } + $run = true; + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, Filesystem::signal_rename, + array( + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2, + Filesystem::signal_param_run => &$run + ) + ); + } + if ($run) { + $mp1 = $this->getMountPoint($path1 . $postFix1); + $mp2 = $this->getMountPoint($path2 . $postFix2); + if ($mp1 == $mp2) { + list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2); + if ($storage) { + $result = $storage->rename($internalPath1, $internalPath2); + } else { + $result = false; + } + } else { + $source = $this->fopen($path1 . $postFix1, 'r'); + $target = $this->fopen($path2 . $postFix2, 'w'); + $count = \OC_Helper::streamCopy($source, $target); + list($storage1, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + $storage1->unlink($internalPath1); + $result = $count > 0; + } + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_rename, + array( + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2 + ) + ); + } + return $result; + } else { + return false; + } + } else { + return false; + } + } + + public function copy($path1, $path2) { + $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : ''; + $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : ''; + $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1)); + $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2)); + if (\OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and Filesystem::isValidPath($path2)) { + $path1 = $this->getRelativePath($absolutePath1); + $path2 = $this->getRelativePath($absolutePath2); + + if ($path1 == null or $path2 == null) { + return false; + } + $run = true; + $exists = $this->file_exists($path2); + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_copy, + array( + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2, + Filesystem::signal_param_run => &$run + ) + ); + if ($run and !$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_create, + array( + Filesystem::signal_param_path => $path2, + Filesystem::signal_param_run => &$run + ) + ); + } + if ($run) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_write, + array( + Filesystem::signal_param_path => $path2, + Filesystem::signal_param_run => &$run + ) + ); + } + } + if ($run) { + $mp1 = $this->getMountPoint($path1 . $postFix1); + $mp2 = $this->getMountPoint($path2 . $postFix2); + if ($mp1 == $mp2) { + list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1); + list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2); + if ($storage) { + $result = $storage->copy($internalPath1, $internalPath2); + } else { + $result = false; + } + } else { + $source = $this->fopen($path1 . $postFix1, 'r'); + $target = $this->fopen($path2 . $postFix2, 'w'); + $result = \OC_Helper::streamCopy($source, $target); + } + if ($this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_copy, + array( + Filesystem::signal_param_oldpath => $path1, + Filesystem::signal_param_newpath => $path2 + ) + ); + if (!$exists) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_create, + array(Filesystem::signal_param_path => $path2) + ); + } + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_post_write, + array(Filesystem::signal_param_path => $path2) + ); + } + return $result; + } else { + return false; + } + } else { + return false; + } + } + + public function fopen($path, $mode) { + $hooks = array(); + switch ($mode) { + case 'r': + case 'rb': + $hooks[] = 'read'; + break; + case 'r+': + case 'rb+': + case 'w+': + case 'wb+': + case 'x+': + case 'xb+': + case 'a+': + case 'ab+': + $hooks[] = 'read'; + $hooks[] = 'write'; + break; + case 'w': + case 'wb': + case 'x': + case 'xb': + case 'a': + case 'ab': + $hooks[] = 'write'; + break; + default: + \OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR); + } + + return $this->basicOperation('fopen', $path, $hooks, $mode); + } + + public function toTmpFile($path) { + if (Filesystem::isValidPath($path)) { + $source = $this->fopen($path, 'r'); + if ($source) { + $extension = ''; + $extOffset = strpos($path, '.'); + if ($extOffset !== false) { + $extension = substr($path, strrpos($path, '.')); + } + $tmpFile = \OC_Helper::tmpFile($extension); + file_put_contents($tmpFile, $source); + return $tmpFile; + } else { + return false; + } + } else { + return false; + } + } + + public function fromTmpFile($tmpFile, $path) { + if (Filesystem::isValidPath($path)) { + if (!$tmpFile) { + debug_print_backtrace(); + } + $source = fopen($tmpFile, 'r'); + if ($source) { + $this->file_put_contents($path, $source); + unlink($tmpFile); + return true; + } else { + return false; + } + } else { + return false; + } + } + + public function getMimeType($path) { + return $this->basicOperation('getMimeType', $path); + } + + public function hash($type, $path, $raw = false) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies('hash', $absolutePath) && Filesystem::isValidPath($path)) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + if (Filesystem::$loaded && $this->fakeRoot == Filesystem::getRoot()) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + Filesystem::signal_read, + array(Filesystem::signal_param_path => $path) + ); + } + list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + if ($storage) { + $result = $storage->hash($type, $internalPath, $raw); + $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result); + return $result; + } + } + return null; + } + + public function free_space($path = '/') { + return $this->basicOperation('free_space', $path); + } + + /** + * @brief abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage + * @param string $operation + * @param string $path + * @param array $hooks (optional) + * @param mixed $extraParam (optional) + * @return mixed + * + * This method takes requests for basic filesystem functions (e.g. reading & writing + * files), processes hooks and proxies, sanitises paths, and finally passes them on to + * \OC\Files\Storage\Storage for delegation to a storage backend for execution + */ + private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) { + $postFix = (substr($path, -1, 1) === '/') ? '/' : ''; + $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path)); + if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and Filesystem::isValidPath($path)) { + $path = $this->getRelativePath($absolutePath); + if ($path == null) { + return false; + } + $run = $this->runHooks($hooks, $path); + list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix); + if ($run and $storage) { + if (!is_null($extraParam)) { + $result = $storage->$operation($internalPath, $extraParam); + } else { + $result = $storage->$operation($internalPath); + } + $result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result); + if (Filesystem::$loaded and $this->fakeRoot == Filesystem::getRoot()) { + if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open + $this->runHooks($hooks, $path, true); + } + } + return $result; + } + } + return null; + } + + private function runHooks($hooks, $path, $post = false) { + $prefix = ($post) ? 'post_' : ''; + $run = true; + if (Filesystem::$loaded and $this->fakeRoot == Filesystem::getRoot()) { + foreach ($hooks as $hook) { + if ($hook != 'read') { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + array( + Filesystem::signal_param_run => &$run, + Filesystem::signal_param_path => $path + ) + ); + } elseif (!$post) { + \OC_Hook::emit( + Filesystem::CLASSNAME, + $prefix . $hook, + array( + Filesystem::signal_param_path => $path + ) + ); + } + } + } + return $run; + } + + /** + * check if a file or folder has been updated since $time + * + * @param string $path + * @param int $time + * @return bool + */ + public function hasUpdated($path, $time) { + return $this->basicOperation('hasUpdated', $path, array(), $time); + } + + /** + * get the filesystem info + * + * @param string $path + * @return array + * + * returns an associative array with the following keys: + * - size + * - mtime + * - mimetype + * - encrypted + * - versioned + */ + public function getFileInfo($path) { + $data = array(); + if (!Filesystem::isValidPath($path)) { + return $data; + } + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + $user = \OC_User::getUser(); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = $storage->getWatcher($internalPath); + $watcher->checkUpdate($internalPath); + } + + $data = $cache->get($internalPath); + + if ($data and $data['fileid']) { + if ($data['mimetype'] === 'httpd/unix-directory') { + //add the sizes of other mountpoints to the folder + $mountPoints = Filesystem::getMountPoints($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + $rootEntry = $subCache->get(''); + $data['size'] += $rootEntry['size']; + } + } + } + + $permissions = $permissionsCache->get($data['fileid'], $user); + if ($permissions === -1) { + $permissions = $storage->getPermissions($internalPath); + $permissionsCache->set($data['fileid'], $user, $permissions); + } + $data['permissions'] = $permissions; + } + } + return $data; + } + + /** + * get the content of a directory + * + * @param string $directory path under datadirectory + * @return array + */ + public function getDirectoryContent($directory, $mimetype_filter = '') { + $result = array(); + if (!Filesystem::isValidPath($directory)) { + return $result; + } + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $directory); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($internalPath); + $permissionsCache = $storage->getPermissionsCache($internalPath); + $user = \OC_User::getUser(); + + if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } else { + $watcher = $storage->getWatcher($internalPath); + $watcher->checkUpdate($internalPath); + } + + $files = $cache->getFolderContents($internalPath); //TODO: mimetype_filter + + $ids = array(); + foreach ($files as $i => $file) { + $files[$i]['type'] = $file['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $ids[] = $file['fileid']; + + $permissions = $permissionsCache->get($file['fileid'], $user); + if ($permissions === -1) { + $permissions = $storage->getPermissions($file['path']); + $permissionsCache->set($file['fileid'], $user, $permissions); + } + $files[$i]['permissions'] = $permissions; + } + + //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders + $mountPoints = Filesystem::getMountPoints($path); + $dirLength = strlen($path); + foreach ($mountPoints as $mountPoint) { + $subStorage = Filesystem::getStorage($mountPoint); + if ($subStorage) { + $subCache = $subStorage->getCache(''); + + if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) { + $subScanner = $subStorage->getScanner(''); + $subScanner->scanFile(''); + } + + $rootEntry = $subCache->get(''); + if ($rootEntry) { + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + foreach ($files as &$entry) { + if ($entry['name'] === $entryName) { + $entry['size'] += $rootEntry['size']; + } + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $subPermissionsCache = $subStorage->getPermissionsCache(''); + $permissions = $subPermissionsCache->get($rootEntry['fileid'], $user); + if ($permissions === -1) { + $permissions = $subStorage->getPermissions($rootEntry['path']); + $subPermissionsCache->set($rootEntry['fileid'], $user, $permissions); + } + $rootEntry['permissions'] = $permissions; + + //remove any existing entry with the same name + foreach ($files as $i => $file) { + if ($file['name'] === $rootEntry['name']) { + unset($files[$i]); + break; + } + } + $files[] = $rootEntry; + } + } + } + } + + if ($mimetype_filter) { + foreach ($files as $file) { + if (strpos($mimetype_filter, '/')) { + if ($file['mimetype'] === $mimetype_filter) { + $result[] = $file; + } + } else { + if ($file['mimepart'] === $mimetype_filter) { + $result[] = $file; + } + } + } + } else { + $result = $files; + } + } + return $result; + } + + /** + * change file metadata + * + * @param string $path + * @param array $data + * @return int + * + * returns the fileid of the updated file + */ + public function putFileInfo($path, $data) { + $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path); + /** + * @var \OC\Files\Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = Filesystem::resolvePath($path); + if ($storage) { + $cache = $storage->getCache($path); + + if (!$cache->inCache($internalPath)) { + $scanner = $storage->getScanner($internalPath); + $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW); + } + + return $cache->put($internalPath, $data); + } else { + return -1; + } + } + + /** + * search for files with the name matching $query + * + * @param string $query + * @return array + */ + public function search($query) { + return $this->searchCommon('%' . $query . '%', 'search'); + } + + /** + * search for files by mimetype + * + * @param string $query + * @return array + */ + public function searchByMime($mimetype) { + return $this->searchCommon($mimetype, 'searchByMime'); + } + + /** + * @param string $query + * @param string $method + * @return array + */ + private function searchCommon($query, $method) { + $files = array(); + $rootLength = strlen($this->fakeRoot); + + $mountPoint = Filesystem::getMountPoint($this->fakeRoot); + $storage = Filesystem::getStorage($mountPoint); + if ($storage) { + $cache = $storage->getCache(''); + + $results = $cache->$method($query); + foreach ($results as $result) { + if (substr($mountPoint . $result['path'], 0, $rootLength) === $this->fakeRoot) { + $result['path'] = substr($mountPoint . $result['path'], $rootLength); + $files[] = $result; + } + } + + $mountPoints = Filesystem::getMountPoints($this->fakeRoot); + foreach ($mountPoints as $mountPoint) { + $storage = Filesystem::getStorage($mountPoint); + if ($storage) { + $cache = $storage->getCache(''); + + $relativeMountPoint = substr($mountPoint, $rootLength); + $results = $cache->$method($query); + foreach ($results as $result) { + $result['path'] = $relativeMountPoint . $result['path']; + $files[] = $result; + } + } + } + } + return $files; + } + + /** + * Get the owner for a file or folder + * + * @param string $path + * @return string + */ + public function getOwner($path) { + return $this->basicOperation('getOwner', $path); + } + + /** + * get the ETag for a file or folder + * + * @param string $path + * @return string + */ + public function getETag($path) { + /** + * @var Storage\Storage $storage + * @var string $internalPath + */ + list($storage, $internalPath) = $this->resolvePath($path); + if ($storage) { + return $storage->getETag($internalPath); + } else { + return null; + } + } + + /** + * Get the path of a file by id, relative to the view + * + * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file + * + * @param int $id + * @return string + */ + public function getPath($id) { + list($storage, $internalPath) = Cache\Cache::getById($id); + $mounts = Mount::findById($storage); + foreach ($mounts as $mount) { + /** + * @var \OC\Files\Mount $mount + */ + $fullPath = $mount->getMountPoint() . $internalPath; + if (!is_null($path = $this->getRelativePath($fullPath))) { + return $path; + } + } + return null; + } +} |