File.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OC\Cache;
  8. use OC\Files\Filesystem;
  9. use OC\Files\View;
  10. use OCP\ICache;
  11. use OCP\Security\ISecureRandom;
  12. use Psr\Log\LoggerInterface;
  13. class File implements ICache {
  14. /** @var View */
  15. protected $storage;
  16. /**
  17. * Returns the cache storage for the logged in user
  18. *
  19. * @return \OC\Files\View cache storage
  20. * @throws \OC\ForbiddenException
  21. * @throws \OC\User\NoUserException
  22. */
  23. protected function getStorage() {
  24. if ($this->storage !== null) {
  25. return $this->storage;
  26. }
  27. if (\OC::$server->getUserSession()->isLoggedIn()) {
  28. $rootView = new View();
  29. $user = \OC::$server->getUserSession()->getUser();
  30. Filesystem::initMountPoints($user->getUID());
  31. if (!$rootView->file_exists('/' . $user->getUID() . '/cache')) {
  32. $rootView->mkdir('/' . $user->getUID() . '/cache');
  33. }
  34. $this->storage = new View('/' . $user->getUID() . '/cache');
  35. return $this->storage;
  36. } else {
  37. \OCP\Server::get(LoggerInterface::class)->error('Can\'t get cache storage, user not logged in', ['app' => 'core']);
  38. throw new \OC\ForbiddenException('Can\t get cache storage, user not logged in');
  39. }
  40. }
  41. /**
  42. * @param string $key
  43. * @return mixed|null
  44. * @throws \OC\ForbiddenException
  45. */
  46. public function get($key) {
  47. $result = null;
  48. if ($this->hasKey($key)) {
  49. $storage = $this->getStorage();
  50. $result = $storage->file_get_contents($key);
  51. }
  52. return $result;
  53. }
  54. /**
  55. * Returns the size of the stored/cached data
  56. *
  57. * @param string $key
  58. * @return int
  59. */
  60. public function size($key) {
  61. $result = 0;
  62. if ($this->hasKey($key)) {
  63. $storage = $this->getStorage();
  64. $result = $storage->filesize($key);
  65. }
  66. return $result;
  67. }
  68. /**
  69. * @param string $key
  70. * @param mixed $value
  71. * @param int $ttl
  72. * @return bool|mixed
  73. * @throws \OC\ForbiddenException
  74. */
  75. public function set($key, $value, $ttl = 0) {
  76. $storage = $this->getStorage();
  77. $result = false;
  78. // unique id to avoid chunk collision, just in case
  79. $uniqueId = \OC::$server->get(ISecureRandom::class)->generate(
  80. 16,
  81. ISecureRandom::CHAR_ALPHANUMERIC
  82. );
  83. // use part file to prevent hasKey() to find the key
  84. // while it is being written
  85. $keyPart = $key . '.' . $uniqueId . '.part';
  86. if ($storage and $storage->file_put_contents($keyPart, $value)) {
  87. if ($ttl === 0) {
  88. $ttl = 86400; // 60*60*24
  89. }
  90. $result = $storage->touch($keyPart, time() + $ttl);
  91. $result &= $storage->rename($keyPart, $key);
  92. }
  93. return $result;
  94. }
  95. /**
  96. * @param string $key
  97. * @return bool
  98. * @throws \OC\ForbiddenException
  99. */
  100. public function hasKey($key) {
  101. $storage = $this->getStorage();
  102. if ($storage && $storage->is_file($key) && $storage->isReadable($key)) {
  103. return true;
  104. }
  105. return false;
  106. }
  107. /**
  108. * @param string $key
  109. * @return bool|mixed
  110. * @throws \OC\ForbiddenException
  111. */
  112. public function remove($key) {
  113. $storage = $this->getStorage();
  114. if (!$storage) {
  115. return false;
  116. }
  117. return $storage->unlink($key);
  118. }
  119. /**
  120. * @param string $prefix
  121. * @return bool
  122. * @throws \OC\ForbiddenException
  123. */
  124. public function clear($prefix = '') {
  125. $storage = $this->getStorage();
  126. if ($storage and $storage->is_dir('/')) {
  127. $dh = $storage->opendir('/');
  128. if (is_resource($dh)) {
  129. while (($file = readdir($dh)) !== false) {
  130. if ($file != '.' and $file != '..' and ($prefix === '' || str_starts_with($file, $prefix))) {
  131. $storage->unlink('/' . $file);
  132. }
  133. }
  134. }
  135. }
  136. return true;
  137. }
  138. /**
  139. * Runs GC
  140. * @throws \OC\ForbiddenException
  141. */
  142. public function gc() {
  143. $storage = $this->getStorage();
  144. if ($storage) {
  145. // extra hour safety, in case of stray part chunks that take longer to write,
  146. // because touch() is only called after the chunk was finished
  147. $now = time() - 3600;
  148. $dh = $storage->opendir('/');
  149. if (!is_resource($dh)) {
  150. return null;
  151. }
  152. while (($file = readdir($dh)) !== false) {
  153. if ($file != '.' and $file != '..') {
  154. try {
  155. $mtime = $storage->filemtime('/' . $file);
  156. if ($mtime < $now) {
  157. $storage->unlink('/' . $file);
  158. }
  159. } catch (\OCP\Lock\LockedException $e) {
  160. // ignore locked chunks
  161. \OCP\Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']);
  162. } catch (\OCP\Files\ForbiddenException $e) {
  163. \OCP\Server::get(LoggerInterface::class)->debug('Could not cleanup forbidden chunk "' . $file . '"', ['app' => 'core']);
  164. } catch (\OCP\Files\LockNotAcquiredException $e) {
  165. \OCP\Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']);
  166. }
  167. }
  168. }
  169. }
  170. }
  171. public static function isAvailable(): bool {
  172. return true;
  173. }
  174. }