cache.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  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. private $partial = array();
  23. /**
  24. * @var string
  25. */
  26. private $storageId;
  27. /**
  28. * @var Storage $storageCache
  29. */
  30. private $storageCache;
  31. private static $mimetypeIds = array();
  32. private 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`
  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'] = (int)$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'] = (int)$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. }
  138. return $data;
  139. }
  140. /**
  141. * get the metadata of all files stored in $folder
  142. *
  143. * @param string $folder
  144. * @return array
  145. */
  146. public function getFolderContents($folder) {
  147. $fileId = $this->getId($folder);
  148. if ($fileId > -1) {
  149. $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
  150. `storage_mtime`, `encrypted`, `unencrypted_size`, `etag`
  151. FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
  152. $result = \OC_DB::executeAudited($sql,array($fileId));
  153. $files = $result->fetchAll();
  154. foreach ($files as &$file) {
  155. $file['mimetype'] = $this->getMimetype($file['mimetype']);
  156. $file['mimepart'] = $this->getMimetype($file['mimepart']);
  157. if ($file['storage_mtime'] == 0) {
  158. $file['storage_mtime'] = $file['mtime'];
  159. }
  160. }
  161. return $files;
  162. } else {
  163. return array();
  164. }
  165. }
  166. /**
  167. * store meta data for a file or folder
  168. *
  169. * @param string $file
  170. * @param array $data
  171. *
  172. * @return int file id
  173. */
  174. public function put($file, array $data) {
  175. if (($id = $this->getId($file)) > -1) {
  176. $this->update($id, $data);
  177. return $id;
  178. } else {
  179. // normalize file
  180. $file = $this->normalize($file);
  181. if (isset($this->partial[$file])) { //add any saved partial data
  182. $data = array_merge($this->partial[$file], $data);
  183. unset($this->partial[$file]);
  184. }
  185. $requiredFields = array('size', 'mtime', 'mimetype');
  186. foreach ($requiredFields as $field) {
  187. if (!isset($data[$field])) { //data not complete save as partial and return
  188. $this->partial[$file] = $data;
  189. return -1;
  190. }
  191. }
  192. $data['path'] = $file;
  193. $data['parent'] = $this->getParentId($file);
  194. $data['name'] = \OC_Util::basename($file);
  195. list($queryParts, $params) = $this->buildParts($data);
  196. $queryParts[] = '`storage`';
  197. $params[] = $this->getNumericStorageId();
  198. $valuesPlaceholder = array_fill(0, count($queryParts), '?');
  199. $sql = 'INSERT INTO `*PREFIX*filecache` (' . implode(', ', $queryParts) . ')'
  200. . ' VALUES (' . implode(', ', $valuesPlaceholder) . ')';
  201. \OC_DB::executeAudited($sql, $params);
  202. return (int)\OC_DB::insertid('*PREFIX*filecache');
  203. }
  204. }
  205. /**
  206. * update the metadata in the cache
  207. *
  208. * @param int $id
  209. * @param array $data
  210. */
  211. public function update($id, array $data) {
  212. if(isset($data['path'])) {
  213. // normalize path
  214. $data['path'] = $this->normalize($data['path']);
  215. }
  216. if(isset($data['name'])) {
  217. // normalize path
  218. $data['name'] = $this->normalize($data['name']);
  219. }
  220. list($queryParts, $params) = $this->buildParts($data);
  221. $params[] = $id;
  222. $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? WHERE `fileid` = ?';
  223. \OC_DB::executeAudited($sql, $params);
  224. }
  225. /**
  226. * extract query parts and params array from data array
  227. *
  228. * @param array $data
  229. * @return array
  230. */
  231. function buildParts(array $data) {
  232. $fields = array('path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted', 'unencrypted_size', 'etag');
  233. $params = array();
  234. $queryParts = array();
  235. foreach ($data as $name => $value) {
  236. if (array_search($name, $fields) !== false) {
  237. if ($name === 'path') {
  238. $params[] = md5($value);
  239. $queryParts[] = '`path_hash`';
  240. } elseif ($name === 'mimetype') {
  241. $params[] = $this->getMimetypeId(substr($value, 0, strpos($value, '/')));
  242. $queryParts[] = '`mimepart`';
  243. $value = $this->getMimetypeId($value);
  244. } elseif ($name === 'storage_mtime') {
  245. if (!isset($data['mtime'])) {
  246. $params[] = $value;
  247. $queryParts[] = '`mtime`';
  248. }
  249. } elseif ($name === 'encrypted') {
  250. // Boolean to integer conversion
  251. $value = $value ? 1 : 0;
  252. }
  253. $params[] = $value;
  254. $queryParts[] = '`' . $name . '`';
  255. }
  256. }
  257. return array($queryParts, $params);
  258. }
  259. /**
  260. * get the file id for a file
  261. *
  262. * @param string $file
  263. * @return int
  264. */
  265. public function getId($file) {
  266. // normalize file
  267. $file = $this->normalize($file);
  268. $pathHash = md5($file);
  269. $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
  270. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $pathHash));
  271. if ($row = $result->fetchRow()) {
  272. return $row['fileid'];
  273. } else {
  274. return -1;
  275. }
  276. }
  277. /**
  278. * get the id of the parent folder of a file
  279. *
  280. * @param string $file
  281. * @return int
  282. */
  283. public function getParentId($file) {
  284. if ($file === '') {
  285. return -1;
  286. } else {
  287. $parent = dirname($file);
  288. if ($parent === '.') {
  289. $parent = '';
  290. }
  291. return $this->getId($parent);
  292. }
  293. }
  294. /**
  295. * check if a file is available in the cache
  296. *
  297. * @param string $file
  298. * @return bool
  299. */
  300. public function inCache($file) {
  301. return $this->getId($file) != -1;
  302. }
  303. /**
  304. * remove a file or folder from the cache
  305. *
  306. * @param string $file
  307. */
  308. public function remove($file) {
  309. $entry = $this->get($file);
  310. if ($entry['mimetype'] === 'httpd/unix-directory') {
  311. $children = $this->getFolderContents($file);
  312. foreach ($children as $child) {
  313. $this->remove($child['path']);
  314. }
  315. }
  316. $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  317. \OC_DB::executeAudited($sql, array($entry['fileid']));
  318. $permissionsCache = new Permissions($this->storageId);
  319. $permissionsCache->remove($entry['fileid']);
  320. }
  321. /**
  322. * Move a file or folder in the cache
  323. *
  324. * @param string $source
  325. * @param string $target
  326. */
  327. public function move($source, $target) {
  328. // normalize source and target
  329. $source = $this->normalize($source);
  330. $target = $this->normalize($target);
  331. $sourceData = $this->get($source);
  332. $sourceId = $sourceData['fileid'];
  333. $newParentId = $this->getParentId($target);
  334. if ($sourceData['mimetype'] === 'httpd/unix-directory') {
  335. //find all child entries
  336. $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
  337. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $source . '/%'));
  338. $childEntries = $result->fetchAll();
  339. $sourceLength = strlen($source);
  340. $query = \OC_DB::prepare('UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
  341. foreach ($childEntries as $child) {
  342. $targetPath = $target . substr($child['path'], $sourceLength);
  343. \OC_DB::executeAudited($query, array($targetPath, md5($targetPath), $child['fileid']));
  344. }
  345. }
  346. $sql = 'UPDATE `*PREFIX*filecache` SET `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
  347. \OC_DB::executeAudited($sql, array($target, md5($target), basename($target), $newParentId, $sourceId));
  348. }
  349. /**
  350. * remove all entries for files that are stored on the storage from the cache
  351. */
  352. public function clear() {
  353. $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
  354. \OC_DB::executeAudited($sql, array($this->getNumericStorageId()));
  355. $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
  356. \OC_DB::executeAudited($sql, array($this->storageId));
  357. }
  358. /**
  359. * @param string $file
  360. *
  361. * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
  362. */
  363. public function getStatus($file) {
  364. // normalize file
  365. $file = $this->normalize($file);
  366. $pathHash = md5($file);
  367. $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
  368. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId(), $pathHash));
  369. if ($row = $result->fetchRow()) {
  370. if ((int)$row['size'] === -1) {
  371. return self::SHALLOW;
  372. } else {
  373. return self::COMPLETE;
  374. }
  375. } else {
  376. if (isset($this->partial[$file])) {
  377. return self::PARTIAL;
  378. } else {
  379. return self::NOT_FOUND;
  380. }
  381. }
  382. }
  383. /**
  384. * search for files matching $pattern
  385. *
  386. * @param string $pattern
  387. * @return array of file data
  388. */
  389. public function search($pattern) {
  390. // normalize pattern
  391. $pattern = $this->normalize($pattern);
  392. $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag`
  393. FROM `*PREFIX*filecache` WHERE `name` LIKE ? AND `storage` = ?';
  394. $result = \OC_DB::executeAudited($sql, array($pattern, $this->getNumericStorageId()));
  395. $files = array();
  396. while ($row = $result->fetchRow()) {
  397. $row['mimetype'] = $this->getMimetype($row['mimetype']);
  398. $row['mimepart'] = $this->getMimetype($row['mimepart']);
  399. $files[] = $row;
  400. }
  401. return $files;
  402. }
  403. /**
  404. * search for files by mimetype
  405. *
  406. * @param string $mimetype
  407. * @return array
  408. */
  409. public function searchByMime($mimetype) {
  410. if (strpos($mimetype, '/')) {
  411. $where = '`mimetype` = ?';
  412. } else {
  413. $where = '`mimepart` = ?';
  414. }
  415. $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`, `encrypted`, `unencrypted_size`, `etag`
  416. FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
  417. $mimetype = $this->getMimetypeId($mimetype);
  418. $result = \OC_DB::executeAudited($sql, array($mimetype, $this->getNumericStorageId()));
  419. $files = array();
  420. while ($row = $result->fetchRow()) {
  421. $row['mimetype'] = $this->getMimetype($row['mimetype']);
  422. $row['mimepart'] = $this->getMimetype($row['mimepart']);
  423. $files[] = $row;
  424. }
  425. return $files;
  426. }
  427. /**
  428. * update the folder size and the size of all parent folders
  429. *
  430. * @param $path
  431. */
  432. public function correctFolderSize($path) {
  433. $this->calculateFolderSize($path);
  434. if ($path !== '') {
  435. $parent = dirname($path);
  436. if ($parent === '.' or $parent === '/') {
  437. $parent = '';
  438. }
  439. $this->correctFolderSize($parent);
  440. }
  441. }
  442. /**
  443. * get the size of a folder and set it in the cache
  444. *
  445. * @param string $path
  446. * @return int
  447. */
  448. public function calculateFolderSize($path) {
  449. $totalSize = 0;
  450. $entry = $this->get($path);
  451. if ($entry && $entry['mimetype'] === 'httpd/unix-directory') {
  452. $id = $entry['fileid'];
  453. $sql = 'SELECT SUM(`size`), MIN(`size`) FROM `*PREFIX*filecache` '.
  454. 'WHERE `parent` = ? AND `storage` = ?';
  455. $result = \OC_DB::executeAudited($sql, array($id, $this->getNumericStorageId()));
  456. if ($row = $result->fetchRow()) {
  457. list($sum, $min) = array_values($row);
  458. $sum = (int)$sum;
  459. $min = (int)$min;
  460. if ($min === -1) {
  461. $totalSize = $min;
  462. } else {
  463. $totalSize = $sum;
  464. }
  465. if ($entry['size'] !== $totalSize) {
  466. $this->update($id, array('size' => $totalSize));
  467. }
  468. }
  469. }
  470. return $totalSize;
  471. }
  472. /**
  473. * get all file ids on the files on the storage
  474. *
  475. * @return int[]
  476. */
  477. public function getAll() {
  478. $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
  479. $result = \OC_DB::executeAudited($sql, array($this->getNumericStorageId()));
  480. $ids = array();
  481. while ($row = $result->fetchRow()) {
  482. $ids[] = $row['fileid'];
  483. }
  484. return $ids;
  485. }
  486. /**
  487. * find a folder in the cache which has not been fully scanned
  488. *
  489. * If multiply incomplete folders are in the cache, the one with the highest id will be returned,
  490. * use the one with the highest id gives the best result with the background scanner, since that is most
  491. * likely the folder where we stopped scanning previously
  492. *
  493. * @return string|bool the path of the folder or false when no folder matched
  494. */
  495. public function getIncomplete() {
  496. $query = \OC_DB::prepare('SELECT `path` FROM `*PREFIX*filecache`'
  497. . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC',1);
  498. $result = \OC_DB::executeAudited($query, array($this->getNumericStorageId()));
  499. if ($row = $result->fetchRow()) {
  500. return $row['path'];
  501. } else {
  502. return false;
  503. }
  504. }
  505. /**
  506. * get the storage id of the storage for a file and the internal path of the file
  507. *
  508. * @param int $id
  509. * @return array, first element holding the storage id, second the path
  510. */
  511. static public function getById($id) {
  512. $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  513. $result = \OC_DB::executeAudited($sql, array($id));
  514. if ($row = $result->fetchRow()) {
  515. $numericId = $row['storage'];
  516. $path = $row['path'];
  517. } else {
  518. return null;
  519. }
  520. if ($id = Storage::getStorageId($numericId)) {
  521. return array($id, $path);
  522. } else {
  523. return null;
  524. }
  525. }
  526. /**
  527. * normalize the given path
  528. * @param $path
  529. * @return string
  530. */
  531. public function normalize($path) {
  532. return \OC_Util::normalizeUnicode($path);
  533. }
  534. }