PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/apps/files_external/lib/storage/swift.php

https://gitlab.com/wuhang2003/core
PHP | 599 lines | 418 code | 94 blank | 87 comment | 72 complexity | cd6f32407df6a48279e62b3697d4c659 MD5 | raw file
  1. <?php
  2. /**
  3. * @author Bart Visscher <bartv@thisnet.nl>
  4. * @author Benjamin Liles <benliles@arch.tamu.edu>
  5. * @author Christian Berendt <berendt@b1-systems.de>
  6. * @author Daniel Tosello <tosello.daniel@gmail.com>
  7. * @author Felix Moeller <mail@felixmoeller.de>
  8. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  9. * @author Martin Mattel <martin.mattel@diemattels.at>
  10. * @author Morris Jobke <hey@morrisjobke.de>
  11. * @author Philipp Kapfer <philipp.kapfer@gmx.at>
  12. * @author Robin Appelman <icewind@owncloud.com>
  13. * @author Robin McCorkell <robin@mccorkell.me.uk>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Tim Dettrick <t.dettrick@uq.edu.au>
  16. * @author Vincent Petry <pvince81@owncloud.com>
  17. *
  18. * @copyright Copyright (c) 2016, ownCloud, Inc.
  19. * @license AGPL-3.0
  20. *
  21. * This code is free software: you can redistribute it and/or modify
  22. * it under the terms of the GNU Affero General Public License, version 3,
  23. * as published by the Free Software Foundation.
  24. *
  25. * This program is distributed in the hope that it will be useful,
  26. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  28. * GNU Affero General Public License for more details.
  29. *
  30. * You should have received a copy of the GNU Affero General Public License, version 3,
  31. * along with this program. If not, see <http://www.gnu.org/licenses/>
  32. *
  33. */
  34. namespace OCA\Files_External\Lib\Storage;
  35. use Guzzle\Http\Url;
  36. use Guzzle\Http\Exception\ClientErrorResponseException;
  37. use Icewind\Streams\IteratorDirectory;
  38. use OpenCloud;
  39. use OpenCloud\Common\Exceptions;
  40. use OpenCloud\OpenStack;
  41. use OpenCloud\Rackspace;
  42. use OpenCloud\ObjectStore\Resource\DataObject;
  43. use OpenCloud\ObjectStore\Exception;
  44. class Swift extends \OC\Files\Storage\Common {
  45. /**
  46. * @var \OpenCloud\ObjectStore\Service
  47. */
  48. private $connection;
  49. /**
  50. * @var \OpenCloud\ObjectStore\Resource\Container
  51. */
  52. private $container;
  53. /**
  54. * @var \OpenCloud\OpenStack
  55. */
  56. private $anchor;
  57. /**
  58. * @var string
  59. */
  60. private $bucket;
  61. /**
  62. * Connection parameters
  63. *
  64. * @var array
  65. */
  66. private $params;
  67. /**
  68. * @var array
  69. */
  70. private static $tmpFiles = array();
  71. /**
  72. * @param string $path
  73. */
  74. private function normalizePath($path) {
  75. $path = trim($path, '/');
  76. if (!$path) {
  77. $path = '.';
  78. }
  79. $path = str_replace('#', '%23', $path);
  80. return $path;
  81. }
  82. const SUBCONTAINER_FILE = '.subcontainers';
  83. /**
  84. * translate directory path to container name
  85. *
  86. * @param string $path
  87. * @return string
  88. */
  89. private function getContainerName($path) {
  90. $path = trim(trim($this->root, '/') . "/" . $path, '/.');
  91. return str_replace('/', '\\', $path);
  92. }
  93. /**
  94. * @param string $path
  95. */
  96. private function doesObjectExist($path) {
  97. try {
  98. $this->getContainer()->getPartialObject($path);
  99. return true;
  100. } catch (ClientErrorResponseException $e) {
  101. // Expected response is "404 Not Found", so only log if it isn't
  102. if ($e->getResponse()->getStatusCode() !== 404) {
  103. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  104. }
  105. return false;
  106. }
  107. }
  108. public function __construct($params) {
  109. if ((empty($params['key']) and empty($params['password']))
  110. or empty($params['user']) or empty($params['bucket'])
  111. or empty($params['region'])
  112. ) {
  113. throw new \Exception("API Key or password, Username, Bucket and Region have to be configured.");
  114. }
  115. $this->id = 'swift::' . $params['user'] . md5($params['bucket']);
  116. $bucketUrl = Url::factory($params['bucket']);
  117. if ($bucketUrl->isAbsolute()) {
  118. $this->bucket = end(($bucketUrl->getPathSegments()));
  119. $params['endpoint_url'] = $bucketUrl->addPath('..')->normalizePath();
  120. } else {
  121. $this->bucket = $params['bucket'];
  122. }
  123. if (empty($params['url'])) {
  124. $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/';
  125. }
  126. if (empty($params['service_name'])) {
  127. $params['service_name'] = 'cloudFiles';
  128. }
  129. $this->params = $params;
  130. }
  131. public function mkdir($path) {
  132. $path = $this->normalizePath($path);
  133. if ($this->is_dir($path)) {
  134. return false;
  135. }
  136. if ($path !== '.') {
  137. $path .= '/';
  138. }
  139. try {
  140. $customHeaders = array('content-type' => 'httpd/unix-directory');
  141. $metadataHeaders = DataObject::stockHeaders(array());
  142. $allHeaders = $customHeaders + $metadataHeaders;
  143. $this->getContainer()->uploadObject($path, '', $allHeaders);
  144. } catch (Exceptions\CreateUpdateError $e) {
  145. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  146. return false;
  147. }
  148. return true;
  149. }
  150. public function file_exists($path) {
  151. $path = $this->normalizePath($path);
  152. if ($path !== '.' && $this->is_dir($path)) {
  153. $path .= '/';
  154. }
  155. return $this->doesObjectExist($path);
  156. }
  157. public function rmdir($path) {
  158. $path = $this->normalizePath($path);
  159. if (!$this->is_dir($path) || !$this->isDeletable($path)) {
  160. return false;
  161. }
  162. $dh = $this->opendir($path);
  163. while ($file = readdir($dh)) {
  164. if (\OC\Files\Filesystem::isIgnoredDir($file)) {
  165. continue;
  166. }
  167. if ($this->is_dir($path . '/' . $file)) {
  168. $this->rmdir($path . '/' . $file);
  169. } else {
  170. $this->unlink($path . '/' . $file);
  171. }
  172. }
  173. try {
  174. $this->getContainer()->dataObject()->setName($path . '/')->delete();
  175. } catch (Exceptions\DeleteError $e) {
  176. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  177. return false;
  178. }
  179. return true;
  180. }
  181. public function opendir($path) {
  182. $path = $this->normalizePath($path);
  183. if ($path === '.') {
  184. $path = '';
  185. } else {
  186. $path .= '/';
  187. }
  188. $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of #
  189. try {
  190. $files = array();
  191. /** @var OpenCloud\Common\Collection $objects */
  192. $objects = $this->getContainer()->objectList(array(
  193. 'prefix' => $path,
  194. 'delimiter' => '/'
  195. ));
  196. /** @var OpenCloud\ObjectStore\Resource\DataObject $object */
  197. foreach ($objects as $object) {
  198. $file = basename($object->getName());
  199. if ($file !== basename($path)) {
  200. $files[] = $file;
  201. }
  202. }
  203. return IteratorDirectory::wrap($files);
  204. } catch (\Exception $e) {
  205. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  206. return false;
  207. }
  208. }
  209. public function stat($path) {
  210. $path = $this->normalizePath($path);
  211. if ($path === '.') {
  212. $path = '';
  213. } else if ($this->is_dir($path)) {
  214. $path .= '/';
  215. }
  216. try {
  217. /** @var DataObject $object */
  218. $object = $this->getContainer()->getPartialObject($path);
  219. } catch (ClientErrorResponseException $e) {
  220. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  221. return false;
  222. }
  223. $dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->getLastModified());
  224. if ($dateTime !== false) {
  225. $mtime = $dateTime->getTimestamp();
  226. } else {
  227. $mtime = null;
  228. }
  229. $objectMetadata = $object->getMetadata();
  230. $metaTimestamp = $objectMetadata->getProperty('timestamp');
  231. if (isset($metaTimestamp)) {
  232. $mtime = $metaTimestamp;
  233. }
  234. if (!empty($mtime)) {
  235. $mtime = floor($mtime);
  236. }
  237. $stat = array();
  238. $stat['size'] = (int)$object->getContentLength();
  239. $stat['mtime'] = $mtime;
  240. $stat['atime'] = time();
  241. return $stat;
  242. }
  243. public function filetype($path) {
  244. $path = $this->normalizePath($path);
  245. if ($path !== '.' && $this->doesObjectExist($path)) {
  246. return 'file';
  247. }
  248. if ($path !== '.') {
  249. $path .= '/';
  250. }
  251. if ($this->doesObjectExist($path)) {
  252. return 'dir';
  253. }
  254. }
  255. public function unlink($path) {
  256. $path = $this->normalizePath($path);
  257. if ($this->is_dir($path)) {
  258. return $this->rmdir($path);
  259. }
  260. try {
  261. $this->getContainer()->dataObject()->setName($path)->delete();
  262. } catch (ClientErrorResponseException $e) {
  263. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  264. return false;
  265. }
  266. return true;
  267. }
  268. public function fopen($path, $mode) {
  269. $path = $this->normalizePath($path);
  270. switch ($mode) {
  271. case 'r':
  272. case 'rb':
  273. try {
  274. $c = $this->getContainer();
  275. $streamFactory = new \Guzzle\Stream\PhpStreamRequestFactory();
  276. $streamInterface = $streamFactory->fromRequest(
  277. $c->getClient()
  278. ->get($c->getUrl($path)));
  279. $streamInterface->rewind();
  280. $stream = $streamInterface->getStream();
  281. stream_context_set_option($stream, 'swift','content', $streamInterface);
  282. if(!strrpos($streamInterface
  283. ->getMetaData('wrapper_data')[0], '404 Not Found')) {
  284. return $stream;
  285. }
  286. return false;
  287. } catch (\Guzzle\Http\Exception\BadResponseException $e) {
  288. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  289. return false;
  290. }
  291. case 'w':
  292. case 'wb':
  293. case 'a':
  294. case 'ab':
  295. case 'r+':
  296. case 'w+':
  297. case 'wb+':
  298. case 'a+':
  299. case 'x':
  300. case 'x+':
  301. case 'c':
  302. case 'c+':
  303. if (strrpos($path, '.') !== false) {
  304. $ext = substr($path, strrpos($path, '.'));
  305. } else {
  306. $ext = '';
  307. }
  308. $tmpFile = \OCP\Files::tmpFile($ext);
  309. \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
  310. // Fetch existing file if required
  311. if ($mode[0] !== 'w' && $this->file_exists($path)) {
  312. if ($mode[0] === 'x') {
  313. // File cannot already exist
  314. return false;
  315. }
  316. $source = $this->fopen($path, 'r');
  317. file_put_contents($tmpFile, $source);
  318. // Seek to end if required
  319. if ($mode[0] === 'a') {
  320. fseek($tmpFile, 0, SEEK_END);
  321. }
  322. }
  323. self::$tmpFiles[$tmpFile] = $path;
  324. return fopen('close://' . $tmpFile, $mode);
  325. }
  326. }
  327. public function touch($path, $mtime = null) {
  328. $path = $this->normalizePath($path);
  329. if (is_null($mtime)) {
  330. $mtime = time();
  331. }
  332. $metadata = array('timestamp' => $mtime);
  333. if ($this->file_exists($path)) {
  334. if ($this->is_dir($path) && $path != '.') {
  335. $path .= '/';
  336. }
  337. $object = $this->getContainer()->getPartialObject($path);
  338. $object->saveMetadata($metadata);
  339. return true;
  340. } else {
  341. $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path);
  342. $customHeaders = array('content-type' => $mimeType);
  343. $metadataHeaders = DataObject::stockHeaders($metadata);
  344. $allHeaders = $customHeaders + $metadataHeaders;
  345. $this->getContainer()->uploadObject($path, '', $allHeaders);
  346. return true;
  347. }
  348. }
  349. public function copy($path1, $path2) {
  350. $path1 = $this->normalizePath($path1);
  351. $path2 = $this->normalizePath($path2);
  352. $fileType = $this->filetype($path1);
  353. if ($fileType === 'file') {
  354. // make way
  355. $this->unlink($path2);
  356. try {
  357. $source = $this->getContainer()->getPartialObject($path1);
  358. $source->copy($this->bucket . '/' . $path2);
  359. } catch (ClientErrorResponseException $e) {
  360. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  361. return false;
  362. }
  363. } else if ($fileType === 'dir') {
  364. // make way
  365. $this->unlink($path2);
  366. try {
  367. $source = $this->getContainer()->getPartialObject($path1 . '/');
  368. $source->copy($this->bucket . '/' . $path2 . '/');
  369. } catch (ClientErrorResponseException $e) {
  370. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  371. return false;
  372. }
  373. $dh = $this->opendir($path1);
  374. while ($file = readdir($dh)) {
  375. if (\OC\Files\Filesystem::isIgnoredDir($file)) {
  376. continue;
  377. }
  378. $source = $path1 . '/' . $file;
  379. $target = $path2 . '/' . $file;
  380. $this->copy($source, $target);
  381. }
  382. } else {
  383. //file does not exist
  384. return false;
  385. }
  386. return true;
  387. }
  388. public function rename($path1, $path2) {
  389. $path1 = $this->normalizePath($path1);
  390. $path2 = $this->normalizePath($path2);
  391. $fileType = $this->filetype($path1);
  392. if ($fileType === 'dir' || $fileType === 'file') {
  393. // make way
  394. $this->unlink($path2);
  395. // copy
  396. if ($this->copy($path1, $path2) === false) {
  397. return false;
  398. }
  399. // cleanup
  400. if ($this->unlink($path1) === false) {
  401. $this->unlink($path2);
  402. return false;
  403. }
  404. return true;
  405. }
  406. return false;
  407. }
  408. public function getId() {
  409. return $this->id;
  410. }
  411. /**
  412. * Returns the connection
  413. *
  414. * @return OpenCloud\ObjectStore\Service connected client
  415. * @throws \Exception if connection could not be made
  416. */
  417. public function getConnection() {
  418. if (!is_null($this->connection)) {
  419. return $this->connection;
  420. }
  421. $settings = array(
  422. 'username' => $this->params['user'],
  423. );
  424. if (!empty($this->params['password'])) {
  425. $settings['password'] = $this->params['password'];
  426. } else if (!empty($this->params['key'])) {
  427. $settings['apiKey'] = $this->params['key'];
  428. }
  429. if (!empty($this->params['tenant'])) {
  430. $settings['tenantName'] = $this->params['tenant'];
  431. }
  432. if (!empty($this->params['timeout'])) {
  433. $settings['timeout'] = $this->params['timeout'];
  434. }
  435. if (isset($settings['apiKey'])) {
  436. $this->anchor = new Rackspace($this->params['url'], $settings);
  437. } else {
  438. $this->anchor = new OpenStack($this->params['url'], $settings);
  439. }
  440. $connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']);
  441. if (!empty($this->params['endpoint_url'])) {
  442. $endpoint = $connection->getEndpoint();
  443. $endpoint->setPublicUrl($this->params['endpoint_url']);
  444. $endpoint->setPrivateUrl($this->params['endpoint_url']);
  445. $connection->setEndpoint($endpoint);
  446. }
  447. $this->connection = $connection;
  448. return $this->connection;
  449. }
  450. /**
  451. * Returns the initialized object store container.
  452. *
  453. * @return OpenCloud\ObjectStore\Resource\Container
  454. */
  455. public function getContainer() {
  456. if (!is_null($this->container)) {
  457. return $this->container;
  458. }
  459. try {
  460. $this->container = $this->getConnection()->getContainer($this->bucket);
  461. } catch (ClientErrorResponseException $e) {
  462. $this->container = $this->getConnection()->createContainer($this->bucket);
  463. }
  464. if (!$this->file_exists('.')) {
  465. $this->mkdir('.');
  466. }
  467. return $this->container;
  468. }
  469. public function writeBack($tmpFile) {
  470. if (!isset(self::$tmpFiles[$tmpFile])) {
  471. return false;
  472. }
  473. $fileData = fopen($tmpFile, 'r');
  474. $this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData);
  475. unlink($tmpFile);
  476. }
  477. public function hasUpdated($path, $time) {
  478. if ($this->is_file($path)) {
  479. return parent::hasUpdated($path, $time);
  480. }
  481. $path = $this->normalizePath($path);
  482. $dh = $this->opendir($path);
  483. $content = array();
  484. while (($file = readdir($dh)) !== false) {
  485. $content[] = $file;
  486. }
  487. if ($path === '.') {
  488. $path = '';
  489. }
  490. $cachedContent = $this->getCache()->getFolderContents($path);
  491. $cachedNames = array_map(function ($content) {
  492. return $content['name'];
  493. }, $cachedContent);
  494. sort($cachedNames);
  495. sort($content);
  496. return $cachedNames != $content;
  497. }
  498. /**
  499. * check if curl is installed
  500. */
  501. public static function checkDependencies() {
  502. return true;
  503. }
  504. }