Storage.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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\Files\Cache;
  8. use OCP\DB\QueryBuilder\IQueryBuilder;
  9. use OCP\Files\Storage\IStorage;
  10. use OCP\IDBConnection;
  11. use Psr\Log\LoggerInterface;
  12. /**
  13. * Handle the mapping between the string and numeric storage ids
  14. *
  15. * Each storage has 2 different ids
  16. * a string id which is generated by the storage backend and reflects the configuration of the storage (e.g. 'smb://user@host/share')
  17. * and a numeric storage id which is referenced in the file cache
  18. *
  19. * A mapping between the two storage ids is stored in the database and accessible through this class
  20. *
  21. * @package OC\Files\Cache
  22. */
  23. class Storage {
  24. /** @var StorageGlobal|null */
  25. private static $globalCache = null;
  26. private $storageId;
  27. private $numericId;
  28. /**
  29. * @return StorageGlobal
  30. */
  31. public static function getGlobalCache() {
  32. if (is_null(self::$globalCache)) {
  33. self::$globalCache = new StorageGlobal(\OC::$server->getDatabaseConnection());
  34. }
  35. return self::$globalCache;
  36. }
  37. /**
  38. * @param \OC\Files\Storage\Storage|string $storage
  39. * @param bool $isAvailable
  40. * @throws \RuntimeException
  41. */
  42. public function __construct($storage, $isAvailable, IDBConnection $connection) {
  43. if ($storage instanceof IStorage) {
  44. $this->storageId = $storage->getId();
  45. } else {
  46. $this->storageId = $storage;
  47. }
  48. $this->storageId = self::adjustStorageId($this->storageId);
  49. if ($row = self::getStorageById($this->storageId)) {
  50. $this->numericId = (int)$row['numeric_id'];
  51. } else {
  52. $available = $isAvailable ? 1 : 0;
  53. if ($connection->insertIfNotExist('*PREFIX*storages', ['id' => $this->storageId, 'available' => $available])) {
  54. $this->numericId = $connection->lastInsertId('*PREFIX*storages');
  55. } else {
  56. if ($row = self::getStorageById($this->storageId)) {
  57. $this->numericId = (int)$row['numeric_id'];
  58. } else {
  59. throw new \RuntimeException('Storage could neither be inserted nor be selected from the database: ' . $this->storageId);
  60. }
  61. }
  62. }
  63. }
  64. /**
  65. * @param string $storageId
  66. * @return array
  67. */
  68. public static function getStorageById($storageId) {
  69. return self::getGlobalCache()->getStorageInfo($storageId);
  70. }
  71. /**
  72. * Adjusts the storage id to use md5 if too long
  73. * @param string $storageId storage id
  74. * @return string unchanged $storageId if its length is less than 64 characters,
  75. * else returns the md5 of $storageId
  76. */
  77. public static function adjustStorageId($storageId) {
  78. if (strlen($storageId) > 64) {
  79. return md5($storageId);
  80. }
  81. return $storageId;
  82. }
  83. /**
  84. * Get the numeric id for the storage
  85. *
  86. * @return int
  87. */
  88. public function getNumericId() {
  89. return $this->numericId;
  90. }
  91. /**
  92. * Get the string id for the storage
  93. *
  94. * @param int $numericId
  95. * @return string|null either the storage id string or null if the numeric id is not known
  96. */
  97. public static function getStorageId(int $numericId): ?string {
  98. $storage = self::getGlobalCache()->getStorageInfoByNumericId($numericId);
  99. return $storage['id'] ?? null;
  100. }
  101. /**
  102. * Get the numeric of the storage with the provided string id
  103. *
  104. * @param $storageId
  105. * @return int|null either the numeric storage id or null if the storage id is not known
  106. */
  107. public static function getNumericStorageId($storageId) {
  108. $storageId = self::adjustStorageId($storageId);
  109. if ($row = self::getStorageById($storageId)) {
  110. return (int)$row['numeric_id'];
  111. } else {
  112. return null;
  113. }
  114. }
  115. /**
  116. * @return array [ available, last_checked ]
  117. */
  118. public function getAvailability() {
  119. if ($row = self::getStorageById($this->storageId)) {
  120. return [
  121. 'available' => (int)$row['available'] === 1,
  122. 'last_checked' => $row['last_checked']
  123. ];
  124. } else {
  125. return [
  126. 'available' => true,
  127. 'last_checked' => time(),
  128. ];
  129. }
  130. }
  131. /**
  132. * @param bool $isAvailable
  133. * @param int $delay amount of seconds to delay reconsidering that storage further
  134. */
  135. public function setAvailability($isAvailable, int $delay = 0) {
  136. $available = $isAvailable ? 1 : 0;
  137. if (!$isAvailable) {
  138. \OCP\Server::get(LoggerInterface::class)->info('Storage with ' . $this->storageId . ' marked as unavailable', ['app' => 'lib']);
  139. }
  140. $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  141. $query->update('storages')
  142. ->set('available', $query->createNamedParameter($available))
  143. ->set('last_checked', $query->createNamedParameter(time() + $delay))
  144. ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
  145. $query->executeStatement();
  146. }
  147. /**
  148. * Check if a string storage id is known
  149. *
  150. * @param string $storageId
  151. * @return bool
  152. */
  153. public static function exists($storageId) {
  154. return !is_null(self::getNumericStorageId($storageId));
  155. }
  156. /**
  157. * remove the entry for the storage
  158. *
  159. * @param string $storageId
  160. */
  161. public static function remove($storageId) {
  162. $storageId = self::adjustStorageId($storageId);
  163. $numericId = self::getNumericStorageId($storageId);
  164. $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  165. $query->delete('storages')
  166. ->where($query->expr()->eq('id', $query->createNamedParameter($storageId)));
  167. $query->executeStatement();
  168. if (!is_null($numericId)) {
  169. $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  170. $query->delete('filecache')
  171. ->where($query->expr()->eq('storage', $query->createNamedParameter($numericId)));
  172. $query->executeStatement();
  173. }
  174. }
  175. /**
  176. * remove the entry for the storage by the mount id
  177. *
  178. * @param int $mountId
  179. */
  180. public static function cleanByMountId(int $mountId) {
  181. $db = \OC::$server->getDatabaseConnection();
  182. try {
  183. $db->beginTransaction();
  184. $query = $db->getQueryBuilder();
  185. $query->select('storage_id')
  186. ->from('mounts')
  187. ->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  188. $storageIds = $query->executeQuery()->fetchAll(\PDO::FETCH_COLUMN);
  189. $storageIds = array_unique($storageIds);
  190. $query = $db->getQueryBuilder();
  191. $query->delete('filecache')
  192. ->where($query->expr()->in('storage', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)));
  193. $query->executeStatement();
  194. $query = $db->getQueryBuilder();
  195. $query->delete('storages')
  196. ->where($query->expr()->in('numeric_id', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)));
  197. $query->executeStatement();
  198. $query = $db->getQueryBuilder();
  199. $query->delete('mounts')
  200. ->where($query->expr()->eq('mount_id', $query->createNamedParameter($mountId, IQueryBuilder::PARAM_INT)));
  201. $query->executeStatement();
  202. $db->commit();
  203. } catch (\Exception $e) {
  204. $db->rollBack();
  205. throw $e;
  206. }
  207. }
  208. }