cache.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. namespace OC\Files\Cache;
  9. /**
  10. * Metadata cache for the filesystem
  11. *
  12. * don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead
  13. */
  14. class Cache {
  15. const NOT_FOUND = 0;
  16. const PARTIAL = 1; //only partial data available, file not cached in the database
  17. const SHALLOW = 2; //folder in cache, but not all child files are completely scanned
  18. const COMPLETE = 3;
  19. /**
  20. * @var array partial data for the cache
  21. */
  22. protected $partial = array();
  23. /**
  24. * @var string
  25. */
  26. protected $storageId;
  27. /**
  28. * @var Storage $storageCache
  29. */
  30. protected $storageCache;
  31. protected static $mimetypeIds = array();
  32. protected static $mimetypes = array();
  33. /**
  34. * @param \OC\Files\Storage\Storage|string $storage
  35. */
  36. public function __construct($storage) {
  37. if ($storage instanceof \OC\Files\Storage\Storage) {
  38. $this->storageId = $storage->getId();
  39. } else {
  40. $this->storageId = $storage;
  41. }
  42. if (strlen($this->storageId) > 64) {
  43. $this->storageId = md5($this->storageId);
  44. }
  45. $this->storageCache = new Storage($storage);
  46. }
  47. public function getNumericStorageId() {
  48. return $this->storageCache->getNumericId();
  49. }
  50. /**
  51. * normalize mimetypes
  52. *
  53. * @param string $mime
  54. * @return int
  55. */
  56. public function getMimetypeId($mime) {
  57. if (empty($mime)) {
  58. // Can not insert empty string into Oracle NOT NULL column.
  59. $mime = 'application/octet-stream';
  60. }
  61. if (empty(self::$mimetypeIds)) {
  62. $this->loadMimetypes();
  63. }
  64. if (!isset(self::$mimetypeIds[$mime])) {
  65. try{
  66. $result = \OC_DB::executeAudited('INSERT INTO `*PREFIX*mimetypes`(`mimetype`) VALUES(?)', array($mime));
  67. self::$mimetypeIds[$mime] = \OC_DB::insertid('*PREFIX*mimetypes');
  68. self::$mimetypes[self::$mimetypeIds[$mime]] = $mime;
  69. }
  70. catch (\Doctrine\DBAL\DBALException $e){
  71. \OC_Log::write('core', 'Exception during mimetype insertion: ' . $e->getmessage(), \OC_Log::DEBUG);
  72. return -1;
  73. }
  74. }
  75. return self::$mimetypeIds[$mime];
  76. }
  77. public function getMimetype($id) {
  78. if (empty(self::$mimetypes)) {
  79. $this->loadMimetypes();
  80. }
  81. return isset(self::$mimetypes[$id]) ? self::$mimetypes[$id] : null;
  82. }
  83. public function loadMimetypes(){
  84. $result = \OC_DB::executeAudited('SELECT `id`, `mimetype` FROM `*PREFIX*mimetypes`', array());
  85. if ($result) {
  86. while ($row = $result->fetchRow()) {
  87. self::$mimetypeIds[$row['mimetype']] = $row['id'];
  88. self::$mimetypes[$row['id']] = $row['mimetype'];
  89. }
  90. }
  91. }
  92. /**
  93. * get the stored metadata of a file or folder
  94. *
  95. * @param string/int $file
  96. * @return array|false
  97. */
  98. public function get($file) {
  99. if (is_string($file) or $file == '') {
  100. // normalize file
  101. $file = $this->normalize($file);
  102. $where = 'WHERE `storage` = ? AND `path_hash` = ?';
  103. $params = array($this->getNumericStorageId(), md5($file));
  104. } else { //file id
  105. $where = 'WHERE `fileid` = ?';
  106. $params = array($file);
  107. }
  108. $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
  109. `storage_mtime`, `encrypted`, `unencrypted_size`, `etag`, `permissions`
  110. FROM `*PREFIX*filecache` ' . $where;
  111. $result = \OC_DB::executeAudited($sql, $params);
  112. $data = $result->fetchRow();
  113. //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
  114. //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
  115. if ($data === null) {
  116. $data = false;
  117. }
  118. //merge partial data
  119. if (!$data and is_string($file)) {
  120. if (isset($this->partial[$file])) {
  121. $data = $this->partial[$file];
  122. }
  123. } else {
  124. //fix types
  125. $data['fileid'] = (int)$data['fileid'];
  126. $data['size'] = 0 + $data['size'];
  127. $data['mtime'] = (int)$data['mtime'];
  128. $data['storage_mtime'] = (int)$data['storage_mtime'];
  129. $data['encrypted'] = (bool)$data['encrypted'];
  130. $data['unencrypted_size'] = 0 + $data['unencrypted_size'];
  131. $data['storage'] = $this->storageId;
  132. $data['mimetype'] = $this->getMimetype($data['mimetype']);
  133. $data['mimepart'] = $this->getMimetype($data['mimepart']);
  134. if ($data['storage_mtime'] == 0) {
  135. $data['storage_mtime'] = $data['mtime'];
  136. }
  137. $data['permissions'] = (int)$data['permissions'];
  138. }
  139. return $data;
  140. }
  141. /**
  142. * get the metadata of all files stored in $folder
  143. *
  144. * @param string $folder
  145. * @return array
  146. */
  147. public function getFolderContents($folder) {
  148. $fileId = $this->getId($folder);
  149. return $this->getFolderContentsById($fileId);
  150. }
  151. /**
  152. * get the metadata of all files stored in $folder
  153. *
  154. * @param int $fileId the file id of the folder
  155. * @return array
  156. */
  157. public function getFolderContentsById($fileId) {
  158. if ($fileId > -1) {
  159. $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
  160. `storage_mtime`, `encrypted`, `unencrypted_size`, `etag`, `permissions`
  161. FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
  162. $result = \OC_DB::executeAudited($sql,array($fileId));
  163. $files = $result->fetchAll();
  164. foreach ($files as &$file) {
  165. $file['mimetype'] = $this->getMimetype($file['mimetype']);
  166. $file['mimepart'] = $this->getMimetype($file['mimepart']);
  167. if ($file['storage_mtime'] == 0) {
  168. $file['storage_mtime'] = $file['mtime'];
  169. }
  170. if ($file['encrypted'] or ($file['unencrypted_size'] > 0 and $file['mimetype'] === 'httpd/unix-directory')) {
  171. $file['encrypted_size'] = $file['size'];
  172. $file['size'] = $file['unencrypted_size'];
  173. }
  174. $file['permissions'] = (int)$file['permissions'];
  175. }
  176. return $files;
  177. } else {
  178. return array();
  179. }
  180. }
  181. /**
  182. * store meta data for a file or folder
  183. *
  184. * @param string $file
  185. * @param array $data
  186. *
  187. * @return int file id
  188. */
  189. public function put($file, array $data) {
  190. if (($id = $this->getId($file)) > -1) {
  191. $this->update($id, $data);
  192. return $id;
  193. } else {
  194. // normalize file
  195. $file = $this->normalize($file);
  196. if (isset($this->partial[$file])) { //add any saved partial data
  197. $data = array_merge($this->partial[$file], $data);
  198. unset($this->partial[$file]);
  199. }
  200. $requiredFields = array('size', 'mtime', 'mimetype');
  201. foreach ($requiredFields as $field) {
  202. if (!isset($data[$field])) { //data not complete save as partial and return
  203. $this->partial[$file] = $data;
  204. return -1;
  205. }
  206. }
  207. $data['path'] = $file;
  208. $data['parent'] = $this->getParentId($file);
  209. $data['name'] = \OC_Util::basename($file);
  210. list($queryParts, $params) = $this->buildParts($data);
  211. $queryParts[] = '`storage`';
  212. $params[] = $this->getNumericStorageId();
  213. $valuesPlaceholder = array_fill(0, count($queryParts), '?');
  214. $sql = 'INSERT INTO `*PREFIX*filecache` (' . implode(', ', $queryParts) . ')'
  215. . ' VALUES (' . implode(', ', $valuesPlaceholder) . ')';
  216. \OC_DB::executeAudited($sql, $params);
  217. return (int)\OC_DB::insertid('*PREFIX*filecache');
  218. }
  219. }
  220. /**
  221. * update the metadata in the cache
  222. *
  223. * @param int $id
  224. * @param array $data
  225. */
  226. public function update($id, array $data) {
  227. if(isset($data['path'])) {
  228. // normalize path
  229. $data['path'] = $this->normalize($data['path']);
  230. }
  231. if(isset($data['name'])) {
  232. // normalize path
  233. $data['name'] = $this->normalize($data['name']);
  234. }
  235. list($queryParts, $params) = $this->buildParts($data);
  236. $params[] = $id;
  237. $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE `fileid` = ?';
  238. \OC_DB::executeAudited($sql, $params);
  239. }
  240. /**
  241. * extract query parts and params array from data array
  242. *
  243. * @param array $data
  244. * @return array
  245. */
  246. function buildParts(array $data) {
  247. $fields = array(
  248. 'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', 'unencrypted_size',
  249. 'etag', 'permissions');
  250. $params = array();
  251. $queryParts = array();
  252. foreach ($data as $name => $value) {
  253. if (array_search($name, $fields) !== false) {
  254. if ($name === 'path') {
  255. $params[] = md5($value);
  256. $queryParts[] = '`path_hash`';
  257. } elseif ($name === 'mimetype') {
  258. $params[] = $this->getMimetypeId(substr($value, 0, strpos($value, '/')));
  259. $queryParts[] = '`mimepart`';
  260. $value = $this->getMimetypeId($value);
  261. } elseif ($name === 'storage_mtime') {
  262. if (!isset($data['mtime'])) {
  263. $params[] = $value;
  264. $queryParts[] = '`mtime`';
  265. }
  266. } elseif ($name === 'encrypted') {
  267. // Boolean to integer conversion
  268. $value = $value ? 1 : 0;
  269. }
  270. $params[] = $value;
  271. $queryParts[] = '`' . $name . '`';
  272. }
  273. }
  274. return array($queryParts, $params);
  275. }
  276. /**
  277. * get the file id for a file
  278. *
  279. * @param string $file
  280. * @return int
  281. */
  282. public function getId($file) {
  283. // normalize file
  284. $file = $this->normalize($file);
  285. $pathHash = md5($file);
  286. $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
  287. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $pathHash));
  288. if ($row = $result->fetchRow()) {
  289. return $row['fileid'];
  290. } else {
  291. return -1;
  292. }
  293. }
  294. /**
  295. * get the id of the parent folder of a file
  296. *
  297. * @param string $file
  298. * @return int
  299. */
  300. public function getParentId($file) {
  301. if ($file === '') {
  302. return -1;
  303. } else {
  304. $parent = dirname($file);
  305. if ($parent === '.') {
  306. $parent = '';
  307. }
  308. return $this->getId($parent);
  309. }
  310. }
  311. /**
  312. * check if a file is available in the cache
  313. *
  314. * @param string $file
  315. * @return bool
  316. */
  317. public function inCache($file) {
  318. return $this->getId($file) != -1;
  319. }
  320. /**
  321. * remove a file or folder from the cache
  322. *
  323. * @param string $file
  324. */
  325. public function remove($file) {
  326. $entry = $this->get($file);
  327. if ($entry['mimetype'] === 'httpd/unix-directory') {
  328. $children = $this->getFolderContents($file);
  329. foreach ($children as $child) {
  330. $this->remove($child['path']);
  331. }
  332. }
  333. $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  334. \OC_DB::executeAudited($sql, array($entry['fileid']));
  335. }
  336. /**
  337. * Move a file or folder in the cache
  338. *
  339. * @param string $source
  340. * @param string $target
  341. */
  342. public function move($source, $target) {
  343. // normalize source and target
  344. $source = $this->normalize($source);
  345. $target = $this->normalize($target);
  346. $sourceData = $this->get($source);
  347. $sourceId = $sourceData['fileid'];
  348. $newParentId = $this->getParentId($target);
  349. if ($sourceData['mimetype'] === 'httpd/unix-directory') {
  350. //find all child entries
  351. $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
  352. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $source . '/%'));
  353. $childEntries = $result->fetchAll();
  354. $sourceLength = strlen($source);
  355. $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
  356. foreach ($childEntries as $child) {
  357. $targetPath = $target . substr($child['path'], $sourceLength);
  358. \OC_DB::executeAudited($query, array($targetPath, md5($targetPath), $child['fileid']));
  359. }
  360. }
  361. $sql = 'UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
  362. \OC_DB::executeAudited($sql, array($target, md5($target), basename($target), $newParentId, $sourceId));
  363. }
  364. /**
  365. * remove all entries for files that are stored on the storage from the cache
  366. */
  367. public function clear() {
  368. $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
  369. \OC_DB::executeAudited($sql, array($this->getNumericStorageId()));
  370. $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
  371. \OC_DB::executeAudited($sql, array($this->storageId));
  372. }
  373. /**
  374. * @param string $file
  375. *
  376. * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
  377. */
  378. public function getStatus($file) {
  379. // normalize file
  380. $file = $this->normalize($file);
  381. $pathHash = md5($file);
  382. $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
  383. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $pathHash));
  384. if ($row = $result->fetchRow()) {
  385. if ((int)$row['size'] === -1) {
  386. return self::SHALLOW;
  387. } else {
  388. return self::COMPLETE;
  389. }
  390. } else {
  391. if (isset($this->partial[$file])) {
  392. return self::PARTIAL;
  393. } else {
  394. return self::NOT_FOUND;
  395. }
  396. }
  397. }
  398. /**
  399. * search for files matching $pattern
  400. *
  401. * @param string $pattern
  402. * @return array an array of file data
  403. */
  404. public function search($pattern) {
  405. // normalize pattern
  406. $pattern = $this->normalize($pattern);
  407. $sql = '
  408. SELECT `fileid`, `storage`, `path`, `parent`, `name`,
  409. `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`,
  410. `unencrypted_size`, `etag`, `permissions`
  411. FROM `*PREFIX*filecache`
  412. WHERE `storage` = ? AND ';
  413. $dbtype = \OC_Config::getValue( 'dbtype', 'sqlite' );
  414. if($dbtype === 'oci') {
  415. //remove starting and ending % from the pattern
  416. $pattern = '^'.str_replace('%', '.*', $pattern).'$';
  417. $sql .= 'REGEXP_LIKE(`name`, ?, \'i\')';
  418. } else if($dbtype === 'pgsql') {
  419. $sql .= '`name` ILIKE ?';
  420. } else if ($dbtype === 'mysql') {
  421. $sql .= '`name` COLLATE utf8_general_ci LIKE ?';
  422. } else {
  423. $sql .= '`name` LIKE ?';
  424. }
  425. $result = \OC_DB::executeAudited($sql,
  426. array($this->getNumericStorageId(), $pattern)
  427. );
  428. $files = array();
  429. while ($row = $result->fetchRow()) {
  430. $row['mimetype'] = $this->getMimetype($row['mimetype']);
  431. $row['mimepart'] = $this->getMimetype($row['mimepart']);
  432. $files[] = $row;
  433. }
  434. return $files;
  435. }
  436. /**
  437. * search for files by mimetype
  438. *
  439. * @param string $mimetype
  440. * @return array
  441. */
  442. public function searchByMime($mimetype) {
  443. if (strpos($mimetype, '/')) {
  444. $where = '`mimetype` = ?';
  445. } else {
  446. $where = '`mimepart` = ?';
  447. }
  448. $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag`, `permissions`
  449. FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
  450. $mimetype = $this->getMimetypeId($mimetype);
  451. $result = \OC_DB::executeAudited($sql, array($mimetype, $this->getNumericStorageId()));
  452. $files = array();
  453. while ($row = $result->fetchRow()) {
  454. $row['mimetype'] = $this->getMimetype($row['mimetype']);
  455. $row['mimepart'] = $this->getMimetype($row['mimepart']);
  456. $files[] = $row;
  457. }
  458. return $files;
  459. }
  460. /**
  461. * update the folder size and the size of all parent folders
  462. *
  463. * @param string|boolean $path
  464. * @param array $data (optional) meta data of the folder
  465. */
  466. public function correctFolderSize($path, $data = null) {
  467. $this->calculateFolderSize($path, $data);
  468. if ($path !== '') {
  469. $parent = dirname($path);
  470. if ($parent === '.' or $parent === '/') {
  471. $parent = '';
  472. }
  473. $this->correctFolderSize($parent);
  474. }
  475. }
  476. /**
  477. * get the size of a folder and set it in the cache
  478. *
  479. * @param string $path
  480. * @param array $entry (optional) meta data of the folder
  481. * @return int
  482. */
  483. public function calculateFolderSize($path, $entry = null) {
  484. $totalSize = 0;
  485. if (is_null($entry) or !isset($entry['fileid'])) {
  486. $entry = $this->get($path);
  487. }
  488. if ($entry && $entry['mimetype'] === 'httpd/unix-directory') {
  489. $id = $entry['fileid'];
  490. $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2, ' .
  491. 'SUM(`unencrypted_size`) AS f3 ' .
  492. 'FROM `*PREFIX*filecache` ' .
  493. 'WHERE `parent` = ? AND `storage` = ?';
  494. $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId()));
  495. if ($row = $result->fetchRow()) {
  496. list($sum, $min, $unencryptedSum) = array_values($row);
  497. $sum = 0 + $sum;
  498. $min = 0 + $min;
  499. $unencryptedSum = 0 + $unencryptedSum;
  500. if ($min === -1) {
  501. $totalSize = $min;
  502. } else {
  503. $totalSize = $sum;
  504. }
  505. $update = array();
  506. if ($entry['size'] !== $totalSize) {
  507. $update['size'] = $totalSize;
  508. }
  509. if (!isset($entry['unencrypted_size']) or $entry['unencrypted_size'] !== $unencryptedSum) {
  510. $update['unencrypted_size'] = $unencryptedSum;
  511. }
  512. if (count($update) > 0) {
  513. $this->update($id, $update);
  514. }
  515. if ($totalSize !== -1 and $unencryptedSum > 0) {
  516. $totalSize = $unencryptedSum;
  517. }
  518. }
  519. }
  520. return $totalSize;
  521. }
  522. /**
  523. * get all file ids on the files on the storage
  524. *
  525. * @return int[]
  526. */
  527. public function getAll() {
  528. $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
  529. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId()));
  530. $ids = array();
  531. while ($row = $result->fetchRow()) {
  532. $ids[] = $row['fileid'];
  533. }
  534. return $ids;
  535. }
  536. /**
  537. * find a folder in the cache which has not been fully scanned
  538. *
  539. * If multiply incomplete folders are in the cache, the one with the highest id will be returned,
  540. * use the one with the highest id gives the best result with the background scanner, since that is most
  541. * likely the folder where we stopped scanning previously
  542. *
  543. * @return string|bool the path of the folder or false when no folder matched
  544. */
  545. public function getIncomplete() {
  546. $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache`'
  547. . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC',1);
  548. $result = \OC_DB::executeAudited($query, array($this->getNumericStorageId()));
  549. if ($row = $result->fetchRow()) {
  550. return $row['path'];
  551. } else {
  552. return false;
  553. }
  554. }
  555. /**
  556. * get the path of a file on this storage by it's id
  557. *
  558. * @param int $id
  559. * @return string|null
  560. */
  561. public function getPathById($id) {
  562. $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
  563. $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId()));
  564. if ($row = $result->fetchRow()) {
  565. // Oracle stores empty strings as null...
  566. if ($row['path'] === null) {
  567. return '';
  568. }
  569. return $row['path'];
  570. } else {
  571. return null;
  572. }
  573. }
  574. /**
  575. * get the storage id of the storage for a file and the internal path of the file
  576. * unlike getPathById this does not limit the search to files on this storage and
  577. * instead does a global search in the cache table
  578. *
  579. * @param int $id
  580. * @return array, first element holding the storage id, second the path
  581. */
  582. static public function getById($id) {
  583. $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  584. $result = \OC_DB::executeAudited($sql, array($id));
  585. if ($row = $result->fetchRow()) {
  586. $numericId = $row['storage'];
  587. $path = $row['path'];
  588. } else {
  589. return null;
  590. }
  591. if ($id = Storage::getStorageId($numericId)) {
  592. return array($id, $path);
  593. } else {
  594. return null;
  595. }
  596. }
  597. /**
  598. * normalize the given path
  599. * @param string $path
  600. * @return string
  601. */
  602. public function normalize($path) {
  603. return \OC_Util::normalizeUnicode($path);
  604. }
  605. }