summaryrefslogtreecommitdiffstats
path: root/lib/files
diff options
context:
space:
mode:
authorHenrik Kjölhede <hkjolhede@gmail.com>2013-02-09 14:05:33 +0100
committerHenrik Kjölhede <hkjolhede@gmail.com>2013-02-09 14:05:33 +0100
commit41fa65e7befb249e260f2dd91e33971261cfd302 (patch)
tree4ebb4cb10f7869ba3288308ca3a35ce53970ed40 /lib/files
parentb1b2eafa50db54b2613cf2bc52bfab2015d67b2f (diff)
parentec829bd345045df985f623fa2fe6587d0ce68eb2 (diff)
downloadnextcloud-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.php527
-rw-r--r--lib/files/cache/legacy.php81
-rw-r--r--lib/files/cache/permissions.php102
-rw-r--r--lib/files/cache/scanner.php146
-rw-r--r--lib/files/cache/updater.php105
-rw-r--r--lib/files/cache/upgrade.php159
-rw-r--r--lib/files/cache/watcher.php72
-rw-r--r--lib/files/filesystem.php637
-rw-r--r--lib/files/mapper.php216
-rw-r--r--lib/files/mount.php188
-rw-r--r--lib/files/storage/common.php304
-rw-r--r--lib/files/storage/commontest.php80
-rw-r--r--lib/files/storage/local.php252
-rw-r--r--lib/files/storage/mappedlocal.php335
-rw-r--r--lib/files/storage/storage.php88
-rw-r--r--lib/files/storage/temporary.php27
-rw-r--r--lib/files/stream/close.php100
-rw-r--r--lib/files/stream/dir.php47
-rw-r--r--lib/files/stream/oc.php129
-rw-r--r--lib/files/stream/staticstream.php191
-rw-r--r--lib/files/view.php974
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;
+ }
+}