Merge pull request #17765 from nextcloud/filecache-extension

Upload time and Creation time
This commit is contained in:
Roeland Jago Douma 2019-11-18 15:29:49 +01:00 committed by GitHub
commit 5320f08cd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 871 additions and 456 deletions

View file

@ -303,6 +303,19 @@ class File extends Node implements IFile {
}
}
$fileInfoUpdate = [
'upload_time' => time()
];
// allow sync clients to send the creation time along in a header
if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
$ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
$fileInfoUpdate['creation_time'] = $ctime;
$this->header('X-OC-CTime: accepted');
}
$this->fileView->putFileInfo($this->path, $fileInfoUpdate);
if ($view) {
$this->emitPostHooks($exists);
}

View file

@ -70,6 +70,9 @@ class FilesPlugin extends ServerPlugin {
const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
const SHARE_NOTE = '{http://nextcloud.org/ns}note';
/**
@ -400,6 +403,14 @@ class FilesPlugin extends ServerPlugin {
return new ChecksumList($checksum);
});
$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function() use ($node) {
return $node->getFileInfo()->getCreationTime();
});
$propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function() use ($node) {
return $node->getFileInfo()->getUploadTime();
});
}
if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
@ -470,6 +481,13 @@ class FilesPlugin extends ServerPlugin {
}
return false;
});
$propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function($time) use ($node) {
if (empty($time)) {
return false;
}
$node->setCreationTime((int) $time);
return true;
});
}
/**

View file

@ -201,6 +201,14 @@ abstract class Node implements \Sabre\DAV\INode {
return $this->fileView->putFileInfo($this->path, array('etag' => $etag));
}
public function setCreationTime(int $time) {
return $this->fileView->putFileInfo($this->path, array('creation_time' => $time));
}
public function setUploadTime(int $time) {
return $this->fileView->putFileInfo($this->path, array('upload_time' => $time));
}
/**
* Returns the size of the node, in bytes
*

View file

@ -177,4 +177,12 @@ class TrashItem implements ITrashItem {
public function getTitle(): string {
return $this->getOriginalLocation();
}
public function getCreationTime(): int {
return $this->fileInfo->getCreationTime();
}
public function getUploadTime(): int {
return $this->fileInfo->getUploadTime();
}
}

View file

@ -44,7 +44,7 @@ class Version17000Date20190514105811 extends SimpleMigrationStep {
$schema = $schemaClosure();
if(!$schema->hasTable('filecache_extended')) {
$table = $schema->createTable('filecache_extended');
$table->addColumn('fileid', Type::INTEGER, [
$table->addColumn('fileid', Type::BIGINT, [
'notnull' => true,
'length' => 4,
'unsigned' => true,

View file

@ -866,6 +866,7 @@ return array(
'OC\\Files\\Cache\\AbstractCacheEvent' => $baseDir . '/lib/private/Files/Cache/AbstractCacheEvent.php',
'OC\\Files\\Cache\\Cache' => $baseDir . '/lib/private/Files/Cache/Cache.php',
'OC\\Files\\Cache\\CacheEntry' => $baseDir . '/lib/private/Files/Cache/CacheEntry.php',
'OC\\Files\\Cache\\CacheQueryBuilder' => $baseDir . '/lib/private/Files/Cache/CacheQueryBuilder.php',
'OC\\Files\\Cache\\FailedCache' => $baseDir . '/lib/private/Files/Cache/FailedCache.php',
'OC\\Files\\Cache\\HomeCache' => $baseDir . '/lib/private/Files/Cache/HomeCache.php',
'OC\\Files\\Cache\\HomePropagator' => $baseDir . '/lib/private/Files/Cache/HomePropagator.php',

View file

@ -895,6 +895,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Files\\Cache\\AbstractCacheEvent' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/AbstractCacheEvent.php',
'OC\\Files\\Cache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/Cache.php',
'OC\\Files\\Cache\\CacheEntry' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/CacheEntry.php',
'OC\\Files\\Cache\\CacheQueryBuilder' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/CacheQueryBuilder.php',
'OC\\Files\\Cache\\FailedCache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/FailedCache.php',
'OC\\Files\\Cache\\HomeCache' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/HomeCache.php',
'OC\\Files\\Cache\\HomePropagator' => __DIR__ . '/../../..' . '/lib/private/Files/Cache/HomePropagator.php',

View file

@ -72,9 +72,10 @@ class FunctionBuilder implements IFunctionBuilder {
return new QueryFunction($this->helper->quoteColumnName($x) . ' - ' . $this->helper->quoteColumnName($y));
}
public function count($count, $alias = '') {
public function count($count = '', $alias = '') {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
return new QueryFunction('COUNT(' . $this->helper->quoteColumnName($count) . ')' . $alias);
$quotedName = $count === '' ? '*' : $this->helper->quoteColumnName($count);
return new QueryFunction('COUNT(' . $quotedName . ')' . $alias);
}
public function max($field) {

View file

@ -245,7 +245,7 @@ class QueryBuilder implements IQueryBuilder {
* @param mixed $value The parameter value.
* @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function setParameter($key, $value, $type = null) {
$this->queryBuilder->setParameter($key, $value, $type);
@ -270,7 +270,7 @@ class QueryBuilder implements IQueryBuilder {
* @param array $params The query parameters to set.
* @param array $types The query parameters types to set.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function setParameters(array $params, array $types = array()) {
$this->queryBuilder->setParameters($params, $types);
@ -323,7 +323,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param integer $firstResult The first result to return.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function setFirstResult($firstResult) {
$this->queryBuilder->setFirstResult($firstResult);
@ -350,7 +350,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param integer $maxResults The maximum number of results to retrieve.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function setMaxResults($maxResults) {
$this->queryBuilder->setMaxResults($maxResults);
@ -381,7 +381,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$selects The selection expressions.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* '@return $this This QueryBuilder instance.
*/
public function select(...$selects) {
if (count($selects) === 1 && is_array($selects[0])) {
@ -408,7 +408,7 @@ class QueryBuilder implements IQueryBuilder {
* @param mixed $select The selection expressions.
* @param string $alias The column alias used in the constructed query.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function selectAlias($select, $alias) {
@ -430,7 +430,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed $select The selection expressions.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function selectDistinct($select) {
@ -454,7 +454,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$selects The selection expression.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function addSelect(...$selects) {
if (count($selects) === 1 && is_array($selects[0])) {
@ -482,7 +482,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $delete The table whose rows are subject to the deletion.
* @param string $alias The table alias used in the constructed query.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function delete($delete = null, $alias = null) {
$this->queryBuilder->delete(
@ -507,7 +507,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $update The table whose rows are subject to the update.
* @param string $alias The table alias used in the constructed query.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function update($update = null, $alias = null) {
$this->queryBuilder->update(
@ -535,7 +535,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param string $insert The table into which the rows should be inserted.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function insert($insert = null) {
$this->queryBuilder->insert(
@ -560,7 +560,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $from The table.
* @param string|null $alias The alias of the table.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function from($from, $alias = null) {
$this->queryBuilder->from(
@ -586,7 +586,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function join($fromAlias, $join, $alias, $condition = null) {
$this->queryBuilder->join(
@ -614,7 +614,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function innerJoin($fromAlias, $join, $alias, $condition = null) {
$this->queryBuilder->innerJoin(
@ -642,7 +642,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function leftJoin($fromAlias, $join, $alias, $condition = null) {
$this->queryBuilder->leftJoin(
@ -670,7 +670,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function rightJoin($fromAlias, $join, $alias, $condition = null) {
$this->queryBuilder->rightJoin(
@ -696,7 +696,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $key The column to set.
* @param string $value The value, expression, placeholder, etc.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function set($key, $value) {
$this->queryBuilder->set(
@ -731,7 +731,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$predicates The restriction predicates.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function where(...$predicates) {
call_user_func_array(
@ -756,7 +756,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$where The query restrictions.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*
* @see where()
*/
@ -783,7 +783,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$where The WHERE statement.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*
* @see where()
*/
@ -809,7 +809,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$groupBys The grouping expression.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function groupBy(...$groupBys) {
if (count($groupBys) === 1 && is_array($groupBys[0])) {
@ -837,7 +837,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$groupBy The grouping expression.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function addGroupBy(...$groupBys) {
if (count($groupBys) === 1 && is_array($groupBys[0])) {
@ -869,7 +869,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $column The column into which the value should be inserted.
* @param string $value The value that should be inserted into the column.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function setValue($column, $value) {
$this->queryBuilder->setValue(
@ -897,7 +897,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param array $values The values to specify for the insert query indexed by column names.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function values(array $values) {
$quotedValues = [];
@ -916,7 +916,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$having The restriction over the groups.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function having(...$having) {
call_user_func_array(
@ -933,7 +933,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$having The restriction to append.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function andHaving(...$having) {
call_user_func_array(
@ -950,7 +950,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param mixed ...$having The restriction to add.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function orHaving(...$having) {
call_user_func_array(
@ -968,7 +968,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $sort The ordering expression.
* @param string $order The ordering direction.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function orderBy($sort, $order = null) {
$this->queryBuilder->orderBy(
@ -985,7 +985,7 @@ class QueryBuilder implements IQueryBuilder {
* @param string $sort The ordering expression.
* @param string $order The ordering direction.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function addOrderBy($sort, $order = null) {
$this->queryBuilder->addOrderBy(
@ -1021,7 +1021,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param array|null $queryPartNames
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function resetQueryParts($queryPartNames = null) {
$this->queryBuilder->resetQueryParts($queryPartNames);
@ -1034,7 +1034,7 @@ class QueryBuilder implements IQueryBuilder {
*
* @param string $queryPartName
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*/
public function resetQueryPart($queryPartName) {
$this->queryBuilder->resetQueryPart($queryPartName);

View file

@ -45,6 +45,7 @@ use OCP\Files\Cache\CacheInsertEvent;
use OCP\Files\Cache\CacheUpdateEvent;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\ICacheEntry;
use OCP\Files\FileInfo;
use \OCP\Files\IMimeTypeLoader;
use OCP\Files\Search\ISearchQuery;
use OCP\Files\Storage\IStorage;
@ -68,7 +69,7 @@ class Cache implements ICache {
/**
* @var array partial data for the cache
*/
protected $partial = array();
protected $partial = [];
/**
* @var string
@ -112,6 +113,15 @@ class Cache implements ICache {
$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
}
private function getQueryBuilder() {
return new CacheQueryBuilder(
$this->connection,
\OC::$server->getSystemConfig(),
\OC::$server->getLogger(),
$this
);
}
/**
* Get the numeric storage id for this cache's storage
*
@ -128,34 +138,24 @@ class Cache implements ICache {
* @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
*/
public function get($file) {
$query = $this->getQueryBuilder();
$query->selectFileCache();
if (is_string($file) or $file == '') {
// normalize file
$file = $this->normalize($file);
$where = 'WHERE `storage` = ? AND `path_hash` = ?';
$params = array($this->getNumericStorageId(), md5($file));
$query->whereStorageId()
->wherePath($file);
} else { //file id
$where = 'WHERE `fileid` = ?';
$params = array($file);
$query->whereFileId($file);
}
$sql = 'SELECT `fileid`, `storage`, `path`, `path_hash`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
`storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
FROM `*PREFIX*filecache` ' . $where;
$result = $this->connection->executeQuery($sql, $params);
$data = $result->fetch();
//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;
}
$data = $query->execute()->fetch();
//merge partial data
if (!$data and is_string($file)) {
if (isset($this->partial[$file])) {
$data = $this->partial[$file];
}
return $data;
if (!$data and is_string($file) and isset($this->partial[$file])) {
return $this->partial[$file];
} else if (!$data) {
return $data;
} else {
@ -187,6 +187,12 @@ class Cache implements ICache {
$data['storage_mtime'] = $data['mtime'];
}
$data['permissions'] = (int)$data['permissions'];
if (isset($data['creation_time'])) {
$data['creation_time'] = (int) $data['creation_time'];
}
if (isset($data['upload_time'])) {
$data['upload_time'] = (int) $data['upload_time'];
}
return new CacheEntry($data);
}
@ -209,11 +215,12 @@ class Cache implements ICache {
*/
public function getFolderContentsById($fileId) {
if ($fileId > -1) {
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
`storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
$result = $this->connection->executeQuery($sql, [$fileId]);
$files = $result->fetchAll();
$query = $this->getQueryBuilder();
$query->selectFileCache()
->whereParent($fileId)
->orderBy('name', 'ASC');
$files = $query->execute()->fetchAll();
return array_map(function (array $data) {
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}, $files);
@ -259,7 +266,7 @@ class Cache implements ICache {
unset($this->partial[$file]);
}
$requiredFields = array('size', 'mtime', 'mimetype');
$requiredFields = ['size', 'mtime', 'mimetype'];
foreach ($requiredFields as $field) {
if (!isset($data[$field])) { //data not complete save as partial and return
$this->partial[$file] = $data;
@ -271,14 +278,8 @@ class Cache implements ICache {
$data['parent'] = $this->getParentId($file);
$data['name'] = basename($file);
list($queryParts, $params) = $this->buildParts($data);
$queryParts[] = '`storage`';
$params[] = $this->getNumericStorageId();
$queryParts = array_map(function ($item) {
return trim($item, "`");
}, $queryParts);
$values = array_combine($queryParts, $params);
[$values, $extensionValues] = $this->normalizeData($data);
$values['storage'] = $this->getNumericStorageId();
try {
$builder = $this->connection->getQueryBuilder();
@ -289,7 +290,19 @@ class Cache implements ICache {
}
if ($builder->execute()) {
$fileId = (int)$this->connection->lastInsertId('*PREFIX*filecache');
$fileId = $builder->getLastInsertId();
if (count($extensionValues)) {
$query = $this->getQueryBuilder();
$query->insert('filecache_extended');
$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
foreach ($extensionValues as $column => $value) {
$query->setValue($column, $query->createNamedParameter($value));
}
$query->execute();
}
$this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
return $fileId;
}
@ -324,20 +337,56 @@ class Cache implements ICache {
$data['name'] = $this->normalize($data['name']);
}
list($queryParts, $params) = $this->buildParts($data);
// duplicate $params because we need the parts twice in the SQL statement
// once for the SET part, once in the WHERE clause
$params = array_merge($params, $params);
$params[] = $id;
[$values, $extensionValues] = $this->normalizeData($data);
// don't update if the data we try to set is the same as the one in the record
// some databases (Postgres) don't like superfluous updates
$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
'WHERE (' .
implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
') AND `fileid` = ? ';
$this->connection->executeQuery($sql, $params);
if (count($values)) {
$query = $this->getQueryBuilder();
$query->update('filecache')
->whereFileId($id)
->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
return $query->expr()->orX(
$query->expr()->neq($key, $query->createNamedParameter($value)),
$query->expr()->isNull($key)
);
}, array_keys($values), array_values($values))));
foreach ($values as $key => $value) {
$query->set($key, $query->createNamedParameter($value));
}
$query->execute();
}
if (count($extensionValues)) {
try {
$query = $this->getQueryBuilder();
$query->insert('filecache_extended');
$query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
foreach ($extensionValues as $column => $value) {
$query->setValue($column, $query->createNamedParameter($value));
}
$query->execute();
} catch (UniqueConstraintViolationException $e) {
$query = $this->getQueryBuilder();
$query->update('filecache_extended')
->whereFileId($id)
->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
return $query->expr()->orX(
$query->expr()->neq($key, $query->createNamedParameter($value)),
$query->expr()->isNull($key)
);
}, array_keys($extensionValues), array_values($extensionValues))));
foreach ($extensionValues as $key => $value) {
$query->set($key, $query->createNamedParameter($value));
}
$query->execute();
}
}
$path = $this->getPathById($id);
// path can still be null if the file doesn't exist
@ -350,14 +399,13 @@ class Cache implements ICache {
* extract query parts and params array from data array
*
* @param array $data
* @return array [$queryParts, $params]
* $queryParts: string[], the (escaped) column names to be set in the query
* $params: mixed[], the new values for the columns, to be passed as params to the query
* @return array
*/
protected function buildParts(array $data) {
$fields = array(
protected function normalizeData(array $data): array {
$fields = [
'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
'etag', 'permissions', 'checksum', 'storage');
'etag', 'permissions', 'checksum', 'storage'];
$extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
$doNotCopyStorageMTime = false;
if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
@ -366,23 +414,20 @@ class Cache implements ICache {
$doNotCopyStorageMTime = true;
}
$params = array();
$queryParts = array();
$params = [];
$extensionParams = [];
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->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
$queryParts[] = '`mimepart`';
$params['path_hash'] = md5($value);
} else if ($name === 'mimetype') {
$params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
$value = $this->mimetypeLoader->getId($value);
} elseif ($name === 'storage_mtime') {
} else if ($name === 'storage_mtime') {
if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
$params[] = $value;
$queryParts[] = '`mtime`';
$params['mtime'] = $value;
}
} elseif ($name === 'encrypted') {
} else if ($name === 'encrypted') {
if (isset($data['encryptedVersion'])) {
$value = $data['encryptedVersion'];
} else {
@ -390,11 +435,13 @@ class Cache implements ICache {
$value = $value ? 1 : 0;
}
}
$params[] = $value;
$queryParts[] = '`' . $name . '`';
$params[$name] = $value;
}
if (array_search($name, $extensionFields) !== false) {
$extensionParams[$name] = $value;
}
}
return array($queryParts, $params);
return [$params, array_filter($extensionParams)];
}
/**
@ -411,15 +458,14 @@ class Cache implements ICache {
// normalize file
$file = $this->normalize($file);
$pathHash = md5($file);
$query = $this->getQueryBuilder();
$query->select('fileid')
->from('filecache')
->whereStorageId()
->wherePath($file);
$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
if ($row = $result->fetch()) {
return (int)$row['fileid'];
} else {
return -1;
}
$id = $query->execute()->fetchColumn();
return $id === false ? -1 : (int)$id;
}
/**
@ -464,39 +510,64 @@ class Cache implements ICache {
*/
public function remove($file) {
$entry = $this->get($file);
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
$this->connection->executeQuery($sql, array($entry['fileid']));
if ($entry['mimetype'] === 'httpd/unix-directory') {
$this->removeChildren($entry);
if ($entry) {
$query = $this->getQueryBuilder();
$query->delete('filecache')
->whereFileId($entry->getId());
$query->execute();
$query = $this->getQueryBuilder();
$query->delete('filecache_extended')
->whereFileId($entry->getId());
$query->execute();
if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
$this->removeChildren($entry);
}
}
}
/**
* Get all sub folders of a folder
*
* @param array $entry the cache entry of the folder to get the subfolders for
* @return array[] the cache entries for the subfolders
* @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
* @return ICacheEntry[] the cache entries for the subfolders
*/
private function getSubFolders($entry) {
$children = $this->getFolderContentsById($entry['fileid']);
private function getSubFolders(ICacheEntry $entry) {
$children = $this->getFolderContentsById($entry->getId());
return array_filter($children, function ($child) {
return $child['mimetype'] === 'httpd/unix-directory';
return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
});
}
/**
* Recursively remove all children of a folder
*
* @param array $entry the cache entry of the folder to remove the children of
* @param ICacheEntry $entry the cache entry of the folder to remove the children of
* @throws \OC\DatabaseException
*/
private function removeChildren($entry) {
$subFolders = $this->getSubFolders($entry);
foreach ($subFolders as $folder) {
private function removeChildren(ICacheEntry $entry) {
$children = $this->getFolderContentsById($entry->getId());
$childIds = array_map(function(ICacheEntry $cacheEntry) {
return $cacheEntry->getId();
}, $children);
$childFolders = array_filter($children, function ($child) {
return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
});
foreach ($childFolders as $folder) {
$this->removeChildren($folder);
}
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
$this->connection->executeQuery($sql, array($entry['fileid']));
$query = $this->getQueryBuilder();
$query->delete('filecache')
->whereParent($entry->getId());
$query->execute();
$query = $this->getQueryBuilder();
$query->delete('filecache_extended')
->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
$query->execute();
}
/**
@ -575,8 +646,16 @@ class Cache implements ICache {
}
}
$sql = 'UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` = ? WHERE `fileid` = ?';
$this->connection->executeQuery($sql, array($targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId));
$query = $this->getQueryBuilder();
$query->update('filecache')
->set('storage', $query->createNamedParameter($targetStorageId))
->set('path', $query->createNamedParameter($targetPath))
->set('path_hash', $query->createNamedParameter(md5($targetPath)))
->set('name', $query->createNamedParameter(basename($targetPath)))
->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
->whereFileId($sourceId);
$query->execute();
$this->connection->commit();
} else {
$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
@ -587,11 +666,15 @@ class Cache implements ICache {
* remove all entries for files that are stored on the storage from the cache
*/
public function clear() {
$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
$query = $this->getQueryBuilder();
$query->delete('filecache')
->whereStorageId();
$query->execute();
$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
$this->connection->executeQuery($sql, array($this->storageId));
$query = $this->connection->getQueryBuilder();
$query->delete('storages')
->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
$query->execute();
}
/**
@ -610,11 +693,14 @@ class Cache implements ICache {
// normalize file
$file = $this->normalize($file);
$pathHash = md5($file);
$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
if ($row = $result->fetch()) {
if ((int)$row['size'] === -1) {
$query = $this->getQueryBuilder();
$query->select('size')
->from('filecache')
->whereStorageId()
->wherePath($file);
$size = $query->execute()->fetchColumn();
if ($size !== false) {
if ((int)$size === -1) {
return self::SHALLOW;
} else {
return self::COMPLETE;
@ -642,18 +728,14 @@ class Cache implements ICache {
return [];
}
$query = $this->getQueryBuilder();
$query->selectFileCache()
->whereStorageId()
->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
$sql = '
SELECT `fileid`, `storage`, `path`, `parent`, `name`,
`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
`encrypted`, `etag`, `permissions`, `checksum`
FROM `*PREFIX*filecache`
WHERE `storage` = ? AND `name` ILIKE ?';
$result = $this->connection->executeQuery($sql,
[$this->getNumericStorageId(), $pattern]
);
return $this->searchResultToCacheEntries($result);
return array_map(function (array $data) {
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}, $query->execute()->fetchAll());
}
/**
@ -676,26 +758,29 @@ class Cache implements ICache {
* @return ICacheEntry[] an array of cache entries where the mimetype matches the search
*/
public function searchByMime($mimetype) {
if (strpos($mimetype, '/')) {
$where = '`mimetype` = ?';
} else {
$where = '`mimepart` = ?';
}
$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
$mimetype = $this->mimetypeLoader->getId($mimetype);
$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
$mimeId = $this->mimetypeLoader->getId($mimetype);
return $this->searchResultToCacheEntries($result);
$query = $this->getQueryBuilder();
$query->selectFileCache()
->whereStorageId();
if (strpos($mimetype, '/')) {
$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
} else {
$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
}
return array_map(function (array $data) {
return self::cacheEntryFromData($data, $this->mimetypeLoader);
}, $query->execute()->fetchAll());
}
public function searchQuery(ISearchQuery $searchQuery) {
$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$builder = $this->getQueryBuilder();
$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
->from('filecache', 'file');
$query = $builder->selectFileCache('file');
$query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
$query->whereStorageId();
if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
$query
@ -755,10 +840,13 @@ class Cache implements ICache {
*/
public function getIncompleteChildrenCount($fileId) {
if ($fileId > -1) {
$sql = 'SELECT count(*)
FROM `*PREFIX*filecache` WHERE `parent` = ? AND size = -1';
$result = $this->connection->executeQuery($sql, [$fileId]);
return (int)$result->fetchColumn();
$query = $this->getQueryBuilder();
$query->select($query->func()->count())
->from('filecache')
->whereParent($fileId)
->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
return (int)$query->execute()->fetchColumn();
}
return -1;
}
@ -775,14 +863,17 @@ class Cache implements ICache {
if (is_null($entry) or !isset($entry['fileid'])) {
$entry = $this->get($path);
}
if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
$id = $entry['fileid'];
$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
'FROM `*PREFIX*filecache` ' .
'WHERE `parent` = ? AND `storage` = ?';
$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
if ($row = $result->fetch()) {
$result->closeCursor();
$query = $this->getQueryBuilder();
$query->selectAlias($query->func()->sum('size'), 'f1')
->selectAlias($query->func()->min('size'), 'f2')
->from('filecache')
->whereStorageId()
->whereParent($id);
if ($row = $query->execute()->fetch()) {
list($sum, $min) = array_values($row);
$sum = 0 + $sum;
$min = 0 + $min;
@ -791,15 +882,9 @@ class Cache implements ICache {
} else {
$totalSize = $sum;
}
$update = array();
if ($entry['size'] !== $totalSize) {
$update['size'] = $totalSize;
$this->update($id, ['size' => $totalSize]);
}
if (count($update) > 0) {
$this->update($id, $update);
}
} else {
$result->closeCursor();
}
}
return $totalSize;
@ -811,13 +896,14 @@ class Cache implements ICache {
* @return int[]
*/
public function getAll() {
$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
$ids = array();
while ($row = $result->fetch()) {
$ids[] = $row['fileid'];
}
return $ids;
$query = $this->getQueryBuilder();
$query->select('fileid')
->from('filecache')
->whereStorageId();
return array_map(function ($id) {
return (int)$id;
}, $query->execute()->fetchAll(\PDO::FETCH_COLUMN));
}
/**
@ -830,14 +916,14 @@ class Cache implements ICache {
* @return string|bool the path of the folder or false when no folder matched
*/
public function getIncomplete() {
$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
$query->execute([$this->getNumericStorageId()]);
if ($row = $query->fetch()) {
return $row['path'];
} else {
return false;
}
$query = $this->getQueryBuilder();
$query->select('path')
->from('filecache')
->whereStorageId()
->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->orderBy('fileid', 'DESC');
return $query->execute()->fetchColumn();
}
/**
@ -847,17 +933,14 @@ class Cache implements ICache {
* @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
*/
public function getPathById($id) {
$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
if ($row = $result->fetch()) {
// Oracle stores empty strings as null...
if ($row['path'] === null) {
return '';
}
return $row['path'];
} else {
return null;
}
$query = $this->getQueryBuilder();
$query->select('path')
->from('filecache')
->whereStorageId()
->whereFileId($id);
$path = $query->execute()->fetchColumn();
return $path === false ? null : $path;
}
/**
@ -866,14 +949,15 @@ class Cache implements ICache {
* instead does a global search in the cache table
*
* @param int $id
* @deprecated use getPathById() instead
* @return array first element holding the storage id, second the path
* @deprecated use getPathById() instead
*/
static public function getById($id) {
$connection = \OC::$server->getDatabaseConnection();
$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
$result = $connection->executeQuery($sql, array($id));
if ($row = $result->fetch()) {
$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
$query->select('path', 'storage')
->from('filecache')
->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
if ($row = $query->execute()->fetch()) {
$numericId = $row['storage'];
$path = $row['path'];
} else {
@ -881,7 +965,7 @@ class Cache implements ICache {
}
if ($id = Storage::getStorageId($numericId)) {
return array($id, $path);
return [$id, $path];
} else {
return null;
}

View file

@ -109,6 +109,18 @@ class CacheEntry implements ICacheEntry, \ArrayAccess {
return isset($this->data['encrypted']) && $this->data['encrypted'];
}
public function getMetadataEtag(): ?string {
return $this->data['metadata_etag'];
}
public function getCreationTime(): ?int {
return $this->data['creation_time'];
}
public function getUploadTime(): ?int {
return $this->data['upload_time'];
}
public function getData() {
return $this->data;
}

View file

@ -0,0 +1,92 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Robin Appelman <robin@icewind.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Files\Cache;
use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\ILogger;
/**
* Query builder with commonly used helpers for filecache queries
*/
class CacheQueryBuilder extends QueryBuilder {
private $cache;
private $alias = null;
public function __construct(IDBConnection $connection, SystemConfig $systemConfig, ILogger $logger, Cache $cache) {
parent::__construct($connection, $systemConfig, $logger);
$this->cache = $cache;
}
public function selectFileCache(string $alias = null) {
$name = $alias ? $alias : 'filecache';
$this->select("$name.fileid", 'storage', 'path', 'path_hash', "$name.parent", 'name', 'mimetype', 'mimepart', 'size', 'mtime',
'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum', 'metadata_etag', 'creation_time', 'upload_time')
->from('filecache', $name)
->leftJoin($name, 'filecache_extended', 'fe', $this->expr()->eq("$name.fileid", 'fe.fileid'));
$this->alias = $name;
return $this;
}
public function whereStorageId() {
$this->andWhere($this->expr()->eq('storage', $this->createNamedParameter($this->cache->getNumericStorageId(), IQueryBuilder::PARAM_INT)));
return $this;
}
public function whereFileId(int $fileId) {
$alias = $this->alias;
if ($alias) {
$alias .= '.';
} else {
$alias = '';
}
$this->andWhere($this->expr()->eq("{$alias}fileid", $this->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
return $this;
}
public function wherePath(string $path) {
$this->andWhere($this->expr()->eq('path_hash', $this->createNamedParameter(md5($path))));
return $this;
}
public function whereParent(int $parent) {
$alias = $this->alias;
if ($alias) {
$alias .= '.';
} else {
$alias = '';
}
$this->andWhere($this->expr()->eq("{$alias}parent", $this->createNamedParameter($parent, IQueryBuilder::PARAM_INT)));
return $this;
}
}

View file

@ -82,7 +82,10 @@ trait MoveFromCacheTrait {
'mimepart' => $entry->getMimePart(),
'etag' => $entry->getEtag(),
'permissions' => $entry->getPermissions(),
'encrypted' => $entry->isEncrypted()
'encrypted' => $entry->isEncrypted(),
'creation_time' => $entry->getCreationTime(),
'upload_time' => $entry->getUploadTime(),
'metadata_etag' => $entry->getMetadataEtag(),
];
}
}

View file

@ -406,4 +406,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
public function getExtension(): string {
return pathinfo($this->getName(), PATHINFO_EXTENSION);
}
public function getCreationTime(): int {
return (int) $this->data['creation_time'];
}
public function getUploadTime(): int {
return (int) $this->data['upload_time'];
}
}

View file

@ -480,4 +480,18 @@ class LazyRoot implements IRootFolder {
public function getRecent($limit, $offset = 0) {
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @inheritDoc
*/
public function getCreationTime(): int {
return $this->__call(__FUNCTION__, func_get_args());
}
/**
* @inheritDoc
*/
public function getUploadTime(): int {
return $this->__call(__FUNCTION__, func_get_args());
}
}

View file

@ -444,4 +444,12 @@ class Node implements \OCP\Files\Node {
}
}
public function getCreationTime(): int {
return $this->getFileInfo()->getCreationTime();
}
public function getUploadTime(): int {
return $this->getFileInfo()->getUploadTime();
}
}

View file

@ -104,7 +104,7 @@ interface IFunctionBuilder {
* @return IQueryFunction
* @since 14.0.0
*/
public function count($count, $alias = '');
public function count($count = '', $alias = '');
/**
* Takes the maximum of all rows in a column

View file

@ -180,7 +180,7 @@ interface IQueryBuilder {
* @param mixed $value The parameter value.
* @param string|null|int $type One of the IQueryBuilder::PARAM_* constants.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setParameter($key, $value, $type = null);
@ -202,7 +202,7 @@ interface IQueryBuilder {
* @param array $params The query parameters to set.
* @param array $types The query parameters types to set.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setParameters(array $params, array $types = array());
@ -248,7 +248,7 @@ interface IQueryBuilder {
*
* @param integer $firstResult The first result to return.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setFirstResult($firstResult);
@ -267,7 +267,7 @@ interface IQueryBuilder {
*
* @param integer $maxResults The maximum number of results to retrieve.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setMaxResults($maxResults);
@ -294,7 +294,7 @@ interface IQueryBuilder {
*
* @param mixed ...$selects The selection expressions.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function select(...$selects);
@ -312,7 +312,7 @@ interface IQueryBuilder {
* @param mixed $select The selection expressions.
* @param string $alias The column alias used in the constructed query.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.1
*/
public function selectAlias($select, $alias);
@ -328,7 +328,7 @@ interface IQueryBuilder {
*
* @param mixed $select The selection expressions.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 9.0.0
*/
public function selectDistinct($select);
@ -346,7 +346,7 @@ interface IQueryBuilder {
*
* @param mixed ...$select The selection expression.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function addSelect(...$select);
@ -365,7 +365,7 @@ interface IQueryBuilder {
* @param string $delete The table whose rows are subject to the deletion.
* @param string $alias The table alias used in the constructed query.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function delete($delete = null, $alias = null);
@ -384,7 +384,7 @@ interface IQueryBuilder {
* @param string $update The table whose rows are subject to the update.
* @param string $alias The table alias used in the constructed query.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function update($update = null, $alias = null);
@ -406,7 +406,7 @@ interface IQueryBuilder {
*
* @param string $insert The table into which the rows should be inserted.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function insert($insert = null);
@ -424,7 +424,7 @@ interface IQueryBuilder {
* @param string $from The table.
* @param string|null $alias The alias of the table.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function from($from, $alias = null);
@ -444,7 +444,7 @@ interface IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function join($fromAlias, $join, $alias, $condition = null);
@ -464,7 +464,7 @@ interface IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function innerJoin($fromAlias, $join, $alias, $condition = null);
@ -484,7 +484,7 @@ interface IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function leftJoin($fromAlias, $join, $alias, $condition = null);
@ -504,7 +504,7 @@ interface IQueryBuilder {
* @param string $alias The alias of the join table.
* @param string $condition The condition for the join.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function rightJoin($fromAlias, $join, $alias, $condition = null);
@ -522,7 +522,7 @@ interface IQueryBuilder {
* @param string $key The column to set.
* @param string $value The value, expression, placeholder, etc.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function set($key, $value);
@ -551,7 +551,7 @@ interface IQueryBuilder {
*
* @param mixed $predicates The restriction predicates.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function where(...$predicates);
@ -570,7 +570,7 @@ interface IQueryBuilder {
*
* @param mixed ...$where The query restrictions.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*
* @see where()
* @since 8.2.0
@ -591,7 +591,7 @@ interface IQueryBuilder {
*
* @param mixed ...$where The WHERE statement.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
*
* @see where()
* @since 8.2.0
@ -611,7 +611,7 @@ interface IQueryBuilder {
*
* @param mixed ...$groupBys The grouping expression.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function groupBy(...$groupBys);
@ -629,7 +629,7 @@ interface IQueryBuilder {
*
* @param mixed ...$groupBy The grouping expression.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function addGroupBy(...$groupBy);
@ -651,7 +651,7 @@ interface IQueryBuilder {
* @param string $column The column into which the value should be inserted.
* @param string $value The value that should be inserted into the column.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function setValue($column, $value);
@ -673,7 +673,7 @@ interface IQueryBuilder {
*
* @param array $values The values to specify for the insert query indexed by column names.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function values(array $values);
@ -684,7 +684,7 @@ interface IQueryBuilder {
*
* @param mixed ...$having The restriction over the groups.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function having(...$having);
@ -695,7 +695,7 @@ interface IQueryBuilder {
*
* @param mixed ...$having The restriction to append.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function andHaving(...$having);
@ -706,7 +706,7 @@ interface IQueryBuilder {
*
* @param mixed ...$having The restriction to add.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function orHaving(...$having);
@ -718,7 +718,7 @@ interface IQueryBuilder {
* @param string $sort The ordering expression.
* @param string $order The ordering direction.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function orderBy($sort, $order = null);
@ -729,7 +729,7 @@ interface IQueryBuilder {
* @param string $sort The ordering expression.
* @param string $order The ordering direction.
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function addOrderBy($sort, $order = null);
@ -757,7 +757,7 @@ interface IQueryBuilder {
*
* @param array|null $queryPartNames
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function resetQueryParts($queryPartNames = null);
@ -767,7 +767,7 @@ interface IQueryBuilder {
*
* @param string $queryPartName
*
* @return \OCP\DB\QueryBuilder\IQueryBuilder This QueryBuilder instance.
* @return $this This QueryBuilder instance.
* @since 8.2.0
*/
public function resetQueryPart($queryPartName);

View file

@ -132,4 +132,28 @@ interface ICacheEntry {
* @since 9.0.0
*/
public function isEncrypted();
/**
* Get the metadata etag for the file
*
* @return string | null
* @since 18.0.0
*/
public function getMetadataEtag(): ?string;
/**
* Get the last modified date as unix timestamp
*
* @return int | null
* @since 18.0.0
*/
public function getCreationTime(): ?int;
/**
* Get the last modified date as unix timestamp
*
* @return int | null
* @since 18.0.0
*/
public function getUploadTime(): ?int;
}

View file

@ -268,4 +268,30 @@ interface FileInfo {
* @since 15.0.0
*/
public function getExtension(): string;
/**
* Get the creation date as unix timestamp
*
* If the creation time is not known, 0 will be returned
*
* creation time is not set automatically by the server and is generally only available
* for files uploaded by the sync clients
*
* @return int
* @since 18.0.0
*/
public function getCreationTime(): int;
/**
* Get the upload date as unix timestamp
*
* If the upload time is not known, 0 will be returned
*
* Upload time will be set automatically by the server for files uploaded over DAV
* files created by Nextcloud apps generally do not have an the upload time set
*
* @return int
* @since 18.0.0
*/
public function getUploadTime(): int;
}

View file

@ -707,6 +707,76 @@ class CacheTest extends \Test\TestCase {
}
}
public function testExtended() {
$folderData = ['size' => 100, 'mtime' => 50, 'mimetype' => 'httpd/unix-directory'];
$this->cache->put("", $folderData);
$data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'creation_time' => 20];
$id1 = $this->cache->put("foo1", $data);
$data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'upload_time' => 30];
$this->cache->put("foo2", $data);
$data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain', 'metadata_etag' => 'foo'];
$this->cache->put("foo3", $data);
$data = ['size' => 100, 'mtime' => 50, 'mimetype' => 'text/plain'];
$id4 = $this->cache->put("foo4", $data);
$entry = $this->cache->get($id1);
$this->assertEquals(20, $entry->getCreationTime());
$this->assertEquals(0, $entry->getUploadTime());
$this->assertEquals(null, $entry->getMetadataEtag());
$entries = $this->cache->getFolderContents("");
$this->assertCount(4, $entries);
$this->assertEquals("foo1", $entries[0]->getName());
$this->assertEquals("foo2", $entries[1]->getName());
$this->assertEquals("foo3", $entries[2]->getName());
$this->assertEquals("foo4", $entries[3]->getName());
$this->assertEquals(20, $entries[0]->getCreationTime());
$this->assertEquals(0, $entries[0]->getUploadTime());
$this->assertEquals(null, $entries[0]->getMetadataEtag());
$this->assertEquals(0, $entries[1]->getCreationTime());
$this->assertEquals(30, $entries[1]->getUploadTime());
$this->assertEquals(null, $entries[1]->getMetadataEtag());
$this->assertEquals(0, $entries[2]->getCreationTime());
$this->assertEquals(0, $entries[2]->getUploadTime());
$this->assertEquals('foo', $entries[2]->getMetadataEtag());
$this->assertEquals(0, $entries[3]->getCreationTime());
$this->assertEquals(0, $entries[3]->getUploadTime());
$this->assertEquals(null, $entries[3]->getMetadataEtag());
$this->cache->update($id1, ['upload_time' => 25]);
$entry = $this->cache->get($id1);
$this->assertEquals(20, $entry->getCreationTime());
$this->assertEquals(25, $entry->getUploadTime());
$this->assertEquals(null, $entry->getMetadataEtag());
$this->cache->put("sub", $folderData);
$this->cache->move("foo1", "sub/foo1");
$entries = $this->cache->getFolderContents("sub");
$this->assertCount(1, $entries);
$this->assertEquals(20, $entries[0]->getCreationTime());
$this->assertEquals(25, $entries[0]->getUploadTime());
$this->assertEquals(null, $entries[0]->getMetadataEtag());
$this->cache->update($id4, ['upload_time' => 25]);
$entry = $this->cache->get($id4);
$this->assertEquals(0, $entry->getCreationTime());
$this->assertEquals(25, $entry->getUploadTime());
$this->assertEquals(null, $entry->getMetadataEtag());
$this->cache->remove("sub");
}
protected function tearDown() {
if ($this->cache) {
$this->cache->clear();

View file

@ -25,6 +25,7 @@ use OCP\Share;
use OCP\Util;
use Test\TestMoveableMountPoint;
use Test\HookHelper;
use Test\Traits\UserTrait;
class TemporaryNoTouch extends Temporary {
public function touch($path, $mtime = null) {
@ -60,10 +61,12 @@ class TemporaryNoLocal extends Temporary {
* @package Test\Files
*/
class ViewTest extends \Test\TestCase {
use UserTrait;
/**
* @var \OC\Files\Storage\Storage[] $storages
*/
private $storages = array();
private $storages = [];
/**
* @var string
@ -138,9 +141,9 @@ class ViewTest extends \Test\TestCase {
$storage2 = $this->getTestStorage();
$storage3 = $this->getTestStorage();
$root = self::getUniqueID('/');
Filesystem::mount($storage1, array(), $root . '/');
Filesystem::mount($storage2, array(), $root . '/substorage');
Filesystem::mount($storage3, array(), $root . '/folder/anotherstorage');
Filesystem::mount($storage1, [], $root . '/');
Filesystem::mount($storage2, [], $root . '/substorage');
Filesystem::mount($storage3, [], $root . '/folder/anotherstorage');
$textSize = strlen("dummy file data\n");
$imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo/logo.png');
$storageSize = $textSize * 2 + $imageSize;
@ -204,13 +207,13 @@ class ViewTest extends \Test\TestCase {
$cachedData = $rootView->getFileInfo('/foo.txt');
$this->assertFalse($cachedData['encrypted']);
$id = $rootView->putFileInfo('/foo.txt', array('encrypted' => true));
$id = $rootView->putFileInfo('/foo.txt', ['encrypted' => true]);
$cachedData = $rootView->getFileInfo('/foo.txt');
$this->assertTrue($cachedData['encrypted']);
$this->assertEquals($cachedData['fileid'], $id);
$this->assertFalse($rootView->getFileInfo('/non/existing'));
$this->assertEquals(array(), $rootView->getDirectoryContent('/non/existing'));
$this->assertEquals([], $rootView->getDirectoryContent('/non/existing'));
}
/**
@ -220,9 +223,9 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
$storage3 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage3, array(), '/folder/anotherstorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
Filesystem::mount($storage3, [], '/folder/anotherstorage');
$rootView = new View('');
@ -262,8 +265,8 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage(false);
$storage2 = $this->getTestStorage();
$storage1->mkdir('substorage');
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
$rootView = new View('');
$folderContent = $rootView->getDirectoryContent('/');
@ -292,8 +295,8 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/mount');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/mount');
$view = new View('/');
@ -313,7 +316,7 @@ class ViewTest extends \Test\TestCase {
public function testCacheIncompleteFolder() {
$storage1 = $this->getTestStorage(false);
Filesystem::clearMounts();
Filesystem::mount($storage1, array(), '/incomplete');
Filesystem::mount($storage1, [], '/incomplete');
$rootView = new View('/incomplete');
$entries = $rootView->getDirectoryContent('/');
@ -327,8 +330,8 @@ class ViewTest extends \Test\TestCase {
public function testAutoScan() {
$storage1 = $this->getTestStorage(false);
$storage2 = $this->getTestStorage(false);
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
$textSize = strlen("dummy file data\n");
$rootView = new View('');
@ -349,15 +352,15 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
$storage3 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage3, array(), '/folder/anotherstorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
Filesystem::mount($storage3, [], '/folder/anotherstorage');
$rootView = new View('');
$results = $rootView->search('foo');
$this->assertCount(6, $results);
$paths = array();
$paths = [];
foreach ($results as $result) {
$this->assertEquals($result['path'], Filesystem::normalizePath($result['path']));
$paths[] = $result['path'];
@ -372,7 +375,7 @@ class ViewTest extends \Test\TestCase {
$folderView = new View('/folder');
$results = $folderView->search('bar');
$this->assertCount(2, $results);
$paths = array();
$paths = [];
foreach ($results as $result) {
$paths[] = $result['path'];
}
@ -381,7 +384,7 @@ class ViewTest extends \Test\TestCase {
$results = $folderView->search('foo');
$this->assertCount(2, $results);
$paths = array();
$paths = [];
foreach ($results as $result) {
$paths[] = $result['path'];
}
@ -397,7 +400,7 @@ class ViewTest extends \Test\TestCase {
*/
public function testWatcher() {
$storage1 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage1, [], '/');
$storage1->getWatcher()->setPolicy(Watcher::CHECK_ALWAYS);
$rootView = new View('');
@ -405,7 +408,7 @@ class ViewTest extends \Test\TestCase {
$cachedData = $rootView->getFileInfo('foo.txt');
$this->assertEquals(16, $cachedData['size']);
$rootView->putFileInfo('foo.txt', array('storage_mtime' => 10));
$rootView->putFileInfo('foo.txt', ['storage_mtime' => 10]);
$storage1->file_put_contents('foo.txt', 'foo');
clearstatcache();
@ -441,8 +444,8 @@ class ViewTest extends \Test\TestCase {
}
public function copyBetweenStorages($storage1, $storage2) {
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
$rootView = new View('');
$rootView->mkdir('substorage/emptyfolder');
@ -487,8 +490,8 @@ class ViewTest extends \Test\TestCase {
}
public function moveBetweenStorages($storage1, $storage2) {
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
$rootView = new View('');
$rootView->rename('foo.txt', 'substorage/folder/foo.txt');
@ -506,8 +509,8 @@ class ViewTest extends \Test\TestCase {
public function testUnlink() {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
$rootView = new View('');
$rootView->file_put_contents('/foo.txt', 'asd');
@ -552,8 +555,8 @@ class ViewTest extends \Test\TestCase {
public function testUnlinkRootMustFail() {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], '/substorage');
$rootView = new View('');
$rootView->file_put_contents('/foo.txt', 'asd');
@ -571,7 +574,7 @@ class ViewTest extends \Test\TestCase {
public function testTouch() {
$storage = $this->getTestStorage(true, TemporaryNoTouch::class);
Filesystem::mount($storage, array(), '/');
Filesystem::mount($storage, [], '/');
$rootView = new View('');
$oldCachedData = $rootView->getFileInfo('foo.txt');
@ -582,7 +585,7 @@ class ViewTest extends \Test\TestCase {
$this->assertEquals(500, $cachedData['mtime']);
$this->assertEquals($oldCachedData['storage_mtime'], $cachedData['storage_mtime']);
$rootView->putFileInfo('foo.txt', array('storage_mtime' => 1000)); //make sure the watcher detects the change
$rootView->putFileInfo('foo.txt', ['storage_mtime' => 1000]); //make sure the watcher detects the change
$rootView->file_put_contents('foo.txt', 'asd');
$cachedData = $rootView->getFileInfo('foo.txt');
$this->assertGreaterThanOrEqual($oldCachedData['mtime'], $cachedData['mtime']);
@ -595,7 +598,7 @@ class ViewTest extends \Test\TestCase {
public function testTouchFloat() {
$storage = $this->getTestStorage(true, TemporaryNoTouch::class);
Filesystem::mount($storage, array(), '/');
Filesystem::mount($storage, [], '/');
$rootView = new View('');
$oldCachedData = $rootView->getFileInfo('foo.txt');
@ -613,8 +616,8 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
$defaultRoot = Filesystem::getRoot();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), $defaultRoot . '/substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], $defaultRoot . '/substorage');
\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
$rootView = new View('');
@ -636,7 +639,7 @@ class ViewTest extends \Test\TestCase {
public function testSearchNotOutsideView() {
$storage1 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage1, [], '/');
$storage1->rename('folder', 'foo');
$scanner = $storage1->getScanner();
$scanner->scan('');
@ -656,7 +659,7 @@ class ViewTest extends \Test\TestCase {
/**
* @var \OC\Files\Storage\Storage $storage
*/
$storage = new $class(array());
$storage = new $class([]);
$textData = "dummy file data\n";
$imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
$storage->mkdir('folder');
@ -679,8 +682,8 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
$defaultRoot = Filesystem::getRoot();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), $defaultRoot . '_substorage');
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], $defaultRoot . '_substorage');
\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
$subView = new View($defaultRoot . '_substorage');
@ -710,8 +713,8 @@ class ViewTest extends \Test\TestCase {
$storage1 = $this->getTestStorage();
$storage2 = $this->getTestStorage();
$defaultRoot = Filesystem::getRoot();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage2, array(), $defaultRoot);
Filesystem::mount($storage1, [], '/');
Filesystem::mount($storage2, [], $defaultRoot);
\OC_Hook::connect('OC_Filesystem', 'post_create', $this, 'dummyHookCreate');
\OC_Hook::connect('OC_Filesystem', 'post_update', $this, 'dummyHookUpdate');
\OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHookWrite');
@ -741,7 +744,7 @@ class ViewTest extends \Test\TestCase {
*/
public function testResolvePath($expected, $pathToTest) {
$storage1 = $this->getTestStorage();
Filesystem::mount($storage1, array(), '/');
Filesystem::mount($storage1, [], '/');
$view = new View('');
@ -756,25 +759,25 @@ class ViewTest extends \Test\TestCase {
}
public function resolvePathTestProvider() {
return array(
array('foo.txt', 'foo.txt'),
array('foo.txt', '/foo.txt'),
array('folder', 'folder'),
array('folder', '/folder'),
array('folder', 'folder/'),
array('folder', '/folder/'),
array('folder/bar.txt', 'folder/bar.txt'),
array('folder/bar.txt', '/folder/bar.txt'),
array('', ''),
array('', '/'),
);
return [
['foo.txt', 'foo.txt'],
['foo.txt', '/foo.txt'],
['folder', 'folder'],
['folder', '/folder'],
['folder', 'folder/'],
['folder', '/folder/'],
['folder/bar.txt', 'folder/bar.txt'],
['folder/bar.txt', '/folder/bar.txt'],
['', ''],
['', '/'],
];
}
public function testUTF8Names() {
$names = array('虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا');
$names = ['虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا'];
$storage = new Temporary(array());
Filesystem::mount($storage, array(), '/');
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$rootView = new View('');
foreach ($names as $name) {
@ -802,8 +805,8 @@ class ViewTest extends \Test\TestCase {
public function xtestLongPath() {
$storage = new Temporary(array());
Filesystem::mount($storage, array(), '/');
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$rootView = new View('');
@ -851,9 +854,9 @@ class ViewTest extends \Test\TestCase {
}
public function testTouchNotSupported() {
$storage = new TemporaryNoTouch(array());
$storage = new TemporaryNoTouch([]);
$scanner = $storage->getScanner();
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$past = time() - 100;
$storage->file_put_contents('test', 'foobar');
$scanner->scan('');
@ -868,13 +871,13 @@ class ViewTest extends \Test\TestCase {
}
public function testWatcherEtagCrossStorage() {
$storage1 = new Temporary(array());
$storage2 = new Temporary(array());
$storage1 = new Temporary([]);
$storage2 = new Temporary([]);
$scanner1 = $storage1->getScanner();
$scanner2 = $storage2->getScanner();
$storage1->mkdir('sub');
Filesystem::mount($storage1, array(), '/test/');
Filesystem::mount($storage2, array(), '/test/sub/storage');
Filesystem::mount($storage1, [], '/test/');
Filesystem::mount($storage2, [], '/test/sub/storage');
$past = time() - 100;
$storage2->file_put_contents('test.txt', 'foobar');
@ -887,9 +890,9 @@ class ViewTest extends \Test\TestCase {
$oldFileInfo = $view->getFileInfo('/test/sub/storage/test.txt');
$oldFolderInfo = $view->getFileInfo('/test');
$storage2->getCache()->update($oldFileInfo->getId(), array(
'storage_mtime' => $past
));
$storage2->getCache()->update($oldFileInfo->getId(), [
'storage_mtime' => $past,
]);
$oldEtag = $oldFolderInfo->getEtag();
@ -908,9 +911,9 @@ class ViewTest extends \Test\TestCase {
}
public function testPartFileInfo() {
$storage = new Temporary(array());
$storage = new Temporary([]);
$scanner = $storage->getScanner();
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$storage->file_put_contents('test.part', 'foobar');
$scanner->scan('');
$view = new View('/test');
@ -922,15 +925,15 @@ class ViewTest extends \Test\TestCase {
}
public function absolutePathProvider() {
return array(
array('/files/', ''),
array('/files/0', '0'),
array('/files/false', 'false'),
array('/files/true', 'true'),
array('/files/', '/'),
array('/files/test', 'test'),
array('/files/test', '/test'),
);
return [
['/files/', ''],
['/files/0', '0'],
['/files/false', 'false'],
['/files/true', 'true'],
['/files/', '/'],
['/files/test', 'test'],
['/files/test', '/test'],
];
}
/**
@ -959,81 +962,81 @@ class ViewTest extends \Test\TestCase {
}
public function relativePathProvider($missingRootExpectedPath) {
return array(
return [
// No root - returns the path
array('', '/files', '/files'),
array('', '/files/', '/files/'),
['', '/files', '/files'],
['', '/files/', '/files/'],
// Root equals path - /
array('/files/', '/files/', '/'),
array('/files/', '/files', '/'),
array('/files', '/files/', '/'),
array('/files', '/files', '/'),
['/files/', '/files/', '/'],
['/files/', '/files', '/'],
['/files', '/files/', '/'],
['/files', '/files', '/'],
// False negatives: chroot fixes those by adding the leading slash.
// But setting them up with this root (instead of chroot($root))
// will fail them, although they should be the same.
// TODO init should be fixed, so it also adds the leading slash
array('files/', '/files/', $missingRootExpectedPath),
array('files', '/files/', $missingRootExpectedPath),
array('files/', '/files', $missingRootExpectedPath),
array('files', '/files', $missingRootExpectedPath),
['files/', '/files/', $missingRootExpectedPath],
['files', '/files/', $missingRootExpectedPath],
['files/', '/files', $missingRootExpectedPath],
['files', '/files', $missingRootExpectedPath],
// False negatives: Paths provided to the method should have a leading slash
// TODO input should be checked to have a leading slash
array('/files/', 'files/', null),
array('/files', 'files/', null),
array('/files/', 'files', null),
array('/files', 'files', null),
['/files/', 'files/', null],
['/files', 'files/', null],
['/files/', 'files', null],
['/files', 'files', null],
// with trailing slashes
array('/files/', '/files/0', '0'),
array('/files/', '/files/false', 'false'),
array('/files/', '/files/true', 'true'),
array('/files/', '/files/test', 'test'),
array('/files/', '/files/test/foo', 'test/foo'),
['/files/', '/files/0', '0'],
['/files/', '/files/false', 'false'],
['/files/', '/files/true', 'true'],
['/files/', '/files/test', 'test'],
['/files/', '/files/test/foo', 'test/foo'],
// without trailing slashes
// TODO false expectation: Should match "with trailing slashes"
array('/files', '/files/0', '/0'),
array('/files', '/files/false', '/false'),
array('/files', '/files/true', '/true'),
array('/files', '/files/test', '/test'),
array('/files', '/files/test/foo', '/test/foo'),
['/files', '/files/0', '/0'],
['/files', '/files/false', '/false'],
['/files', '/files/true', '/true'],
['/files', '/files/test', '/test'],
['/files', '/files/test/foo', '/test/foo'],
// leading slashes
array('/files/', '/files_trashbin/', null),
array('/files', '/files_trashbin/', null),
array('/files/', '/files_trashbin', null),
array('/files', '/files_trashbin', null),
['/files/', '/files_trashbin/', null],
['/files', '/files_trashbin/', null],
['/files/', '/files_trashbin', null],
['/files', '/files_trashbin', null],
// no leading slashes
array('files/', 'files_trashbin/', null),
array('files', 'files_trashbin/', null),
array('files/', 'files_trashbin', null),
array('files', 'files_trashbin', null),
['files/', 'files_trashbin/', null],
['files', 'files_trashbin/', null],
['files/', 'files_trashbin', null],
['files', 'files_trashbin', null],
// mixed leading slashes
array('files/', '/files_trashbin/', null),
array('/files/', 'files_trashbin/', null),
array('files', '/files_trashbin/', null),
array('/files', 'files_trashbin/', null),
array('files/', '/files_trashbin', null),
array('/files/', 'files_trashbin', null),
array('files', '/files_trashbin', null),
array('/files', 'files_trashbin', null),
['files/', '/files_trashbin/', null],
['/files/', 'files_trashbin/', null],
['files', '/files_trashbin/', null],
['/files', 'files_trashbin/', null],
['files/', '/files_trashbin', null],
['/files/', 'files_trashbin', null],
['files', '/files_trashbin', null],
['/files', 'files_trashbin', null],
array('files', 'files_trashbin/test', null),
array('/files', '/files_trashbin/test', null),
array('/files', 'files_trashbin/test', null),
);
['files', 'files_trashbin/test', null],
['/files', '/files_trashbin/test', null],
['/files', 'files_trashbin/test', null],
];
}
public function testFileView() {
$storage = new Temporary(array());
$storage = new Temporary([]);
$scanner = $storage->getScanner();
$storage->file_put_contents('foo.txt', 'bar');
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$scanner->scan('');
$view = new View('/test/foo.txt');
@ -1059,9 +1062,9 @@ class ViewTest extends \Test\TestCase {
$longPath .= '/' . $folderName;
}
$storage = new Temporary(array());
$storage = new Temporary([]);
$this->tempStorage = $storage; // for later hard cleanup
Filesystem::mount($storage, array(), '/');
Filesystem::mount($storage, [], '/');
$rootView = new View('');
@ -1074,64 +1077,64 @@ class ViewTest extends \Test\TestCase {
$longPath = 'md5';
}
call_user_func(array($rootView, $operation), $longPath, $param0);
call_user_func([$rootView, $operation], $longPath, $param0);
}
public function tooLongPathDataProvider() {
return array(
array('getAbsolutePath'),
array('getRelativePath'),
array('getMountPoint'),
array('resolvePath'),
array('getLocalFile'),
array('getLocalFolder'),
array('mkdir'),
array('rmdir'),
array('opendir'),
array('is_dir'),
array('is_file'),
array('stat'),
array('filetype'),
array('filesize'),
array('readfile'),
array('isCreatable'),
array('isReadable'),
array('isUpdatable'),
array('isDeletable'),
array('isSharable'),
array('file_exists'),
array('filemtime'),
array('touch'),
array('file_get_contents'),
array('unlink'),
array('deleteAll'),
array('toTmpFile'),
array('getMimeType'),
array('free_space'),
array('getFileInfo'),
array('getDirectoryContent'),
array('getOwner'),
array('getETag'),
array('file_put_contents', 'ipsum'),
array('rename', '@0'),
array('copy', '@0'),
array('fopen', 'r'),
array('fromTmpFile', '@0'),
array('hash'),
array('hasUpdated', 0),
array('putFileInfo', array()),
);
return [
['getAbsolutePath'],
['getRelativePath'],
['getMountPoint'],
['resolvePath'],
['getLocalFile'],
['getLocalFolder'],
['mkdir'],
['rmdir'],
['opendir'],
['is_dir'],
['is_file'],
['stat'],
['filetype'],
['filesize'],
['readfile'],
['isCreatable'],
['isReadable'],
['isUpdatable'],
['isDeletable'],
['isSharable'],
['file_exists'],
['filemtime'],
['touch'],
['file_get_contents'],
['unlink'],
['deleteAll'],
['toTmpFile'],
['getMimeType'],
['free_space'],
['getFileInfo'],
['getDirectoryContent'],
['getOwner'],
['getETag'],
['file_put_contents', 'ipsum'],
['rename', '@0'],
['copy', '@0'],
['fopen', 'r'],
['fromTmpFile', '@0'],
['hash'],
['hasUpdated', 0],
['putFileInfo', []],
];
}
public function testRenameCrossStoragePreserveMtime() {
$storage1 = new Temporary(array());
$storage2 = new Temporary(array());
$storage1 = new Temporary([]);
$storage2 = new Temporary([]);
$storage1->mkdir('sub');
$storage1->mkdir('foo');
$storage1->file_put_contents('foo.txt', 'asd');
$storage1->file_put_contents('foo/bar.txt', 'asd');
Filesystem::mount($storage1, array(), '/test/');
Filesystem::mount($storage2, array(), '/test/sub/storage');
Filesystem::mount($storage1, [], '/test/');
Filesystem::mount($storage2, [], '/test/sub/storage');
$view = new View('');
$time = time() - 200;
@ -1157,7 +1160,7 @@ class ViewTest extends \Test\TestCase {
}
private function doTestCopyRenameFail($operation) {
$storage1 = new Temporary(array());
$storage1 = new Temporary([]);
/** @var \PHPUnit_Framework_MockObject_MockObject|Temporary $storage2 */
$storage2 = $this->getMockBuilder(TemporaryNoCross::class)
->setConstructorArgs([[]])
@ -1180,8 +1183,8 @@ class ViewTest extends \Test\TestCase {
$storage2->file_put_contents('existing.txt', '0123');
$storage1->getScanner()->scan('');
$storage2->getScanner()->scan('');
Filesystem::mount($storage1, array(), '/test/');
Filesystem::mount($storage2, array(), '/test/sub/storage');
Filesystem::mount($storage1, [], '/test/');
Filesystem::mount($storage2, [], '/test/sub/storage');
// move file
$view = new View('');
@ -1212,8 +1215,8 @@ class ViewTest extends \Test\TestCase {
public function testDeleteFailKeepCache() {
/** @var Temporary|\PHPUnit_Framework_MockObject_MockObject $storage */
$storage = $this->getMockBuilder(Temporary::class)
->setConstructorArgs(array(array()))
->setMethods(array('unlink'))
->setConstructorArgs([[]])
->setMethods(['unlink'])
->getMock();
$storage->expects($this->once())
->method('unlink')
@ -1222,7 +1225,7 @@ class ViewTest extends \Test\TestCase {
$cache = $storage->getCache();
$storage->file_put_contents('foo.txt', 'asd');
$scanner->scan('');
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$view = new View('/test');
@ -1248,14 +1251,14 @@ class ViewTest extends \Test\TestCase {
}
public function testRenameOverWrite() {
$storage = new Temporary(array());
$storage = new Temporary([]);
$scanner = $storage->getScanner();
$storage->mkdir('sub');
$storage->mkdir('foo');
$storage->file_put_contents('foo.txt', 'asd');
$storage->file_put_contents('foo/bar.txt', 'asd');
$scanner->scan('');
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$view = new View('');
$this->assertTrue($view->rename('/test/foo.txt', '/test/foo/bar.txt'));
}
@ -1309,7 +1312,7 @@ class ViewTest extends \Test\TestCase {
$pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
$view = new View($rootPath);
$storage = new Temporary(array());
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
$view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED);
@ -1328,7 +1331,7 @@ class ViewTest extends \Test\TestCase {
$pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
$view = new View($rootPath);
$storage = new Temporary(array());
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED));
@ -1349,7 +1352,7 @@ class ViewTest extends \Test\TestCase {
$pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
$view = new View($rootPath);
$storage = new Temporary(array());
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
$view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
@ -1368,7 +1371,7 @@ class ViewTest extends \Test\TestCase {
$pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
$view = new View($rootPath);
$storage = new Temporary(array());
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
$this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
@ -1516,7 +1519,7 @@ class ViewTest extends \Test\TestCase {
public function testChangeLock() {
$view = new View('/testuser/files/');
$storage = new Temporary(array());
$storage = new Temporary([]);
Filesystem::mount($storage, [], '/');
$view->lockFile('/test/sub', ILockingProvider::LOCK_SHARED);
@ -1544,7 +1547,7 @@ class ViewTest extends \Test\TestCase {
['/foo', '/files/foo', true],
['/foo', 'filesfoo', false],
['', '/foo/files', true],
['', '/foo/files/bar.txt', true]
['', '/foo/files/bar.txt', true],
];
}
@ -1828,7 +1831,7 @@ class ViewTest extends \Test\TestCase {
->setMethods([$operation])
->getMock();
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
// work directly on disk because mkdir might be mocked
$realPath = $storage->getSourcePath('');
@ -1852,7 +1855,7 @@ class ViewTest extends \Test\TestCase {
$this->connectMockHooks($hookType, $view, $lockedPath, $lockTypePre, $lockTypePost);
// do operation
call_user_func_array(array($view, $operation), $operationArgs);
call_user_func_array([$view, $operation], $operationArgs);
if ($hookType !== null) {
$this->assertEquals($expectedLockBefore, $lockTypePre, 'File locked properly during pre-hook');
@ -1878,7 +1881,7 @@ class ViewTest extends \Test\TestCase {
->setMethods(['fopen'])
->getMock();
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
$storage->mkdir('files');
$storage->expects($this->once())
@ -1917,7 +1920,7 @@ class ViewTest extends \Test\TestCase {
->setMethods(['fopen'])
->getMock();
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
$storage->mkdir('files');
$storage->expects($this->once())
@ -1972,7 +1975,7 @@ class ViewTest extends \Test\TestCase {
->setMethods([$operation])
->getMock();
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
// work directly on disk because mkdir might be mocked
$realPath = $storage->getSourcePath('');
@ -1991,7 +1994,7 @@ class ViewTest extends \Test\TestCase {
$thrown = false;
try {
call_user_func_array(array($view, $operation), $operationArgs);
call_user_func_array([$view, $operation], $operationArgs);
} catch (\Exception $e) {
$thrown = true;
$this->assertEquals('Simulated exception', $e->getMessage());
@ -2005,7 +2008,7 @@ class ViewTest extends \Test\TestCase {
$storage = new Temporary([]);
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
$storage->mkdir('files');
$storage->mkdir('files/dir');
@ -2054,7 +2057,7 @@ class ViewTest extends \Test\TestCase {
->setMethods([$operation])
->getMock();
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
$storage->mkdir('files');
Util::connectHook(
@ -2064,7 +2067,7 @@ class ViewTest extends \Test\TestCase {
'cancellingCallback'
);
call_user_func_array(array($view, $operation), $operationArgs);
call_user_func_array([$view, $operation], $operationArgs);
$this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
}
@ -2100,7 +2103,7 @@ class ViewTest extends \Test\TestCase {
$sourcePath = 'original.txt';
$targetPath = 'target.txt';
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
$storage->mkdir('files');
$view->file_put_contents($sourcePath, 'meh');
@ -2152,7 +2155,7 @@ class ViewTest extends \Test\TestCase {
$sourcePath = 'original.txt';
$targetPath = 'target.txt';
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage, [], $this->user . '/');
$storage->mkdir('files');
$view->file_put_contents($sourcePath, 'meh');
@ -2279,8 +2282,8 @@ class ViewTest extends \Test\TestCase {
$sourcePath = 'original.txt';
$targetPath = 'substorage/target.txt';
Filesystem::mount($storage, array(), $this->user . '/');
Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
Filesystem::mount($storage, [], $this->user . '/');
Filesystem::mount($storage2, [], $this->user . '/files/substorage');
$storage->mkdir('files');
$view->file_put_contents($sourcePath, 'meh');
@ -2513,7 +2516,7 @@ class ViewTest extends \Test\TestCase {
public function testGetDirectoryContentMimeFilter($filter, $expected) {
$storage1 = new Temporary();
$root = self::getUniqueID('/');
Filesystem::mount($storage1, array(), $root . '/');
Filesystem::mount($storage1, [], $root . '/');
$view = new View($root);
$view->file_put_contents('test1.txt', 'asd');
@ -2532,10 +2535,10 @@ class ViewTest extends \Test\TestCase {
}
public function testFilePutContentsClearsChecksum() {
$storage = new Temporary(array());
$storage = new Temporary([]);
$scanner = $storage->getScanner();
$storage->file_put_contents('foo.txt', 'bar');
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$scanner->scan('');
$view = new View('/test/foo.txt');
@ -2552,11 +2555,11 @@ class ViewTest extends \Test\TestCase {
}
public function testDeleteGhostFile() {
$storage = new Temporary(array());
$storage = new Temporary([]);
$scanner = $storage->getScanner();
$cache = $storage->getCache();
$storage->file_put_contents('foo.txt', 'bar');
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$scanner->scan('');
$storage->unlink('foo.txt');
@ -2575,12 +2578,12 @@ class ViewTest extends \Test\TestCase {
}
public function testDeleteGhostFolder() {
$storage = new Temporary(array());
$storage = new Temporary([]);
$scanner = $storage->getScanner();
$cache = $storage->getCache();
$storage->mkdir('foo');
$storage->file_put_contents('foo/foo.txt', 'bar');
Filesystem::mount($storage, array(), '/test/');
Filesystem::mount($storage, [], '/test/');
$scanner->scan('');
$storage->rmdir('foo');
@ -2669,4 +2672,24 @@ class ViewTest extends \Test\TestCase {
->willReturn(true);
$this->assertFalse(self::invokePrivate($view, 'createParentDirectories', ['/file.txt/folder/structure']));
}
public function testCacheExtension() {
$storage = new Temporary([]);
$scanner = $storage->getScanner();
$storage->file_put_contents('foo.txt', 'bar');
$scanner->scan('');
Filesystem::mount($storage, [], '/test/');
$view = new View('/test');
$info = $view->getFileInfo('/foo.txt');
$this->assertEquals(0, $info->getUploadTime());
$this->assertEquals(0, $info->getCreationTime());
$view->putFileInfo('/foo.txt', ['upload_time' => 25]);
$info = $view->getFileInfo('/foo.txt');
$this->assertEquals(25, $info->getUploadTime());
$this->assertEquals(0, $info->getCreationTime());
}
}

View file

@ -26,6 +26,7 @@ class HelperStorageTest extends \Test\TestCase {
parent::setUp();
$this->user = $this->getUniqueID('user_');
\OC_User::useBackend('dummy');
\OC::$server->getUserManager()->createUser($this->user, $this->user);
$this->storage = \OC\Files\Filesystem::getStorage('/');