cache.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <?php
  2. /**
  3. * @author Bart Visscher <bartv@thisnet.nl>
  4. * @author Björn Schießle <schiessle@owncloud.com>
  5. * @author Christopher Schäpers <kondou@ts.unde.re>
  6. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  7. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <icewind@owncloud.com>
  10. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  11. * @author Scrutinizer Auto-Fixer <auto-fixer@scrutinizer-ci.com>
  12. * @author Thomas Müller <thomas.mueller@tmit.eu>
  13. * @author Vincent Petry <pvince81@owncloud.com>
  14. *
  15. * @copyright Copyright (c) 2015, ownCloud, Inc.
  16. * @license AGPL-3.0
  17. *
  18. * This code is free software: you can redistribute it and/or modify
  19. * it under the terms of the GNU Affero General Public License, version 3,
  20. * as published by the Free Software Foundation.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU Affero General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU Affero General Public License, version 3,
  28. * along with this program. If not, see <http://www.gnu.org/licenses/>
  29. *
  30. */
  31. namespace OC\Files\Cache;
  32. use OCP\Share_Backend_Collection;
  33. /**
  34. * Metadata cache for shared files
  35. *
  36. * don't use this class directly if you need to get metadata, use \OC\Files\Filesystem::getFileInfo instead
  37. */
  38. class Shared_Cache extends Cache {
  39. private $storage;
  40. private $files = array();
  41. /**
  42. * @param \OC\Files\Storage\Shared $storage
  43. */
  44. public function __construct($storage) {
  45. $this->storage = $storage;
  46. }
  47. /**
  48. * Get the source cache of a shared file or folder
  49. *
  50. * @param string $target Shared target file path
  51. * @return \OC\Files\Cache\Cache|false
  52. */
  53. private function getSourceCache($target) {
  54. if ($target === false || $target === $this->storage->getMountPoint()) {
  55. $target = '';
  56. }
  57. $source = \OC_Share_Backend_File::getSource($target, $this->storage->getShare());
  58. if (isset($source['path']) && isset($source['fileOwner'])) {
  59. \OC\Files\Filesystem::initMountPoints($source['fileOwner']);
  60. $mounts = \OC\Files\Filesystem::getMountByNumericId($source['storage']);
  61. if (is_array($mounts) and !empty($mounts)) {
  62. $fullPath = $mounts[0]->getMountPoint() . $source['path'];
  63. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($fullPath);
  64. if ($storage) {
  65. $this->files[$target] = $internalPath;
  66. $cache = $storage->getCache();
  67. $this->storageId = $storage->getId();
  68. $this->numericId = $cache->getNumericStorageId();
  69. return $cache;
  70. }
  71. }
  72. }
  73. return false;
  74. }
  75. public function getNumericStorageId() {
  76. if (isset($this->numericId)) {
  77. return $this->numericId;
  78. } else {
  79. return false;
  80. }
  81. }
  82. /**
  83. * get the stored metadata of a file or folder
  84. *
  85. * @param string|int $file
  86. * @return array|false
  87. */
  88. public function get($file) {
  89. if (is_string($file)) {
  90. $cache = $this->getSourceCache($file);
  91. if ($cache) {
  92. $data = $cache->get($this->files[$file]);
  93. if ($data) {
  94. $data['displayname_owner'] = \OC_User::getDisplayName($this->storage->getSharedFrom());
  95. $data['path'] = $file;
  96. if ($file === '') {
  97. $data['is_share_mount_point'] = true;
  98. }
  99. $data['uid_owner'] = $this->storage->getOwner($file);
  100. if (isset($data['permissions'])) {
  101. $data['permissions'] &= $this->storage->getPermissions($file);
  102. } else {
  103. $data['permissions'] = $this->storage->getPermissions($file);
  104. }
  105. }
  106. return $data;
  107. }
  108. } else {
  109. $sourceId = $file;
  110. // if we are at the root of the mount point we want to return the
  111. // cache information for the source item
  112. if (!is_int($sourceId) || $sourceId === 0) {
  113. $sourceId = $this->storage->getSourceId();
  114. }
  115. $query = \OCP\DB::prepare(
  116. 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`,'
  117. . ' `size`, `mtime`, `encrypted`, `storage_mtime`, `etag`, `permissions`'
  118. . ' FROM `*PREFIX*filecache` WHERE `fileid` = ?');
  119. $result = $query->execute(array($sourceId));
  120. $data = $result->fetchRow();
  121. $data['fileid'] = (int)$data['fileid'];
  122. $data['mtime'] = (int)$data['mtime'];
  123. $data['storage_mtime'] = (int)$data['storage_mtime'];
  124. $data['encrypted'] = (bool)$data['encrypted'];
  125. $data['mimetype'] = $this->getMimetype($data['mimetype']);
  126. $data['mimepart'] = $this->getMimetype($data['mimepart']);
  127. if ($data['storage_mtime'] === 0) {
  128. $data['storage_mtime'] = $data['mtime'];
  129. }
  130. $data['size'] = (int)$data['size'];
  131. $data['permissions'] = (int)$data['permissions'];
  132. if (!is_int($file) || $file === 0) {
  133. $data['path'] = '';
  134. $data['name'] = basename($this->storage->getMountPoint());
  135. $data['is_share_mount_point'] = true;
  136. }
  137. $data['permissions'] &= $this->storage->getPermissions('');
  138. return $data;
  139. }
  140. return false;
  141. }
  142. /**
  143. * get the metadata of all files stored in $folder
  144. *
  145. * @param string $folderId
  146. * @return array|false
  147. */
  148. public function getFolderContentsById($folderId) {
  149. $cache = $this->getSourceCache('');
  150. if ($cache) {
  151. $owner = $this->storage->getSharedFrom();
  152. $parentPath = $this->getPathById($folderId);
  153. if ($parentPath !== '') {
  154. $parentPath .= '/';
  155. }
  156. $sourceFolderContent = $cache->getFolderContentsById($folderId);
  157. foreach ($sourceFolderContent as &$c) {
  158. $c['path'] = ltrim($parentPath . $c['name'], '/');
  159. $c['uid_owner'] = $owner;
  160. $c['displayname_owner'] = \OC_User::getDisplayName($owner);
  161. $c['permissions'] = $c['permissions'] & $this->storage->getPermissions(false);
  162. }
  163. return $sourceFolderContent;
  164. }
  165. return false;
  166. }
  167. /**
  168. * store meta data for a file or folder
  169. *
  170. * @param string $file
  171. * @param array $data
  172. *
  173. * @return int|false file id
  174. */
  175. public function put($file, array $data) {
  176. $file = ($file === false) ? '' : $file;
  177. if ($cache = $this->getSourceCache($file)) {
  178. return $cache->put($this->files[$file], $data);
  179. }
  180. return false;
  181. }
  182. /**
  183. * get the file id for a file
  184. *
  185. * @param string $file
  186. * @return int
  187. */
  188. public function getId($file) {
  189. if ($file === false) {
  190. return $this->storage->getSourceId();
  191. }
  192. $cache = $this->getSourceCache($file);
  193. if ($cache) {
  194. return $cache->getId($this->files[$file]);
  195. }
  196. return -1;
  197. }
  198. /**
  199. * check if a file is available in the cache
  200. *
  201. * @param string $file
  202. * @return bool
  203. */
  204. public function inCache($file) {
  205. if ($file == '') {
  206. return true;
  207. }
  208. return parent::inCache($file);
  209. }
  210. /**
  211. * remove a file or folder from the cache
  212. *
  213. * @param string $file
  214. */
  215. public function remove($file) {
  216. $file = ($file === false) ? '' : $file;
  217. if ($cache = $this->getSourceCache($file)) {
  218. $cache->remove($this->files[$file]);
  219. }
  220. }
  221. /**
  222. * Get the storage id and path needed for a move
  223. *
  224. * @param string $path
  225. * @return array [$storageId, $internalPath]
  226. */
  227. protected function getMoveInfo($path) {
  228. $cache = $this->getSourceCache($path);
  229. $file = \OC_Share_Backend_File::getSource($path, $this->storage->getShare());
  230. return [$cache->getNumericStorageId(), $file['path']];
  231. }
  232. /**
  233. * remove all entries for files that are stored on the storage from the cache
  234. */
  235. public function clear() {
  236. // Not a valid action for Shared Cache
  237. }
  238. /**
  239. * @param string $file
  240. *
  241. * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
  242. */
  243. public function getStatus($file) {
  244. if ($file == '') {
  245. return self::COMPLETE;
  246. }
  247. if ($cache = $this->getSourceCache($file)) {
  248. return $cache->getStatus($this->files[$file]);
  249. }
  250. return self::NOT_FOUND;
  251. }
  252. /**
  253. * search for files matching $pattern
  254. *
  255. * @param string $pattern
  256. * @return array of file data
  257. */
  258. public function search($pattern) {
  259. $pattern = trim($pattern, '%');
  260. $normalizedPattern = $this->normalize($pattern);
  261. $result = array();
  262. $exploreDirs = array('');
  263. while (count($exploreDirs) > 0) {
  264. $dir = array_pop($exploreDirs);
  265. $files = $this->getFolderContents($dir);
  266. // no results?
  267. if (!$files) {
  268. // maybe it's a single shared file
  269. $file = $this->get('');
  270. if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
  271. $result[] = $file;
  272. }
  273. continue;
  274. }
  275. foreach ($files as $file) {
  276. if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
  277. $result[] = $file;
  278. }
  279. if ($file['mimetype'] === 'httpd/unix-directory') {
  280. $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
  281. }
  282. }
  283. }
  284. return $result;
  285. }
  286. /**
  287. * search for files by mimetype
  288. *
  289. * @param string $mimetype
  290. * @return array
  291. */
  292. public function searchByMime($mimetype) {
  293. $mimepart = null;
  294. if (strpos($mimetype, '/') === false) {
  295. $mimepart = $mimetype;
  296. $mimetype = null;
  297. }
  298. $result = array();
  299. $exploreDirs = array('');
  300. while (count($exploreDirs) > 0) {
  301. $dir = array_pop($exploreDirs);
  302. $files = $this->getFolderContents($dir);
  303. // no results?
  304. if (!$files) {
  305. // maybe it's a single shared file
  306. $file = $this->get('');
  307. if (($mimepart && $file['mimepart'] === $mimepart) || ($mimetype && $file['mimetype'] === $mimetype)) {
  308. $result[] = $file;
  309. }
  310. continue;
  311. }
  312. foreach ($files as $file) {
  313. if ($file['mimetype'] === 'httpd/unix-directory') {
  314. $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
  315. } else if (($mimepart && $file['mimepart'] === $mimepart) || ($mimetype && $file['mimetype'] === $mimetype)) {
  316. $result[] = $file;
  317. }
  318. }
  319. }
  320. return $result;
  321. }
  322. /**
  323. * Checks whether the given file has the given tag.
  324. *
  325. * @param \OCP\ITags $tagger
  326. * @param array $fileData file data
  327. * @param string $tag tag to check for
  328. * @return boolean true if the given file has the expected tag,
  329. * false otherwise
  330. */
  331. private function hasTag($tagger, $fileData, $tag) {
  332. $tags = $tagger->getTagsForObjects(array((int)$fileData['fileid']));
  333. return (!empty($tags) && in_array($tag, current($tags)));
  334. }
  335. /**
  336. * search for files by tag
  337. *
  338. * @param string|int $tag tag to search for
  339. * @param string $userId owner of the tags
  340. * @return array file data
  341. */
  342. public function searchByTag($tag, $userId) {
  343. // TODO: inject this
  344. $tagger = \OC::$server->getTagManager()->load('files', null, null, $userId);
  345. $result = array();
  346. $exploreDirs = array('');
  347. // check if root is tagged
  348. $file = $this->get('');
  349. if ($this->hasTag($tagger, $file, $tag)) {
  350. $result[] = $file;
  351. }
  352. // FIXME: this is so wrong and unefficient, need to replace with actual DB queries
  353. while (count($exploreDirs) > 0) {
  354. $dir = array_pop($exploreDirs);
  355. $files = $this->getFolderContents($dir);
  356. if (!$files) {
  357. continue;
  358. }
  359. foreach ($files as $file) {
  360. if ($this->hasTag($tagger, $file, $tag)) {
  361. $result[] = $file;
  362. }
  363. if ($file['mimetype'] === 'httpd/unix-directory') {
  364. $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
  365. }
  366. }
  367. }
  368. return $result;
  369. }
  370. /**
  371. * update the folder size and the size of all parent folders
  372. *
  373. * @param string|boolean $path
  374. * @param array $data (optional) meta data of the folder
  375. */
  376. public function correctFolderSize($path, $data = null) {
  377. $this->calculateFolderSize($path, $data);
  378. if ($path !== '') {
  379. $parent = dirname($path);
  380. if ($parent === '.' or $parent === '/') {
  381. $parent = '';
  382. }
  383. $this->correctFolderSize($parent);
  384. } else {
  385. // bubble up to source cache
  386. $sourceCache = $this->getSourceCache($path);
  387. if (isset($this->files[$path])) {
  388. $parent = dirname($this->files[$path]);
  389. if ($sourceCache) {
  390. $sourceCache->correctFolderSize($parent);
  391. }
  392. }
  393. }
  394. }
  395. /**
  396. * get the size of a folder and set it in the cache
  397. *
  398. * @param string $path
  399. * @param array $entry (optional) meta data of the folder
  400. * @return int
  401. */
  402. public function calculateFolderSize($path, $entry = null) {
  403. $path = ($path === false) ? '' : $path;
  404. if ($cache = $this->getSourceCache($path)) {
  405. return $cache->calculateFolderSize($this->files[$path]);
  406. }
  407. return 0;
  408. }
  409. /**
  410. * get all file ids on the files on the storage
  411. *
  412. * @return int[]
  413. */
  414. public function getAll() {
  415. $ids = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL);
  416. $folderBackend = \OCP\Share::getBackend('folder');
  417. if ($folderBackend instanceof Share_Backend_Collection) {
  418. foreach ($ids as $file) {
  419. /** @var $folderBackend Share_Backend_Collection */
  420. $children = $folderBackend->getChildren($file);
  421. foreach ($children as $child) {
  422. $ids[] = (int)$child['source'];
  423. }
  424. }
  425. }
  426. return $ids;
  427. }
  428. /**
  429. * find a folder in the cache which has not been fully scanned
  430. *
  431. * If multiply incomplete folders are in the cache, the one with the highest id will be returned,
  432. * use the one with the highest id gives the best result with the background scanner, since that is most
  433. * likely the folder where we stopped scanning previously
  434. *
  435. * @return boolean the path of the folder or false when no folder matched
  436. */
  437. public function getIncomplete() {
  438. return false;
  439. }
  440. /**
  441. * get the path of a file on this storage relative to the mount point by it's id
  442. *
  443. * @param int $id
  444. * @param string $pathEnd (optional) used internally for recursive calls
  445. * @return string|null
  446. */
  447. public function getPathById($id, $pathEnd = '') {
  448. // direct shares are easy
  449. if ($id === $this->storage->getSourceId()) {
  450. return ltrim($pathEnd, '/');
  451. } else {
  452. // if the item is a direct share we try and get the path of the parent and append the name of the item to it
  453. list($parent, $name) = $this->getParentInfo($id);
  454. if ($parent > 0) {
  455. return $this->getPathById($parent, '/' . $name . $pathEnd);
  456. } else {
  457. return null;
  458. }
  459. }
  460. }
  461. /**
  462. * @param integer $id
  463. * @return array
  464. */
  465. private function getParentInfo($id) {
  466. $sql = 'SELECT `parent`, `name` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  467. $query = \OCP\DB::prepare($sql);
  468. $result = $query->execute(array($id));
  469. if ($row = $result->fetchRow()) {
  470. return array((int)$row['parent'], $row['name']);
  471. } else {
  472. return array(-1, '');
  473. }
  474. }
  475. }