You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

805 lines
21 KiB

9 years ago
9 years ago
10 years ago
9 years ago
10 years ago
13 years ago
11 years ago
13 years ago
11 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
10 years ago
10 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bart Visscher <bartv@thisnet.nl>
  7. * @author Björn Schießle <bjoern@schiessle.org>
  8. * @author hkjolhede <hkjolhede@gmail.com>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  11. * @author Lukas Reschke <lukas@statuscode.ch>
  12. * @author Martin Mattel <martin.mattel@diemattels.at>
  13. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  14. * @author Morris Jobke <hey@morrisjobke.de>
  15. * @author Robin Appelman <robin@icewind.nl>
  16. * @author Robin McCorkell <robin@mccorkell.me.uk>
  17. * @author Roeland Jago Douma <roeland@famdouma.nl>
  18. * @author Sam Tuke <mail@samtuke.com>
  19. * @author scambra <sergio@entrecables.com>
  20. * @author Stefan Weil <sw@weilnetz.de>
  21. * @author Thomas Müller <thomas.mueller@tmit.eu>
  22. * @author Vincent Petry <pvince81@owncloud.com>
  23. *
  24. * @license AGPL-3.0
  25. *
  26. * This code is free software: you can redistribute it and/or modify
  27. * it under the terms of the GNU Affero General Public License, version 3,
  28. * as published by the Free Software Foundation.
  29. *
  30. * This program is distributed in the hope that it will be useful,
  31. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  32. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  33. * GNU Affero General Public License for more details.
  34. *
  35. * You should have received a copy of the GNU Affero General Public License, version 3,
  36. * along with this program. If not, see <http://www.gnu.org/licenses/>
  37. *
  38. */
  39. namespace OC\Files\Storage;
  40. use OC\Files\Cache\Cache;
  41. use OC\Files\Cache\Propagator;
  42. use OC\Files\Cache\Scanner;
  43. use OC\Files\Cache\Updater;
  44. use OC\Files\Filesystem;
  45. use OC\Files\Cache\Watcher;
  46. use OCP\Files\EmptyFileNameException;
  47. use OCP\Files\FileNameTooLongException;
  48. use OCP\Files\InvalidCharacterInPathException;
  49. use OCP\Files\InvalidDirectoryException;
  50. use OCP\Files\InvalidPathException;
  51. use OCP\Files\ReservedWordException;
  52. use OCP\Files\Storage\ILockingStorage;
  53. use OCP\Lock\ILockingProvider;
  54. use OCP\Lock\LockedException;
  55. /**
  56. * Storage backend class for providing common filesystem operation methods
  57. * which are not storage-backend specific.
  58. *
  59. * \OC\Files\Storage\Common is never used directly; it is extended by all other
  60. * storage backends, where its methods may be overridden, and additional
  61. * (backend-specific) methods are defined.
  62. *
  63. * Some \OC\Files\Storage\Common methods call functions which are first defined
  64. * in classes which extend it, e.g. $this->stat() .
  65. */
  66. abstract class Common implements Storage, ILockingStorage {
  67. use LocalTempFileTrait;
  68. protected $cache;
  69. protected $scanner;
  70. protected $watcher;
  71. protected $propagator;
  72. protected $storageCache;
  73. protected $updater;
  74. protected $mountOptions = [];
  75. protected $owner = null;
  76. private $shouldLogLocks = null;
  77. private $logger;
  78. public function __construct($parameters) {
  79. }
  80. /**
  81. * Remove a file or folder
  82. *
  83. * @param string $path
  84. * @return bool
  85. */
  86. protected function remove($path) {
  87. if ($this->is_dir($path)) {
  88. return $this->rmdir($path);
  89. } else if ($this->is_file($path)) {
  90. return $this->unlink($path);
  91. } else {
  92. return false;
  93. }
  94. }
  95. public function is_dir($path) {
  96. return $this->filetype($path) === 'dir';
  97. }
  98. public function is_file($path) {
  99. return $this->filetype($path) === 'file';
  100. }
  101. public function filesize($path) {
  102. if ($this->is_dir($path)) {
  103. return 0; //by definition
  104. } else {
  105. $stat = $this->stat($path);
  106. if (isset($stat['size'])) {
  107. return $stat['size'];
  108. } else {
  109. return 0;
  110. }
  111. }
  112. }
  113. public function isReadable($path) {
  114. // at least check whether it exists
  115. // subclasses might want to implement this more thoroughly
  116. return $this->file_exists($path);
  117. }
  118. public function isUpdatable($path) {
  119. // at least check whether it exists
  120. // subclasses might want to implement this more thoroughly
  121. // a non-existing file/folder isn't updatable
  122. return $this->file_exists($path);
  123. }
  124. public function isCreatable($path) {
  125. if ($this->is_dir($path) && $this->isUpdatable($path)) {
  126. return true;
  127. }
  128. return false;
  129. }
  130. public function isDeletable($path) {
  131. if ($path === '' || $path === '/') {
  132. return false;
  133. }
  134. $parent = dirname($path);
  135. return $this->isUpdatable($parent) && $this->isUpdatable($path);
  136. }
  137. public function isSharable($path) {
  138. return $this->isReadable($path);
  139. }
  140. public function getPermissions($path) {
  141. $permissions = 0;
  142. if ($this->isCreatable($path)) {
  143. $permissions |= \OCP\Constants::PERMISSION_CREATE;
  144. }
  145. if ($this->isReadable($path)) {
  146. $permissions |= \OCP\Constants::PERMISSION_READ;
  147. }
  148. if ($this->isUpdatable($path)) {
  149. $permissions |= \OCP\Constants::PERMISSION_UPDATE;
  150. }
  151. if ($this->isDeletable($path)) {
  152. $permissions |= \OCP\Constants::PERMISSION_DELETE;
  153. }
  154. if ($this->isSharable($path)) {
  155. $permissions |= \OCP\Constants::PERMISSION_SHARE;
  156. }
  157. return $permissions;
  158. }
  159. public function filemtime($path) {
  160. $stat = $this->stat($path);
  161. if (isset($stat['mtime']) && $stat['mtime'] > 0) {
  162. return $stat['mtime'];
  163. } else {
  164. return 0;
  165. }
  166. }
  167. public function file_get_contents($path) {
  168. $handle = $this->fopen($path, "r");
  169. if (!$handle) {
  170. return false;
  171. }
  172. $data = stream_get_contents($handle);
  173. fclose($handle);
  174. return $data;
  175. }
  176. public function file_put_contents($path, $data) {
  177. $handle = $this->fopen($path, "w");
  178. $this->removeCachedFile($path);
  179. $count = fwrite($handle, $data);
  180. fclose($handle);
  181. return $count;
  182. }
  183. public function rename($path1, $path2) {
  184. $this->remove($path2);
  185. $this->removeCachedFile($path1);
  186. return $this->copy($path1, $path2) and $this->remove($path1);
  187. }
  188. public function copy($path1, $path2) {
  189. if ($this->is_dir($path1)) {
  190. $this->remove($path2);
  191. $dir = $this->opendir($path1);
  192. $this->mkdir($path2);
  193. while ($file = readdir($dir)) {
  194. if (!Filesystem::isIgnoredDir($file)) {
  195. if (!$this->copy($path1 . '/' . $file, $path2 . '/' . $file)) {
  196. return false;
  197. }
  198. }
  199. }
  200. closedir($dir);
  201. return true;
  202. } else {
  203. $source = $this->fopen($path1, 'r');
  204. $target = $this->fopen($path2, 'w');
  205. list(, $result) = \OC_Helper::streamCopy($source, $target);
  206. $this->removeCachedFile($path2);
  207. return $result;
  208. }
  209. }
  210. public function getMimeType($path) {
  211. if ($this->is_dir($path)) {
  212. return 'httpd/unix-directory';
  213. } elseif ($this->file_exists($path)) {
  214. return \OC::$server->getMimeTypeDetector()->detectPath($path);
  215. } else {
  216. return false;
  217. }
  218. }
  219. public function hash($type, $path, $raw = false) {
  220. $fh = $this->fopen($path, 'rb');
  221. $ctx = hash_init($type);
  222. hash_update_stream($ctx, $fh);
  223. fclose($fh);
  224. return hash_final($ctx, $raw);
  225. }
  226. public function search($query) {
  227. return $this->searchInDir($query);
  228. }
  229. public function getLocalFile($path) {
  230. return $this->getCachedFile($path);
  231. }
  232. /**
  233. * @param string $path
  234. * @param string $target
  235. */
  236. private function addLocalFolder($path, $target) {
  237. $dh = $this->opendir($path);
  238. if (is_resource($dh)) {
  239. while (($file = readdir($dh)) !== false) {
  240. if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
  241. if ($this->is_dir($path . '/' . $file)) {
  242. mkdir($target . '/' . $file);
  243. $this->addLocalFolder($path . '/' . $file, $target . '/' . $file);
  244. } else {
  245. $tmp = $this->toTmpFile($path . '/' . $file);
  246. rename($tmp, $target . '/' . $file);
  247. }
  248. }
  249. }
  250. }
  251. }
  252. /**
  253. * @param string $query
  254. * @param string $dir
  255. * @return array
  256. */
  257. protected function searchInDir($query, $dir = '') {
  258. $files = array();
  259. $dh = $this->opendir($dir);
  260. if (is_resource($dh)) {
  261. while (($item = readdir($dh)) !== false) {
  262. if (\OC\Files\Filesystem::isIgnoredDir($item)) continue;
  263. if (strstr(strtolower($item), strtolower($query)) !== false) {
  264. $files[] = $dir . '/' . $item;
  265. }
  266. if ($this->is_dir($dir . '/' . $item)) {
  267. $files = array_merge($files, $this->searchInDir($query, $dir . '/' . $item));
  268. }
  269. }
  270. }
  271. closedir($dh);
  272. return $files;
  273. }
  274. /**
  275. * check if a file or folder has been updated since $time
  276. *
  277. * The method is only used to check if the cache needs to be updated. Storage backends that don't support checking
  278. * the mtime should always return false here. As a result storage implementations that always return false expect
  279. * exclusive access to the backend and will not pick up files that have been added in a way that circumvents
  280. * ownClouds filesystem.
  281. *
  282. * @param string $path
  283. * @param int $time
  284. * @return bool
  285. */
  286. public function hasUpdated($path, $time) {
  287. return $this->filemtime($path) > $time;
  288. }
  289. public function getCache($path = '', $storage = null) {
  290. if (!$storage) {
  291. $storage = $this;
  292. }
  293. if (!isset($storage->cache)) {
  294. $storage->cache = new Cache($storage);
  295. }
  296. return $storage->cache;
  297. }
  298. public function getScanner($path = '', $storage = null) {
  299. if (!$storage) {
  300. $storage = $this;
  301. }
  302. if (!isset($storage->scanner)) {
  303. $storage->scanner = new Scanner($storage);
  304. }
  305. return $storage->scanner;
  306. }
  307. public function getWatcher($path = '', $storage = null) {
  308. if (!$storage) {
  309. $storage = $this;
  310. }
  311. if (!isset($this->watcher)) {
  312. $this->watcher = new Watcher($storage);
  313. $globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
  314. $this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
  315. }
  316. return $this->watcher;
  317. }
  318. /**
  319. * get a propagator instance for the cache
  320. *
  321. * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher
  322. * @return \OC\Files\Cache\Propagator
  323. */
  324. public function getPropagator($storage = null) {
  325. if (!$storage) {
  326. $storage = $this;
  327. }
  328. if (!isset($storage->propagator)) {
  329. $storage->propagator = new Propagator($storage, \OC::$server->getDatabaseConnection());
  330. }
  331. return $storage->propagator;
  332. }
  333. public function getUpdater($storage = null) {
  334. if (!$storage) {
  335. $storage = $this;
  336. }
  337. if (!isset($storage->updater)) {
  338. $storage->updater = new Updater($storage);
  339. }
  340. return $storage->updater;
  341. }
  342. public function getStorageCache($storage = null) {
  343. if (!$storage) {
  344. $storage = $this;
  345. }
  346. if (!isset($this->storageCache)) {
  347. $this->storageCache = new \OC\Files\Cache\Storage($storage);
  348. }
  349. return $this->storageCache;
  350. }
  351. /**
  352. * get the owner of a path
  353. *
  354. * @param string $path The path to get the owner
  355. * @return string|false uid or false
  356. */
  357. public function getOwner($path) {
  358. if ($this->owner === null) {
  359. $this->owner = \OC_User::getUser();
  360. }
  361. return $this->owner;
  362. }
  363. /**
  364. * get the ETag for a file or folder
  365. *
  366. * @param string $path
  367. * @return string
  368. */
  369. public function getETag($path) {
  370. return uniqid();
  371. }
  372. /**
  373. * clean a path, i.e. remove all redundant '.' and '..'
  374. * making sure that it can't point to higher than '/'
  375. *
  376. * @param string $path The path to clean
  377. * @return string cleaned path
  378. */
  379. public function cleanPath($path) {
  380. if (strlen($path) == 0 or $path[0] != '/') {
  381. $path = '/' . $path;
  382. }
  383. $output = array();
  384. foreach (explode('/', $path) as $chunk) {
  385. if ($chunk == '..') {
  386. array_pop($output);
  387. } else if ($chunk == '.') {
  388. } else {
  389. $output[] = $chunk;
  390. }
  391. }
  392. return implode('/', $output);
  393. }
  394. /**
  395. * Test a storage for availability
  396. *
  397. * @return bool
  398. */
  399. public function test() {
  400. try {
  401. if ($this->stat('')) {
  402. return true;
  403. }
  404. return false;
  405. } catch (\Exception $e) {
  406. return false;
  407. }
  408. }
  409. /**
  410. * get the free space in the storage
  411. *
  412. * @param string $path
  413. * @return int|false
  414. */
  415. public function free_space($path) {
  416. return \OCP\Files\FileInfo::SPACE_UNKNOWN;
  417. }
  418. /**
  419. * {@inheritdoc}
  420. */
  421. public function isLocal() {
  422. // the common implementation returns a temporary file by
  423. // default, which is not local
  424. return false;
  425. }
  426. /**
  427. * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class
  428. *
  429. * @param string $class
  430. * @return bool
  431. */
  432. public function instanceOfStorage($class) {
  433. if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') {
  434. // FIXME Temporary fix to keep existing checks working
  435. $class = '\OCA\Files_Sharing\SharedStorage';
  436. }
  437. return is_a($this, $class);
  438. }
  439. /**
  440. * A custom storage implementation can return an url for direct download of a give file.
  441. *
  442. * For now the returned array can hold the parameter url - in future more attributes might follow.
  443. *
  444. * @param string $path
  445. * @return array|false
  446. */
  447. public function getDirectDownload($path) {
  448. return [];
  449. }
  450. /**
  451. * @inheritdoc
  452. * @throws InvalidPathException
  453. */
  454. public function verifyPath($path, $fileName) {
  455. // verify empty and dot files
  456. $trimmed = trim($fileName);
  457. if ($trimmed === '') {
  458. throw new EmptyFileNameException();
  459. }
  460. if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) {
  461. throw new InvalidDirectoryException();
  462. }
  463. if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
  464. // verify database - e.g. mysql only 3-byte chars
  465. if (preg_match('%(?:
  466. \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
  467. | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
  468. | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
  469. )%xs', $fileName)) {
  470. throw new InvalidCharacterInPathException();
  471. }
  472. }
  473. if (isset($fileName[255])) {
  474. throw new FileNameTooLongException();
  475. }
  476. // NOTE: $path will remain unverified for now
  477. $this->verifyPosixPath($fileName);
  478. }
  479. /**
  480. * @param string $fileName
  481. * @throws InvalidPathException
  482. */
  483. protected function verifyPosixPath($fileName) {
  484. $fileName = trim($fileName);
  485. $this->scanForInvalidCharacters($fileName, "\\/");
  486. $reservedNames = ['*'];
  487. if (in_array($fileName, $reservedNames)) {
  488. throw new ReservedWordException();
  489. }
  490. }
  491. /**
  492. * @param string $fileName
  493. * @param string $invalidChars
  494. * @throws InvalidPathException
  495. */
  496. private function scanForInvalidCharacters($fileName, $invalidChars) {
  497. foreach (str_split($invalidChars) as $char) {
  498. if (strpos($fileName, $char) !== false) {
  499. throw new InvalidCharacterInPathException();
  500. }
  501. }
  502. $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
  503. if ($sanitizedFileName !== $fileName) {
  504. throw new InvalidCharacterInPathException();
  505. }
  506. }
  507. /**
  508. * @param array $options
  509. */
  510. public function setMountOptions(array $options) {
  511. $this->mountOptions = $options;
  512. }
  513. /**
  514. * @param string $name
  515. * @param mixed $default
  516. * @return mixed
  517. */
  518. public function getMountOption($name, $default = null) {
  519. return isset($this->mountOptions[$name]) ? $this->mountOptions[$name] : $default;
  520. }
  521. /**
  522. * @param \OCP\Files\Storage $sourceStorage
  523. * @param string $sourceInternalPath
  524. * @param string $targetInternalPath
  525. * @param bool $preserveMtime
  526. * @return bool
  527. */
  528. public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = false) {
  529. if ($sourceStorage === $this) {
  530. return $this->copy($sourceInternalPath, $targetInternalPath);
  531. }
  532. if ($sourceStorage->is_dir($sourceInternalPath)) {
  533. $dh = $sourceStorage->opendir($sourceInternalPath);
  534. $result = $this->mkdir($targetInternalPath);
  535. if (is_resource($dh)) {
  536. while ($result and ($file = readdir($dh)) !== false) {
  537. if (!Filesystem::isIgnoredDir($file)) {
  538. $result &= $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file);
  539. }
  540. }
  541. }
  542. } else {
  543. $source = $sourceStorage->fopen($sourceInternalPath, 'r');
  544. // TODO: call fopen in a way that we execute again all storage wrappers
  545. // to avoid that we bypass storage wrappers which perform important actions
  546. // for this operation. Same is true for all other operations which
  547. // are not the same as the original one.Once this is fixed we also
  548. // need to adjust the encryption wrapper.
  549. $target = $this->fopen($targetInternalPath, 'w');
  550. list(, $result) = \OC_Helper::streamCopy($source, $target);
  551. if ($result and $preserveMtime) {
  552. $this->touch($targetInternalPath, $sourceStorage->filemtime($sourceInternalPath));
  553. }
  554. fclose($source);
  555. fclose($target);
  556. if (!$result) {
  557. // delete partially written target file
  558. $this->unlink($targetInternalPath);
  559. // delete cache entry that was created by fopen
  560. $this->getCache()->remove($targetInternalPath);
  561. }
  562. }
  563. return (bool)$result;
  564. }
  565. /**
  566. * @param \OCP\Files\Storage $sourceStorage
  567. * @param string $sourceInternalPath
  568. * @param string $targetInternalPath
  569. * @return bool
  570. */
  571. public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
  572. if ($sourceStorage === $this) {
  573. return $this->rename($sourceInternalPath, $targetInternalPath);
  574. }
  575. if (!$sourceStorage->isDeletable($sourceInternalPath)) {
  576. return false;
  577. }
  578. $result = $this->copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, true);
  579. if ($result) {
  580. if ($sourceStorage->is_dir($sourceInternalPath)) {
  581. $result &= $sourceStorage->rmdir($sourceInternalPath);
  582. } else {
  583. $result &= $sourceStorage->unlink($sourceInternalPath);
  584. }
  585. }
  586. return $result;
  587. }
  588. /**
  589. * @inheritdoc
  590. */
  591. public function getMetaData($path) {
  592. $permissions = $this->getPermissions($path);
  593. if (!$permissions & \OCP\Constants::PERMISSION_READ) {
  594. //can't read, nothing we can do
  595. return null;
  596. }
  597. $data = [];
  598. $data['mimetype'] = $this->getMimeType($path);
  599. $data['mtime'] = $this->filemtime($path);
  600. if ($data['mtime'] === false) {
  601. $data['mtime'] = time();
  602. }
  603. if ($data['mimetype'] == 'httpd/unix-directory') {
  604. $data['size'] = -1; //unknown
  605. } else {
  606. $data['size'] = $this->filesize($path);
  607. }
  608. $data['etag'] = $this->getETag($path);
  609. $data['storage_mtime'] = $data['mtime'];
  610. $data['permissions'] = $permissions;
  611. return $data;
  612. }
  613. /**
  614. * @param string $path
  615. * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
  616. * @param \OCP\Lock\ILockingProvider $provider
  617. * @throws \OCP\Lock\LockedException
  618. */
  619. public function acquireLock($path, $type, ILockingProvider $provider) {
  620. $logger = $this->getLockLogger();
  621. if ($logger) {
  622. $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
  623. $logger->info(
  624. sprintf(
  625. 'acquire %s lock on "%s" on storage "%s"',
  626. $typeString,
  627. $path,
  628. $this->getId()
  629. ),
  630. [
  631. 'app' => 'locking',
  632. ]
  633. );
  634. }
  635. try {
  636. $provider->acquireLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
  637. } catch (LockedException $e) {
  638. if ($logger) {
  639. $logger->logException($e);
  640. }
  641. throw $e;
  642. }
  643. }
  644. /**
  645. * @param string $path
  646. * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
  647. * @param \OCP\Lock\ILockingProvider $provider
  648. * @throws \OCP\Lock\LockedException
  649. */
  650. public function releaseLock($path, $type, ILockingProvider $provider) {
  651. $logger = $this->getLockLogger();
  652. if ($logger) {
  653. $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
  654. $logger->info(
  655. sprintf(
  656. 'release %s lock on "%s" on storage "%s"',
  657. $typeString,
  658. $path,
  659. $this->getId()
  660. ),
  661. [
  662. 'app' => 'locking',
  663. ]
  664. );
  665. }
  666. try {
  667. $provider->releaseLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
  668. } catch (LockedException $e) {
  669. if ($logger) {
  670. $logger->logException($e);
  671. }
  672. throw $e;
  673. }
  674. }
  675. /**
  676. * @param string $path
  677. * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
  678. * @param \OCP\Lock\ILockingProvider $provider
  679. * @throws \OCP\Lock\LockedException
  680. */
  681. public function changeLock($path, $type, ILockingProvider $provider) {
  682. $logger = $this->getLockLogger();
  683. if ($logger) {
  684. $typeString = ($type === ILockingProvider::LOCK_SHARED) ? 'shared' : 'exclusive';
  685. $logger->info(
  686. sprintf(
  687. 'change lock on "%s" to %s on storage "%s"',
  688. $path,
  689. $typeString,
  690. $this->getId()
  691. ),
  692. [
  693. 'app' => 'locking',
  694. ]
  695. );
  696. }
  697. try {
  698. $provider->changeLock('files/' . md5($this->getId() . '::' . trim($path, '/')), $type);
  699. } catch (LockedException $e) {
  700. if ($logger) {
  701. $logger->logException($e);
  702. }
  703. throw $e;
  704. }
  705. }
  706. private function getLockLogger() {
  707. if (is_null($this->shouldLogLocks)) {
  708. $this->shouldLogLocks = \OC::$server->getConfig()->getSystemValue('filelocking.debug', false);
  709. $this->logger = $this->shouldLogLocks ? \OC::$server->getLogger() : null;
  710. }
  711. return $this->logger;
  712. }
  713. /**
  714. * @return array [ available, last_checked ]
  715. */
  716. public function getAvailability() {
  717. return $this->getStorageCache()->getAvailability();
  718. }
  719. /**
  720. * @param bool $isAvailable
  721. */
  722. public function setAvailability($isAvailable) {
  723. $this->getStorageCache()->setAvailability($isAvailable);
  724. }
  725. /**
  726. * @return bool
  727. */
  728. public function needsPartFile() {
  729. return true;
  730. }
  731. }