PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/apps/files_external/lib/swift.php

https://gitlab.com/Red54/core
PHP | 582 lines | 411 code | 90 blank | 81 comment | 67 complexity | fe198bac9f501f0190ee1a61a855f045 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 Felix Moeller <mail@felixmoeller.de>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Philipp Kapfer <philipp.kapfer@gmx.at>
  10. * @author Robin Appelman <icewind@owncloud.com>
  11. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  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\Storage;
  32. use Guzzle\Http\Exception\ClientErrorResponseException;
  33. use OpenCloud;
  34. use OpenCloud\Common\Exceptions;
  35. use OpenCloud\OpenStack;
  36. use OpenCloud\Rackspace;
  37. use OpenCloud\ObjectStore\Resource\DataObject;
  38. use OpenCloud\ObjectStore\Exception;
  39. class Swift extends \OC\Files\Storage\Common {
  40. /**
  41. * @var \OpenCloud\ObjectStore\Service
  42. */
  43. private $connection;
  44. /**
  45. * @var \OpenCloud\ObjectStore\Resource\Container
  46. */
  47. private $container;
  48. /**
  49. * @var \OpenCloud\OpenStack
  50. */
  51. private $anchor;
  52. /**
  53. * @var string
  54. */
  55. private $bucket;
  56. /**
  57. * Connection parameters
  58. *
  59. * @var array
  60. */
  61. private $params;
  62. /**
  63. * @var array
  64. */
  65. private static $tmpFiles = array();
  66. /**
  67. * @param string $path
  68. */
  69. private function normalizePath($path) {
  70. $path = trim($path, '/');
  71. if (!$path) {
  72. $path = '.';
  73. }
  74. $path = str_replace('#', '%23', $path);
  75. return $path;
  76. }
  77. const SUBCONTAINER_FILE = '.subcontainers';
  78. /**
  79. * translate directory path to container name
  80. *
  81. * @param string $path
  82. * @return string
  83. */
  84. private function getContainerName($path) {
  85. $path = trim(trim($this->root, '/') . "/" . $path, '/.');
  86. return str_replace('/', '\\', $path);
  87. }
  88. /**
  89. * @param string $path
  90. */
  91. private function doesObjectExist($path) {
  92. try {
  93. $this->getContainer()->getPartialObject($path);
  94. return true;
  95. } catch (ClientErrorResponseException $e) {
  96. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  97. return false;
  98. }
  99. }
  100. public function __construct($params) {
  101. if ((empty($params['key']) and empty($params['password']))
  102. or empty($params['user']) or empty($params['bucket'])
  103. or empty($params['region'])
  104. ) {
  105. throw new \Exception("API Key or password, Username, Bucket and Region have to be configured.");
  106. }
  107. $this->id = 'swift::' . $params['user'] . md5($params['bucket']);
  108. $this->bucket = $params['bucket'];
  109. if (empty($params['url'])) {
  110. $params['url'] = 'https://identity.api.rackspacecloud.com/v2.0/';
  111. }
  112. if (empty($params['service_name'])) {
  113. $params['service_name'] = 'cloudFiles';
  114. }
  115. $this->params = $params;
  116. }
  117. public function mkdir($path) {
  118. $path = $this->normalizePath($path);
  119. if ($this->is_dir($path)) {
  120. return false;
  121. }
  122. if ($path !== '.') {
  123. $path .= '/';
  124. }
  125. try {
  126. $customHeaders = array('content-type' => 'httpd/unix-directory');
  127. $metadataHeaders = DataObject::stockHeaders(array());
  128. $allHeaders = $customHeaders + $metadataHeaders;
  129. $this->getContainer()->uploadObject($path, '', $allHeaders);
  130. } catch (Exceptions\CreateUpdateError $e) {
  131. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  132. return false;
  133. }
  134. return true;
  135. }
  136. public function file_exists($path) {
  137. $path = $this->normalizePath($path);
  138. if ($path !== '.' && $this->is_dir($path)) {
  139. $path .= '/';
  140. }
  141. return $this->doesObjectExist($path);
  142. }
  143. public function rmdir($path) {
  144. $path = $this->normalizePath($path);
  145. if (!$this->is_dir($path) || !$this->isDeletable($path)) {
  146. return false;
  147. }
  148. $dh = $this->opendir($path);
  149. while ($file = readdir($dh)) {
  150. if ($file === '.' || $file === '..') {
  151. continue;
  152. }
  153. if ($this->is_dir($path . '/' . $file)) {
  154. $this->rmdir($path . '/' . $file);
  155. } else {
  156. $this->unlink($path . '/' . $file);
  157. }
  158. }
  159. try {
  160. $this->getContainer()->dataObject()->setName($path . '/')->delete();
  161. } catch (Exceptions\DeleteError $e) {
  162. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  163. return false;
  164. }
  165. return true;
  166. }
  167. public function opendir($path) {
  168. $path = $this->normalizePath($path);
  169. if ($path === '.') {
  170. $path = '';
  171. } else {
  172. $path .= '/';
  173. }
  174. $path = str_replace('%23', '#', $path); // the prefix is sent as a query param, so revert the encoding of #
  175. try {
  176. $files = array();
  177. /** @var OpenCloud\Common\Collection $objects */
  178. $objects = $this->getContainer()->objectList(array(
  179. 'prefix' => $path,
  180. 'delimiter' => '/'
  181. ));
  182. /** @var OpenCloud\ObjectStore\Resource\DataObject $object */
  183. foreach ($objects as $object) {
  184. $file = basename($object->getName());
  185. if ($file !== basename($path)) {
  186. $files[] = $file;
  187. }
  188. }
  189. \OC\Files\Stream\Dir::register('swift' . $path, $files);
  190. return opendir('fakedir://swift' . $path);
  191. } catch (\Exception $e) {
  192. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  193. return false;
  194. }
  195. }
  196. public function stat($path) {
  197. $path = $this->normalizePath($path);
  198. if ($path === '.') {
  199. $path = '';
  200. } else if ($this->is_dir($path)) {
  201. $path .= '/';
  202. }
  203. try {
  204. /** @var DataObject $object */
  205. $object = $this->getContainer()->getPartialObject($path);
  206. } catch (ClientErrorResponseException $e) {
  207. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  208. return false;
  209. }
  210. $dateTime = \DateTime::createFromFormat(\DateTime::RFC1123, $object->getLastModified());
  211. if ($dateTime !== false) {
  212. $mtime = $dateTime->getTimestamp();
  213. } else {
  214. $mtime = null;
  215. }
  216. $objectMetadata = $object->getMetadata();
  217. $metaTimestamp = $objectMetadata->getProperty('timestamp');
  218. if (isset($metaTimestamp)) {
  219. $mtime = $metaTimestamp;
  220. }
  221. if (!empty($mtime)) {
  222. $mtime = floor($mtime);
  223. }
  224. $stat = array();
  225. $stat['size'] = (int)$object->getContentLength();
  226. $stat['mtime'] = $mtime;
  227. $stat['atime'] = time();
  228. return $stat;
  229. }
  230. public function filetype($path) {
  231. $path = $this->normalizePath($path);
  232. if ($path !== '.' && $this->doesObjectExist($path)) {
  233. return 'file';
  234. }
  235. if ($path !== '.') {
  236. $path .= '/';
  237. }
  238. if ($this->doesObjectExist($path)) {
  239. return 'dir';
  240. }
  241. }
  242. public function unlink($path) {
  243. $path = $this->normalizePath($path);
  244. if ($this->is_dir($path)) {
  245. return $this->rmdir($path);
  246. }
  247. try {
  248. $this->getContainer()->dataObject()->setName($path)->delete();
  249. } catch (ClientErrorResponseException $e) {
  250. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  251. return false;
  252. }
  253. return true;
  254. }
  255. public function fopen($path, $mode) {
  256. $path = $this->normalizePath($path);
  257. switch ($mode) {
  258. case 'r':
  259. case 'rb':
  260. $tmpFile = \OC_Helper::tmpFile();
  261. self::$tmpFiles[$tmpFile] = $path;
  262. try {
  263. $object = $this->getContainer()->getObject($path);
  264. } catch (ClientErrorResponseException $e) {
  265. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  266. return false;
  267. } catch (Exception\ObjectNotFoundException $e) {
  268. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  269. return false;
  270. }
  271. try {
  272. $objectContent = $object->getContent();
  273. $objectContent->rewind();
  274. $stream = $objectContent->getStream();
  275. file_put_contents($tmpFile, $stream);
  276. } catch (Exceptions\IOError $e) {
  277. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  278. return false;
  279. }
  280. return fopen($tmpFile, 'r');
  281. case 'w':
  282. case 'wb':
  283. case 'a':
  284. case 'ab':
  285. case 'r+':
  286. case 'w+':
  287. case 'wb+':
  288. case 'a+':
  289. case 'x':
  290. case 'x+':
  291. case 'c':
  292. case 'c+':
  293. if (strrpos($path, '.') !== false) {
  294. $ext = substr($path, strrpos($path, '.'));
  295. } else {
  296. $ext = '';
  297. }
  298. $tmpFile = \OC_Helper::tmpFile($ext);
  299. \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
  300. if ($this->file_exists($path)) {
  301. $source = $this->fopen($path, 'r');
  302. file_put_contents($tmpFile, $source);
  303. }
  304. self::$tmpFiles[$tmpFile] = $path;
  305. return fopen('close://' . $tmpFile, $mode);
  306. }
  307. }
  308. public function getMimeType($path) {
  309. $path = $this->normalizePath($path);
  310. if ($this->is_dir($path)) {
  311. return 'httpd/unix-directory';
  312. } else if ($this->file_exists($path)) {
  313. $object = $this->getContainer()->getPartialObject($path);
  314. return $object->getContentType();
  315. }
  316. return false;
  317. }
  318. public function touch($path, $mtime = null) {
  319. $path = $this->normalizePath($path);
  320. if (is_null($mtime)) {
  321. $mtime = time();
  322. }
  323. $metadata = array('timestamp' => $mtime);
  324. if ($this->file_exists($path)) {
  325. if ($this->is_dir($path) && $path != '.') {
  326. $path .= '/';
  327. }
  328. $object = $this->getContainer()->getPartialObject($path);
  329. $object->saveMetadata($metadata);
  330. return true;
  331. } else {
  332. $mimeType = \OC_Helper::getMimetypeDetector()->detectPath($path);
  333. $customHeaders = array('content-type' => $mimeType);
  334. $metadataHeaders = DataObject::stockHeaders($metadata);
  335. $allHeaders = $customHeaders + $metadataHeaders;
  336. $this->getContainer()->uploadObject($path, '', $allHeaders);
  337. return true;
  338. }
  339. }
  340. public function copy($path1, $path2) {
  341. $path1 = $this->normalizePath($path1);
  342. $path2 = $this->normalizePath($path2);
  343. $fileType = $this->filetype($path1);
  344. if ($fileType === 'file') {
  345. // make way
  346. $this->unlink($path2);
  347. try {
  348. $source = $this->getContainer()->getPartialObject($path1);
  349. $source->copy($this->bucket . '/' . $path2);
  350. } catch (ClientErrorResponseException $e) {
  351. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  352. return false;
  353. }
  354. } else if ($fileType === 'dir') {
  355. // make way
  356. $this->unlink($path2);
  357. try {
  358. $source = $this->getContainer()->getPartialObject($path1 . '/');
  359. $source->copy($this->bucket . '/' . $path2 . '/');
  360. } catch (ClientErrorResponseException $e) {
  361. \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR);
  362. return false;
  363. }
  364. $dh = $this->opendir($path1);
  365. while ($file = readdir($dh)) {
  366. if ($file === '.' || $file === '..') {
  367. continue;
  368. }
  369. $source = $path1 . '/' . $file;
  370. $target = $path2 . '/' . $file;
  371. $this->copy($source, $target);
  372. }
  373. } else {
  374. //file does not exist
  375. return false;
  376. }
  377. return true;
  378. }
  379. public function rename($path1, $path2) {
  380. $path1 = $this->normalizePath($path1);
  381. $path2 = $this->normalizePath($path2);
  382. $fileType = $this->filetype($path1);
  383. if ($fileType === 'dir' || $fileType === 'file') {
  384. // make way
  385. $this->unlink($path2);
  386. // copy
  387. if ($this->copy($path1, $path2) === false) {
  388. return false;
  389. }
  390. // cleanup
  391. if ($this->unlink($path1) === false) {
  392. $this->unlink($path2);
  393. return false;
  394. }
  395. return true;
  396. }
  397. return false;
  398. }
  399. public function getId() {
  400. return $this->id;
  401. }
  402. /**
  403. * Returns the connection
  404. *
  405. * @return OpenCloud\ObjectStore\Service connected client
  406. * @throws \Exception if connection could not be made
  407. */
  408. public function getConnection() {
  409. if (!is_null($this->connection)) {
  410. return $this->connection;
  411. }
  412. $settings = array(
  413. 'username' => $this->params['user'],
  414. );
  415. if (!empty($this->params['password'])) {
  416. $settings['password'] = $this->params['password'];
  417. } else if (!empty($this->params['key'])) {
  418. $settings['apiKey'] = $this->params['key'];
  419. }
  420. if (!empty($this->params['tenant'])) {
  421. $settings['tenantName'] = $this->params['tenant'];
  422. }
  423. if (!empty($this->params['timeout'])) {
  424. $settings['timeout'] = $this->params['timeout'];
  425. }
  426. if (isset($settings['apiKey'])) {
  427. $this->anchor = new Rackspace($this->params['url'], $settings);
  428. } else {
  429. $this->anchor = new OpenStack($this->params['url'], $settings);
  430. }
  431. $this->connection = $this->anchor->objectStoreService($this->params['service_name'], $this->params['region']);
  432. return $this->connection;
  433. }
  434. /**
  435. * Returns the initialized object store container.
  436. *
  437. * @return OpenCloud\ObjectStore\Resource\Container
  438. */
  439. public function getContainer() {
  440. if (!is_null($this->container)) {
  441. return $this->container;
  442. }
  443. try {
  444. $this->container = $this->getConnection()->getContainer($this->bucket);
  445. } catch (ClientErrorResponseException $e) {
  446. $this->container = $this->getConnection()->createContainer($this->bucket);
  447. }
  448. if (!$this->file_exists('.')) {
  449. $this->mkdir('.');
  450. }
  451. return $this->container;
  452. }
  453. public function writeBack($tmpFile) {
  454. if (!isset(self::$tmpFiles[$tmpFile])) {
  455. return false;
  456. }
  457. $fileData = fopen($tmpFile, 'r');
  458. $this->getContainer()->uploadObject(self::$tmpFiles[$tmpFile], $fileData);
  459. unlink($tmpFile);
  460. }
  461. public function hasUpdated($path, $time) {
  462. if ($this->is_file($path)) {
  463. return parent::hasUpdated($path, $time);
  464. }
  465. $path = $this->normalizePath($path);
  466. $dh = $this->opendir($path);
  467. $content = array();
  468. while (($file = readdir($dh)) !== false) {
  469. $content[] = $file;
  470. }
  471. if ($path === '.') {
  472. $path = '';
  473. }
  474. $cachedContent = $this->getCache()->getFolderContents($path);
  475. $cachedNames = array_map(function ($content) {
  476. return $content['name'];
  477. }, $cachedContent);
  478. sort($cachedNames);
  479. sort($content);
  480. return $cachedNames != $content;
  481. }
  482. /**
  483. * check if curl is installed
  484. */
  485. public static function checkDependencies() {
  486. return true;
  487. }
  488. }