summaryrefslogtreecommitdiffstats
path: root/lib/private/files/cache
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/files/cache')
-rw-r--r--lib/private/files/cache/backgroundwatcher.php104
-rw-r--r--lib/private/files/cache/cache.php582
-rw-r--r--lib/private/files/cache/legacy.php136
-rw-r--r--lib/private/files/cache/permissions.php143
-rw-r--r--lib/private/files/cache/scanner.php258
-rw-r--r--lib/private/files/cache/storage.php60
-rw-r--r--lib/private/files/cache/updater.php161
-rw-r--r--lib/private/files/cache/upgrade.php227
-rw-r--r--lib/private/files/cache/watcher.php72
9 files changed, 1743 insertions, 0 deletions
diff --git a/lib/private/files/cache/backgroundwatcher.php b/lib/private/files/cache/backgroundwatcher.php
new file mode 100644
index 00000000000..923804f48d0
--- /dev/null
+++ b/lib/private/files/cache/backgroundwatcher.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Files\Cache;
+
+use \OC\Files\Mount;
+use \OC\Files\Filesystem;
+
+class BackgroundWatcher {
+ static $folderMimetype = null;
+
+ static private function getFolderMimetype() {
+ if (!is_null(self::$folderMimetype)) {
+ return self::$folderMimetype;
+ }
+ $sql = 'SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?';
+ $result = \OC_DB::executeAudited($sql, array('httpd/unix-directory'));
+ $row = $result->fetchRow();
+ return $row['id'];
+ }
+
+ static private function checkUpdate($id) {
+ $cacheItem = Cache::getById($id);
+ if (is_null($cacheItem)) {
+ return;
+ }
+ list($storageId, $internalPath) = $cacheItem;
+ $mounts = Filesystem::getMountByStorageId($storageId);
+
+ if (count($mounts) === 0) {
+ //if the storage we need isn't mounted on default, try to find a user that has access to the storage
+ $permissionsCache = new Permissions($storageId);
+ $users = $permissionsCache->getUsers($id);
+ if (count($users) === 0) {
+ return;
+ }
+ Filesystem::initMountPoints($users[0]);
+ $mounts = Filesystem::getMountByStorageId($storageId);
+ if (count($mounts) === 0) {
+ return;
+ }
+ }
+ $storage = $mounts[0]->getStorage();
+ $watcher = new Watcher($storage);
+ $watcher->checkUpdate($internalPath);
+ }
+
+ /**
+ * get the next fileid in the cache
+ *
+ * @param int $previous
+ * @param bool $folder
+ * @return int
+ */
+ static private function getNextFileId($previous, $folder) {
+ if ($folder) {
+ $stmt = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` > ? AND `mimetype` = ? ORDER BY `fileid` ASC', 1);
+ } else {
+ $stmt = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE `fileid` > ? AND `mimetype` != ? ORDER BY `fileid` ASC', 1);
+ }
+ $result = \OC_DB::executeAudited($stmt, array($previous,self::getFolderMimetype()));
+ if ($row = $result->fetchRow()) {
+ return $row['fileid'];
+ } else {
+ return 0;
+ }
+ }
+
+ static public function checkNext() {
+ // check both 1 file and 1 folder, this way new files are detected quicker because there are less folders than files usually
+ $previousFile = \OC_Appconfig::getValue('files', 'backgroundwatcher_previous_file', 0);
+ $previousFolder = \OC_Appconfig::getValue('files', 'backgroundwatcher_previous_folder', 0);
+ $nextFile = self::getNextFileId($previousFile, false);
+ $nextFolder = self::getNextFileId($previousFolder, true);
+ \OC_Appconfig::setValue('files', 'backgroundwatcher_previous_file', $nextFile);
+ \OC_Appconfig::setValue('files', 'backgroundwatcher_previous_folder', $nextFolder);
+ if ($nextFile > 0) {
+ self::checkUpdate($nextFile);
+ }
+ if ($nextFolder > 0) {
+ self::checkUpdate($nextFolder);
+ }
+ }
+
+ static public function checkAll() {
+ $previous = 0;
+ $next = 1;
+ while ($next != 0) {
+ $next = self::getNextFileId($previous, true);
+ self::checkUpdate($next);
+ }
+ $previous = 0;
+ $next = 1;
+ while ($next != 0) {
+ $next = self::getNextFileId($previous, false);
+ self::checkUpdate($next);
+ }
+ }
+}
diff --git a/lib/private/files/cache/cache.php b/lib/private/files/cache/cache.php
new file mode 100644
index 00000000000..e69733727af
--- /dev/null
+++ b/lib/private/files/cache/cache.php
@@ -0,0 +1,582 @@
+<?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;
+
+ /**
+ * @var Storage $storageCache
+ */
+ private $storageCache;
+
+ 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;
+ }
+ if (strlen($this->storageId) > 64) {
+ $this->storageId = md5($this->storageId);
+ }
+
+ $this->storageCache = new Storage($storage);
+ }
+
+ public function getNumericStorageId() {
+ return $this->storageCache->getNumericId();
+ }
+
+ /**
+ * normalize mimetypes
+ *
+ * @param string $mime
+ * @return int
+ */
+ public function getMimetypeId($mime) {
+ if (!isset($this->mimetypeIds[$mime])) {
+ $result = \OC_DB::executeAudited('SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = ?', array($mime));
+ if ($row = $result->fetchRow()) {
+ $this->mimetypeIds[$mime] = $row['id'];
+ } else {
+ $result = \OC_DB::executeAudited('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)', 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])) {
+ $sql = 'SELECT `mimetype` FROM `*PREFIX*mimetypes` WHERE `id` = ?';
+ $result = \OC_DB::executeAudited($sql, 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 | false
+ */
+ public function get($file) {
+ if (is_string($file) or $file == '') {
+ // normalize file
+ $file = $this->normalize($file);
+
+ $where = 'WHERE `storage` = ? AND `path_hash` = ?';
+ $params = array($this->getNumericStorageId(), md5($file));
+ } else { //file id
+ $where = 'WHERE `fileid` = ?';
+ $params = array($file);
+ }
+ $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
+ `storage_mtime`, `encrypted`, `unencrypted_size`, `etag`
+ FROM `*PREFIX*filecache` ' . $where;
+ $result = \OC_DB::executeAudited($sql, $params);
+ $data = $result->fetchRow();
+
+ //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
+ //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
+ if ($data === null) {
+ $data = false;
+ }
+
+ //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['unencrypted_size'] = (int)$data['unencrypted_size'];
+ $data['storage'] = $this->storageId;
+ $data['mimetype'] = $this->getMimetype($data['mimetype']);
+ $data['mimepart'] = $this->getMimetype($data['mimepart']);
+ if ($data['storage_mtime'] == 0) {
+ $data['storage_mtime'] = $data['mtime'];
+ }
+ }
+
+ 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) {
+ $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
+ `storage_mtime`, `encrypted`, `unencrypted_size`, `etag`
+ FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
+ $result = \OC_DB::executeAudited($sql,array($fileId));
+ $files = $result->fetchAll();
+ foreach ($files as &$file) {
+ $file['mimetype'] = $this->getMimetype($file['mimetype']);
+ $file['mimepart'] = $this->getMimetype($file['mimepart']);
+ if ($file['storage_mtime'] == 0) {
+ $file['storage_mtime'] = $file['mtime'];
+ }
+ }
+ 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 {
+ // normalize file
+ $file = $this->normalize($file);
+
+ 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'] = \OC_Util::basename($file);
+
+ list($queryParts, $params) = $this->buildParts($data);
+ $queryParts[] = '`storage`';
+ $params[] = $this->getNumericStorageId();
+ $valuesPlaceholder = array_fill(0, count($queryParts), '?');
+
+ $sql = 'INSERT INTO `*PREFIX*filecache` (' . implode(', ', $queryParts) . ')'
+ . ' VALUES (' . implode(', ', $valuesPlaceholder) . ')';
+ \OC_DB::executeAudited($sql, $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) {
+
+ if(isset($data['path'])) {
+ // normalize path
+ $data['path'] = $this->normalize($data['path']);
+ }
+
+ if(isset($data['name'])) {
+ // normalize path
+ $data['name'] = $this->normalize($data['name']);
+ }
+
+ list($queryParts, $params) = $this->buildParts($data);
+ $params[] = $id;
+
+ $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE `fileid` = ?';
+ \OC_DB::executeAudited($sql, $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', 'storage_mtime', 'encrypted', 'unencrypted_size', '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);
+ } elseif ($name === 'storage_mtime') {
+ if (!isset($data['mtime'])) {
+ $params[] = $value;
+ $queryParts[] = '`mtime`';
+ }
+ } elseif ($name === 'encrypted') {
+ // Boolean to integer conversion
+ $value = $value ? 1 : 0;
+ }
+ $params[] = $value;
+ $queryParts[] = '`' . $name . '`';
+ }
+ }
+ return array($queryParts, $params);
+ }
+
+ /**
+ * get the file id for a file
+ *
+ * @param string $file
+ * @return int
+ */
+ public function getId($file) {
+ // normalize file
+ $file = $this->normalize($file);
+
+ $pathHash = md5($file);
+
+ $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
+ $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $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']);
+ }
+ }
+
+ $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
+ \OC_DB::executeAudited($sql, array($entry['fileid']));
+
+ $permissionsCache = new Permissions($this->storageId);
+ $permissionsCache->remove($entry['fileid']);
+ }
+
+ /**
+ * Move a file or folder in the cache
+ *
+ * @param string $source
+ * @param string $target
+ */
+ public function move($source, $target) {
+ // normalize source and target
+ $source = $this->normalize($source);
+ $target = $this->normalize($target);
+
+ $sourceData = $this->get($source);
+ $sourceId = $sourceData['fileid'];
+ $newParentId = $this->getParentId($target);
+
+ if ($sourceData['mimetype'] === 'httpd/unix-directory') {
+ //find all child entries
+ $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
+ $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $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);
+ \OC_DB::executeAudited($query, array($targetPath, md5($targetPath), $child['fileid']));
+ }
+ }
+
+ $sql = 'UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
+ \OC_DB::executeAudited($sql, array($target, md5($target), basename($target), $newParentId, $sourceId));
+ }
+
+ /**
+ * remove all entries for files that are stored on the storage from the cache
+ */
+ public function clear() {
+ $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
+ \OC_DB::executeAudited($sql, array($this->getNumericStorageId()));
+
+ $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
+ \OC_DB::executeAudited($sql, array($this->storageId));
+ }
+
+ /**
+ * @param string $file
+ *
+ * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
+ */
+ public function getStatus($file) {
+ // normalize file
+ $file = $this->normalize($file);
+
+ $pathHash = md5($file);
+ $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
+ $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $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) {
+
+ // normalize pattern
+ $pattern = $this->normalize($pattern);
+
+ $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag`
+ FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?';
+ $result = \OC_DB::executeAudited($sql, array($pattern, $this->getNumericStorageId()));
+ $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` = ?';
+ }
+ $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag`
+ FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
+ $mimetype = $this->getMimetypeId($mimetype);
+ $result = \OC_DB::executeAudited($sql, array($mimetype, $this->getNumericStorageId()));
+ $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 === '.' or $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) {
+ $totalSize = 0;
+ $entry = $this->get($path);
+ if ($entry && $entry['mimetype'] === 'httpd/unix-directory') {
+ $id = $entry['fileid'];
+ $sql = 'SELECT SUM(`size`), MIN(`size`) FROM `*PREFIX*filecache` '.
+ 'WHERE `parent` = ? AND `storage` = ?';
+ $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId()));
+ if ($row = $result->fetchRow()) {
+ list($sum, $min) = array_values($row);
+ $sum = (int)$sum;
+ $min = (int)$min;
+ if ($min === -1) {
+ $totalSize = $min;
+ } else {
+ $totalSize = $sum;
+ }
+ if ($entry['size'] !== $totalSize) {
+ $this->update($id, array('size' => $totalSize));
+ }
+
+ }
+ }
+ return $totalSize;
+ }
+
+ /**
+ * get all file ids on the files on the storage
+ *
+ * @return int[]
+ */
+ public function getAll() {
+ $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
+ $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId()));
+ $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',1);
+ $result = \OC_DB::executeAudited($query, array($this->getNumericStorageId()));
+ if ($row = $result->fetchRow()) {
+ return $row['path'];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * get the storage id of the storage for a file and the internal path of the file
+ *
+ * @param int $id
+ * @return array, first element holding the storage id, second the path
+ */
+ static public function getById($id) {
+ $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
+ $result = \OC_DB::executeAudited($sql, array($id));
+ if ($row = $result->fetchRow()) {
+ $numericId = $row['storage'];
+ $path = $row['path'];
+ } else {
+ return null;
+ }
+
+ if ($id = Storage::getStorageId($numericId)) {
+ return array($id, $path);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * normalize the given path
+ * @param $path
+ * @return string
+ */
+ public function normalize($path) {
+
+ return \OC_Util::normalizeUnicode($path);
+ }
+}
diff --git a/lib/private/files/cache/legacy.php b/lib/private/files/cache/legacy.php
new file mode 100644
index 00000000000..8eed1f67a5d
--- /dev/null
+++ b/lib/private/files/cache/legacy.php
@@ -0,0 +1,136 @@
+<?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;
+ }
+
+ /**
+ * get the numbers of items in the legacy cache
+ *
+ * @return int
+ */
+ function getCount() {
+ $sql = 'SELECT COUNT(`id`) AS `count` FROM `*PREFIX*fscache` WHERE `user` = ?';
+ $result = \OC_DB::executeAudited($sql, 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` = ?',1);
+ } catch (\Exception $e) {
+ $this->cacheHasItems = false;
+ return false;
+ }
+ try {
+ $result = $query->execute(array($this->user));
+ } catch (\Exception $e) {
+ $this->cacheHasItems = false;
+ return false;
+ }
+
+ if ($result === false || property_exists($result, 'error_message_prefix')) {
+ $this->cacheHasItems = false;
+ return false;
+ }
+
+ $this->cacheHasItems = (bool)$result->fetchRow();
+ return $this->cacheHasItems;
+ }
+
+ /**
+ * get an item from the legacy cache
+ *
+ * @param string|int $path
+ * @return array
+ */
+ function get($path) {
+ if (is_numeric($path)) {
+ $sql = 'SELECT * FROM `*PREFIX*fscache` WHERE `id` = ?';
+ } else {
+ $sql = 'SELECT * FROM `*PREFIX*fscache` WHERE `path` = ?';
+ }
+ $result = \OC_DB::executeAudited($sql, array($path));
+ $data = $result->fetchRow();
+ $data['etag'] = $this->getEtag($data['path'], $data['user']);
+ return $data;
+ }
+
+ /**
+ * Get the ETag for the given path
+ *
+ * @param type $path
+ * @return string
+ */
+ function getEtag($path, $user = null) {
+ static $query = null;
+
+ $pathDetails = explode('/', $path, 4);
+ if((!$user) && !isset($pathDetails[1])) {
+ //no user!? Too odd, return empty string.
+ return '';
+ } else if(!$user) {
+ //guess user from path, if no user passed.
+ $user = $pathDetails[1];
+ }
+
+ if(!isset($pathDetails[3]) || is_null($pathDetails[3])) {
+ $relativePath = '';
+ } else {
+ $relativePath = $pathDetails[3];
+ }
+
+ if(is_null($query)){
+ $query = \OC_DB::prepare('SELECT `propertyvalue` FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = \'{DAV:}getetag\'');
+ }
+ $result = \OC_DB::executeAudited($query,array($user, '/' . $relativePath));
+ if ($row = $result->fetchRow()) {
+ return trim($row['propertyvalue'], '"');
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * get all child items of an item from the legacy cache
+ *
+ * @param int $id
+ * @return array
+ */
+ function getChildren($id) {
+ $result = \OC_DB::executeAudited('SELECT * FROM `*PREFIX*fscache` WHERE `parent` = ?', array($id));
+ $data = $result->fetchAll();
+ foreach ($data as $i => $item) {
+ $data[$i]['etag'] = $this->getEtag($item['path'], $item['user']);
+ }
+ return $data;
+ }
+}
diff --git a/lib/private/files/cache/permissions.php b/lib/private/files/cache/permissions.php
new file mode 100644
index 00000000000..2e2bdb20b78
--- /dev/null
+++ b/lib/private/files/cache/permissions.php
@@ -0,0 +1,143 @@
+<?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) {
+ $sql = 'SELECT `permissions` FROM `*PREFIX*permissions` WHERE `user` = ? AND `fileid` = ?';
+ $result = \OC_DB::executeAudited($sql, 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) {
+ $sql = 'UPDATE `*PREFIX*permissions` SET `permissions` = ? WHERE `user` = ? AND `fileid` = ?';
+ } else {
+ $sql = 'INSERT INTO `*PREFIX*permissions`(`permissions`, `user`, `fileid`) VALUES(?, ?,? )';
+ }
+ \OC_DB::executeAudited($sql, 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), '?'));
+
+ $sql = 'SELECT `fileid`, `permissions` FROM `*PREFIX*permissions`'
+ . ' WHERE `fileid` IN (' . $inPart . ') AND `user` = ?';
+ $result = \OC_DB::executeAudited($sql, $params);
+ $filePermissions = array();
+ while ($row = $result->fetchRow()) {
+ $filePermissions[$row['fileid']] = $row['permissions'];
+ }
+ return $filePermissions;
+ }
+
+ /**
+ * get the permissions for all files in a folder
+ *
+ * @param int $parentId
+ * @param string $user
+ * @return int[]
+ */
+ public function getDirectoryPermissions($parentId, $user) {
+ $sql = 'SELECT `*PREFIX*permissions`.`fileid`, `permissions`
+ FROM `*PREFIX*permissions`
+ INNER JOIN `*PREFIX*filecache` ON `*PREFIX*permissions`.`fileid` = `*PREFIX*filecache`.`fileid`
+ WHERE `*PREFIX*filecache`.`parent` = ? AND `*PREFIX*permissions`.`user` = ?';
+
+ $result = \OC_DB::executeAudited($sql, array($parentId, $user));
+ $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 = null) {
+ if (is_null($user)) {
+ \OC_DB::executeAudited('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ?', array($fileId));
+ } else {
+ $sql = 'DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?';
+ \OC_DB::executeAudited($sql, array($fileId, $user));
+ }
+ }
+
+ public function removeMultiple($fileIds, $user) {
+ $query = \OC_DB::prepare('DELETE FROM `*PREFIX*permissions` WHERE `fileid` = ? AND `user` = ?');
+ foreach ($fileIds as $fileId) {
+ \OC_DB::executeAudited($query, array($fileId, $user));
+ }
+ }
+
+ /**
+ * get the list of users which have permissions stored for a file
+ *
+ * @param int $fileId
+ */
+ public function getUsers($fileId) {
+ $sql = 'SELECT `user` FROM `*PREFIX*permissions` WHERE `fileid` = ?';
+ $result = \OC_DB::executeAudited($sql, array($fileId));
+ $users = array();
+ while ($row = $result->fetchRow()) {
+ $users[] = $row['user'];
+ }
+ return $users;
+ }
+}
diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php
new file mode 100644
index 00000000000..96f84609cf2
--- /dev/null
+++ b/lib/private/files/cache/scanner.php
@@ -0,0 +1,258 @@
+<?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;
+
+use OC\Files\Filesystem;
+use OC\Hooks\BasicEmitter;
+
+/**
+ * Class Scanner
+ *
+ * Hooks available in scope \OC\Files\Cache\Scanner:
+ * - scanFile(string $path, string $storageId)
+ * - scanFolder(string $path, string $storageId)
+ *
+ * @package OC\Files\Cache
+ */
+class Scanner extends BasicEmitter {
+ /**
+ * @var \OC\Files\Storage\Storage $storage
+ */
+ private $storage;
+
+ /**
+ * @var string $storageId
+ */
+ private $storageId;
+
+ /**
+ * @var \OC\Files\Cache\Cache $cache
+ */
+ private $cache;
+
+ /**
+ * @var \OC\Files\Cache\Permissions $permissionsCache
+ */
+ private $permissionsCache;
+
+ const SCAN_RECURSIVE = true;
+ const SCAN_SHALLOW = false;
+
+ const REUSE_ETAG = 1;
+ const REUSE_SIZE = 2;
+
+ public function __construct(\OC\Files\Storage\Storage $storage) {
+ $this->storage = $storage;
+ $this->storageId = $this->storage->getId();
+ $this->cache = $storage->getCache();
+ $this->permissionsCache = $storage->getPermissionsCache();
+ }
+
+ /**
+ * 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);
+ $data['storage_mtime'] = $data['mtime'];
+ return $data;
+ }
+
+ /**
+ * scan a single file and store it in the cache
+ *
+ * @param string $file
+ * @param int $reuseExisting
+ * @param bool $parentExistsInCache
+ * @return array with metadata of the scanned file
+ */
+ public function scanFile($file, $reuseExisting = 0, $parentExistsInCache = false) {
+ if (!self::isPartialFile($file)
+ and !Filesystem::isFileBlacklisted($file)
+ ) {
+ $this->emit('\OC\Files\Cache\Scanner', 'scanFile', array($file, $this->storageId));
+ \OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', array('path' => $file, 'storage' => $this->storageId));
+ $data = $this->getData($file);
+ if ($data) {
+ if ($file and !$parentExistsInCache) {
+ $parent = dirname($file);
+ if ($parent === '.' or $parent === '/') {
+ $parent = '';
+ }
+ if (!$this->cache->inCache($parent)) {
+ $this->scanFile($parent);
+ }
+ }
+ $newData = $data;
+ $cacheData = $this->cache->get($file);
+ if ($cacheData) {
+ $this->permissionsCache->remove($cacheData['fileid']);
+ if ($reuseExisting) {
+ // prevent empty etag
+ $etag = $cacheData['etag'];
+ $propagateETagChange = false;
+ if (empty($etag)) {
+ $etag = $data['etag'];
+ $propagateETagChange = true;
+ }
+ // only reuse data if the file hasn't explicitly changed
+ if (isset($data['mtime']) && isset($cacheData['mtime']) && $data['mtime'] === $cacheData['mtime']) {
+ if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
+ $data['size'] = $cacheData['size'];
+ }
+ if ($reuseExisting & self::REUSE_ETAG) {
+ $data['etag'] = $etag;
+ if ($propagateETagChange) {
+ $parent = $file;
+ while ($parent !== '') {
+ $parent = dirname($parent);
+ if ($parent === '.') {
+ $parent = '';
+ }
+ $parentCacheData = $this->cache->get($parent);
+ $this->cache->update($parentCacheData['fileid'], array(
+ 'etag' => $this->storage->getETag($parent),
+ ));
+ }
+ }
+ }
+ }
+ // Only update metadata that has changed
+ $newData = array_diff($data, $cacheData);
+ }
+ }
+ if (!empty($newData)) {
+ $this->cache->put($file, $newData);
+ }
+ } else {
+ $this->cache->remove($file);
+ }
+ return $data;
+ }
+ return null;
+ }
+
+ /**
+ * scan a folder and all it's children
+ *
+ * @param string $path
+ * @param bool $recursive
+ * @param int $reuse
+ * @return int the size of the scanned folder or -1 if the size is unknown at this stage
+ */
+ public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1) {
+ if ($reuse === -1) {
+ $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0;
+ }
+ $this->scanFile($path, $reuse);
+ return $this->scanChildren($path, $recursive, $reuse);
+ }
+
+ /**
+ * scan all the files and folders in a folder
+ *
+ * @param string $path
+ * @param bool $recursive
+ * @param int $reuse
+ * @return int the size of the scanned folder or -1 if the size is unknown at this stage
+ */
+ public function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1) {
+ if ($reuse === -1) {
+ $reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : 0;
+ }
+ $this->emit('\OC\Files\Cache\Scanner', 'scanFolder', array($path, $this->storageId));
+ $size = 0;
+ $childQueue = array();
+ $existingChildren = array();
+ if ($this->cache->inCache($path)) {
+ $children = $this->cache->getFolderContents($path);
+ foreach ($children as $child) {
+ $existingChildren[] = $child['name'];
+ }
+ }
+ $newChildren = array();
+ if ($this->storage->is_dir($path) && ($dh = $this->storage->opendir($path))) {
+ \OC_DB::beginTransaction();
+ if (is_resource($dh)) {
+ while (($file = readdir($dh)) !== false) {
+ $child = ($path) ? $path . '/' . $file : $file;
+ if (!Filesystem::isIgnoredDir($file)) {
+ $newChildren[] = $file;
+ $data = $this->scanFile($child, $reuse, true);
+ if ($data) {
+ if ($data['size'] === -1) {
+ if ($recursive === self::SCAN_RECURSIVE) {
+ $childQueue[] = $child;
+ } else {
+ $size = -1;
+ }
+ } else if ($size !== -1) {
+ $size += $data['size'];
+ }
+ }
+ }
+ }
+ }
+ $removedChildren = \array_diff($existingChildren, $newChildren);
+ foreach ($removedChildren as $childName) {
+ $child = ($path) ? $path . '/' . $childName : $childName;
+ $this->cache->remove($child);
+ }
+ \OC_DB::commit();
+ foreach ($childQueue as $child) {
+ $childSize = $this->scanChildren($child, self::SCAN_RECURSIVE, $reuse);
+ if ($childSize === -1) {
+ $size = -1;
+ } else {
+ $size += $childSize;
+ }
+ }
+ $this->cache->put($path, array('size' => $size));
+ }
+ return $size;
+ }
+
+ /**
+ * @brief check if the file should be ignored when scanning
+ * NOTE: files with a '.part' extension are ignored as well!
+ * prevents unfinished put requests to be scanned
+ * @param String $file
+ * @return boolean
+ */
+ public static function isPartialFile($file) {
+ if (pathinfo($file, PATHINFO_EXTENSION) === 'part') {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * walk over any folders that are not fully scanned yet and scan them
+ */
+ public function backgroundScan() {
+ $lastPath = null;
+ while (($path = $this->cache->getIncomplete()) !== false && $path !== $lastPath) {
+ $this->scan($path);
+ $this->cache->correctFolderSize($path);
+ $lastPath = $path;
+ }
+ }
+}
diff --git a/lib/private/files/cache/storage.php b/lib/private/files/cache/storage.php
new file mode 100644
index 00000000000..8a9e47ca36d
--- /dev/null
+++ b/lib/private/files/cache/storage.php
@@ -0,0 +1,60 @@
+<?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\Cache;
+
+/**
+ * Class Storage
+ *
+ * cache storage specific data
+ *
+ * @package OC\Files\Cache
+ */
+class Storage {
+ private $storageId;
+ private $numericId;
+
+ /**
+ * @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;
+ }
+ if (strlen($this->storageId) > 64) {
+ $this->storageId = md5($this->storageId);
+ }
+
+ $sql = 'SELECT `numeric_id` FROM `*PREFIX*storages` WHERE `id` = ?';
+ $result = \OC_DB::executeAudited($sql, array($this->storageId));
+ if ($row = $result->fetchRow()) {
+ $this->numericId = $row['numeric_id'];
+ } else {
+ $sql = 'INSERT INTO `*PREFIX*storages` (`id`) VALUES(?)';
+ \OC_DB::executeAudited($sql, array($this->storageId));
+ $this->numericId = \OC_DB::insertid('*PREFIX*storages');
+ }
+ }
+
+ public function getNumericId() {
+ return $this->numericId;
+ }
+
+ public static function getStorageId($numericId) {
+
+ $sql = 'SELECT `id` FROM `*PREFIX*storages` WHERE `numeric_id` = ?';
+ $result = \OC_DB::executeAudited($sql, array($numericId));
+ if ($row = $result->fetchRow()) {
+ return $row['id'];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/lib/private/files/cache/updater.php b/lib/private/files/cache/updater.php
new file mode 100644
index 00000000000..1f30173a8f8
--- /dev/null
+++ b/lib/private/files/cache/updater.php
@@ -0,0 +1,161 @@
+<?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;
+use OCP\Util;
+
+/**
+ * listen to filesystem hooks and change the cache accordingly
+ */
+class Updater {
+
+ /**
+ * resolve a path to a storage and internal path
+ *
+ * @param string $path the relative 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);
+ }
+
+ /**
+ * perform a write update
+ *
+ * @param string $path the relative path of the file
+ */
+ 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));
+ }
+ }
+
+ /**
+ * perform a delete update
+ *
+ * @param string $path the relative path of the file
+ */
+ 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());
+ }
+ }
+
+ /**
+ * preform a rename update
+ *
+ * @param string $from the relative path of the source file
+ * @param string $to the relative path of the target file
+ */
+ static public function renameUpdate($from, $to) {
+ /**
+ * @var \OC\Files\Storage\Storage $storageFrom
+ * @var \OC\Files\Storage\Storage $storageTo
+ * @var string $internalFrom
+ * @var string $internalTo
+ */
+ list($storageFrom, $internalFrom) = self::resolvePath($from);
+ list($storageTo, $internalTo) = self::resolvePath($to);
+ if ($storageFrom && $storageTo) {
+ if ($storageFrom === $storageTo) {
+ $cache = $storageFrom->getCache($internalFrom);
+ $cache->move($internalFrom, $internalTo);
+ $cache->correctFolderSize($internalFrom);
+ $cache->correctFolderSize($internalTo);
+ self::correctFolder($from, time());
+ self::correctFolder($to, time());
+ } else {
+ self::deleteUpdate($from);
+ self::writeUpdate($to);
+ }
+ }
+ }
+
+ /**
+ * 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 === '\\') {
+ $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);
+ } else {
+ Util::writeLog('core', 'Path not in cache: '.$internalPath, Util::ERROR);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param array $params
+ */
+ static public function writeHook($params) {
+ self::writeUpdate($params['path']);
+ }
+
+ /**
+ * @param array $params
+ */
+ static public function touchHook($params) {
+ $path = $params['path'];
+ list($storage, $internalPath) = self::resolvePath($path);
+ $cache = $storage->getCache();
+ $id = $cache->getId($internalPath);
+ if ($id !== -1) {
+ $cache->update($id, array('etag' => $storage->getETag($internalPath)));
+ }
+ self::writeUpdate($path);
+ }
+
+ /**
+ * @param array $params
+ */
+ static public function renameHook($params) {
+ self::renameUpdate($params['oldpath'], $params['newpath']);
+ }
+
+ /**
+ * @param array $params
+ */
+ static public function deleteHook($params) {
+ self::deleteUpdate($params['path']);
+ }
+}
diff --git a/lib/private/files/cache/upgrade.php b/lib/private/files/cache/upgrade.php
new file mode 100644
index 00000000000..cfb9a117311
--- /dev/null
+++ b/lib/private/files/cache/upgrade.php
@@ -0,0 +1,227 @@
+<?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 upgrade a path and it's childs
+ *
+ * @param string $path
+ * @param bool $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);
+ if ($data) {
+ $this->insert($data);
+ $this->upgradeChilds($data['id'], $mode);
+ }
+ }
+ }
+
+ /**
+ * upgrade all child elements of an item
+ *
+ * @param int $id
+ * @param bool $mode
+ */
+ 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']);
+ if ($childData) {
+ $this->insert($childData);
+ if ($mode == Scanner::SCAN_RECURSIVE) {
+ $this->upgradeChilds($child['id']);
+ }
+ }
+ }
+ }
+
+ /**
+ * insert data into the new cache
+ *
+ * @param array $data the data for the new cache
+ */
+ function insert($data) {
+ static $insertQuery = null;
+ if(is_null($insertQuery)) {
+ $insertQuery = \OC_DB::prepare('INSERT INTO `*PREFIX*filecache`
+ ( `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `etag` )
+ VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
+ }
+ if (!$this->inCache($data['storage'], $data['path_hash'], $data['id'])) {
+ \OC_DB::executeAudited($insertQuery, array($data['id'], $data['storage'],
+ $data['path'], $data['path_hash'], $data['parent'], $data['name'],
+ $data['mimetype'], $data['mimepart'], $data['size'], $data['mtime'], $data['encrypted'], $data['etag']));
+ }
+ }
+
+ /**
+ * check if an item is already in the new cache
+ *
+ * @param string $storage
+ * @param string $pathHash
+ * @param string $id
+ * @return bool
+ */
+ function inCache($storage, $pathHash, $id) {
+ static $query = null;
+ if(is_null($query)) {
+ $query = \OC_DB::prepare('SELECT `fileid` FROM `*PREFIX*filecache` WHERE (`storage` = ? AND `path_hash` = ?) OR `fileid` = ?');
+ }
+ $result = \OC_DB::executeAudited($query, array($storage, $pathHash, $id));
+ return (bool)$result->fetchRow();
+ }
+
+ /**
+ * get the new data array from the old one
+ *
+ * @param array $data the data from the old cache
+ * Example data array
+ * Array
+ * (
+ * [id] => 418
+ * [path] => /tina/files/picture.jpg //relative to datadir
+ * [path_hash] => 66d4547e372888deed80b24fec9b192b
+ * [parent] => 234
+ * [name] => picture.jpg
+ * [user] => tina
+ * [size] => 1265283
+ * [ctime] => 1363909709
+ * [mtime] => 1363909709
+ * [mimetype] => image/jpeg
+ * [mimepart] => image
+ * [encrypted] => 0
+ * [versioned] => 0
+ * [writable] => 1
+ * )
+ *
+ * @return array
+ */
+ function getNewData($data) {
+ //Make sure there is a path, otherwise we can do nothing.
+ if(!isset($data['path'])) {
+ return false;
+ }
+ $newData = $data;
+ /**
+ * @var \OC\Files\Storage\Storage $storage
+ * @var string $internalPath;
+ */
+ list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($data['path']);
+ if ($storage) {
+ $newData['etag'] = $data['etag'];
+ $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;
+ } else {
+ \OC_Log::write('core', 'Unable to migrate data from old cache for '.$data['path'].' because the storage was not found', \OC_Log::ERROR);
+ return false;
+ }
+ }
+
+ /**
+ * 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];
+ }
+
+ /**
+ * get the numeric id for a mimetype
+ *
+ * @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);
+ }
+
+ /**
+ * Does a "silent" upgrade, i.e. without an Event-Source as triggered
+ * on User-Login via Ajax. This method is called within the regular
+ * ownCloud upgrade.
+ *
+ * @param string $user a User ID
+ */
+ public static function doSilentUpgrade($user) {
+ if(!self::needUpgrade($user)) {
+ return;
+ }
+ $legacy = new \OC\Files\Cache\Legacy($user);
+ if ($legacy->hasItems()) {
+ \OC_DB::beginTransaction();
+ $upgrade = new \OC\Files\Cache\Upgrade($legacy);
+ $upgrade->upgradePath('/' . $user . '/files');
+ \OC_DB::commit();
+ }
+ \OC\Files\Cache\Upgrade::upgradeDone($user);
+ }
+}
diff --git a/lib/private/files/cache/watcher.php b/lib/private/files/cache/watcher.php
new file mode 100644
index 00000000000..8bfd4602f3a
--- /dev/null
+++ b/lib/private/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['storage_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']);
+ }
+ }
+ }
+}