cache.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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->getMountPoint(), $this->storage->getItemType());
  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 = \OC_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. * Move a file or folder in the cache
  223. *
  224. * @param string $source
  225. * @param string $target
  226. */
  227. public function move($source, $target) {
  228. if ($cache = $this->getSourceCache($source)) {
  229. $file = \OC_Share_Backend_File::getSource($target, $this->storage->getMountPoint(), $this->storage->getItemType());
  230. if ($file && isset($file['path'])) {
  231. $cache->move($this->files[$source], $file['path']);
  232. }
  233. }
  234. }
  235. /**
  236. * remove all entries for files that are stored on the storage from the cache
  237. */
  238. public function clear() {
  239. // Not a valid action for Shared Cache
  240. }
  241. /**
  242. * @param string $file
  243. *
  244. * @return int, Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
  245. */
  246. public function getStatus($file) {
  247. if ($file == '') {
  248. return self::COMPLETE;
  249. }
  250. if ($cache = $this->getSourceCache($file)) {
  251. return $cache->getStatus($this->files[$file]);
  252. }
  253. return self::NOT_FOUND;
  254. }
  255. /**
  256. * search for files matching $pattern
  257. *
  258. * @param string $pattern
  259. * @return array of file data
  260. */
  261. public function search($pattern) {
  262. $pattern = trim($pattern, '%');
  263. $normalizedPattern = $this->normalize($pattern);
  264. $result = array();
  265. $exploreDirs = array('');
  266. while (count($exploreDirs) > 0) {
  267. $dir = array_pop($exploreDirs);
  268. $files = $this->getFolderContents($dir);
  269. // no results?
  270. if (!$files) {
  271. // maybe it's a single shared file
  272. $file = $this->get('');
  273. if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
  274. $result[] = $file;
  275. }
  276. continue;
  277. }
  278. foreach ($files as $file) {
  279. if ($normalizedPattern === '' || stristr($file['name'], $normalizedPattern) !== false) {
  280. $result[] = $file;
  281. }
  282. if ($file['mimetype'] === 'httpd/unix-directory') {
  283. $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
  284. }
  285. }
  286. }
  287. return $result;
  288. }
  289. /**
  290. * search for files by mimetype
  291. *
  292. * @param string $mimetype
  293. * @return array
  294. */
  295. public function searchByMime($mimetype) {
  296. $mimepart = null;
  297. if (strpos($mimetype, '/') === false) {
  298. $mimepart = $mimetype;
  299. $mimetype = null;
  300. }
  301. $result = array();
  302. $exploreDirs = array('');
  303. while (count($exploreDirs) > 0) {
  304. $dir = array_pop($exploreDirs);
  305. $files = $this->getFolderContents($dir);
  306. // no results?
  307. if (!$files) {
  308. // maybe it's a single shared file
  309. $file = $this->get('');
  310. if (($mimepart && $file['mimepart'] === $mimepart) || ($mimetype && $file['mimetype'] === $mimetype)) {
  311. $result[] = $file;
  312. }
  313. continue;
  314. }
  315. foreach ($files as $file) {
  316. if ($file['mimetype'] === 'httpd/unix-directory') {
  317. $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
  318. } else if (($mimepart && $file['mimepart'] === $mimepart) || ($mimetype && $file['mimetype'] === $mimetype)) {
  319. $result[] = $file;
  320. }
  321. }
  322. }
  323. return $result;
  324. }
  325. /**
  326. * Checks whether the given file has the given tag.
  327. *
  328. * @param \OCP\ITags $tagger
  329. * @param array $fileData file data
  330. * @param string $tag tag to check for
  331. * @return boolean true if the given file has the expected tag,
  332. * false otherwise
  333. */
  334. private function hasTag($tagger, $fileData, $tag) {
  335. $tags = $tagger->getTagsForObjects(array((int)$fileData['fileid']));
  336. return (!empty($tags) && in_array($tag, current($tags)));
  337. }
  338. /**
  339. * search for files by tag
  340. *
  341. * @param string|int $tag tag to search for
  342. * @param string $userId owner of the tags
  343. * @return array file data
  344. */
  345. public function searchByTag($tag, $userId) {
  346. // TODO: inject this
  347. $tagger = \OC::$server->getTagManager()->load('files', null, null, $userId);
  348. $result = array();
  349. $exploreDirs = array('');
  350. // check if root is tagged
  351. $file = $this->get('');
  352. if ($this->hasTag($tagger, $file, $tag)) {
  353. $result[] = $file;
  354. }
  355. // FIXME: this is so wrong and unefficient, need to replace with actual DB queries
  356. while (count($exploreDirs) > 0) {
  357. $dir = array_pop($exploreDirs);
  358. $files = $this->getFolderContents($dir);
  359. if (!$files) {
  360. continue;
  361. }
  362. foreach ($files as $file) {
  363. if ($this->hasTag($tagger, $file, $tag)) {
  364. $result[] = $file;
  365. }
  366. if ($file['mimetype'] === 'httpd/unix-directory') {
  367. $exploreDirs[] = ltrim($dir . '/' . $file['name'], '/');
  368. }
  369. }
  370. }
  371. return $result;
  372. }
  373. /**
  374. * update the folder size and the size of all parent folders
  375. *
  376. * @param string|boolean $path
  377. * @param array $data (optional) meta data of the folder
  378. */
  379. public function correctFolderSize($path, $data = null) {
  380. $this->calculateFolderSize($path, $data);
  381. if ($path !== '') {
  382. $parent = dirname($path);
  383. if ($parent === '.' or $parent === '/') {
  384. $parent = '';
  385. }
  386. $this->correctFolderSize($parent);
  387. } else {
  388. // bubble up to source cache
  389. $sourceCache = $this->getSourceCache($path);
  390. $parent = dirname($this->files[$path]);
  391. $sourceCache->correctFolderSize($parent);
  392. }
  393. }
  394. /**
  395. * get the size of a folder and set it in the cache
  396. *
  397. * @param string $path
  398. * @param array $entry (optional) meta data of the folder
  399. * @return int
  400. */
  401. public function calculateFolderSize($path, $entry = null) {
  402. $path = ($path === false) ? '' : $path;
  403. if ($cache = $this->getSourceCache($path)) {
  404. return $cache->calculateFolderSize($this->files[$path]);
  405. }
  406. return 0;
  407. }
  408. /**
  409. * get all file ids on the files on the storage
  410. *
  411. * @return int[]
  412. */
  413. public function getAll() {
  414. $ids = \OCP\Share::getItemsSharedWith('file', \OC_Share_Backend_File::FORMAT_GET_ALL);
  415. $folderBackend = \OCP\Share::getBackend('folder');
  416. if ($folderBackend instanceof Share_Backend_Collection) {
  417. foreach ($ids as $file) {
  418. /** @var $folderBackend Share_Backend_Collection */
  419. $children = $folderBackend->getChildren($file);
  420. foreach ($children as $child) {
  421. $ids[] = (int)$child['source'];
  422. }
  423. }
  424. }
  425. return $ids;
  426. }
  427. /**
  428. * find a folder in the cache which has not been fully scanned
  429. *
  430. * If multiply incomplete folders are in the cache, the one with the highest id will be returned,
  431. * use the one with the highest id gives the best result with the background scanner, since that is most
  432. * likely the folder where we stopped scanning previously
  433. *
  434. * @return boolean the path of the folder or false when no folder matched
  435. */
  436. public function getIncomplete() {
  437. return false;
  438. }
  439. /**
  440. * get the path of a file on this storage relative to the mount point by it's id
  441. *
  442. * @param int $id
  443. * @param string $pathEnd (optional) used internally for recursive calls
  444. * @return string|null
  445. */
  446. public function getPathById($id, $pathEnd = '') {
  447. // direct shares are easy
  448. if ($id === $this->storage->getSourceId()) {
  449. return ltrim($pathEnd, '/');
  450. } else {
  451. // 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
  452. list($parent, $name) = $this->getParentInfo($id);
  453. if ($parent > 0) {
  454. return $this->getPathById($parent, '/' . $name . $pathEnd);
  455. } else {
  456. return null;
  457. }
  458. }
  459. }
  460. /**
  461. * @param integer $id
  462. * @return array
  463. */
  464. private function getParentInfo($id) {
  465. $sql = 'SELECT `parent`, `name` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  466. $query = \OC_DB::prepare($sql);
  467. $result = $query->execute(array($id));
  468. if ($row = $result->fetchRow()) {
  469. return array((int)$row['parent'], $row['name']);
  470. } else {
  471. return array(-1, '');
  472. }
  473. }
  474. }