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.

590 lines
13 KiB

13 years ago
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. namespace OC\Files\Storage;
  9. require_once 'php-cloudfiles/cloudfiles.php';
  10. class SWIFT extends \OC\Files\Storage\Common{
  11. private $id;
  12. private $host;
  13. private $root;
  14. private $user;
  15. private $token;
  16. private $secure;
  17. private $ready = false;
  18. /**
  19. * @var \CF_Authentication auth
  20. */
  21. private $auth;
  22. /**
  23. * @var \CF_Connection conn
  24. */
  25. private $conn;
  26. /**
  27. * @var \CF_Container rootContainer
  28. */
  29. private $rootContainer;
  30. private static $tempFiles=array();
  31. private $objects=array();
  32. private $containers=array();
  33. const SUBCONTAINER_FILE='.subcontainers';
  34. /**
  35. * translate directory path to container name
  36. * @param string $path
  37. * @return string
  38. */
  39. private function getContainerName($path) {
  40. $path=trim(trim($this->root, '/') . "/".$path, '/.');
  41. return str_replace('/', '\\', $path);
  42. }
  43. /**
  44. * get container by path
  45. * @param string $path
  46. * @return \CF_Container
  47. */
  48. private function getContainer($path) {
  49. if ($path=='' or $path=='/') {
  50. return $this->rootContainer;
  51. }
  52. if (isset($this->containers[$path])) {
  53. return $this->containers[$path];
  54. }
  55. try {
  56. $container=$this->conn->get_container($this->getContainerName($path));
  57. $this->containers[$path]=$container;
  58. return $container;
  59. } catch(\NoSuchContainerException $e) {
  60. return null;
  61. }
  62. }
  63. /**
  64. * create container
  65. * @param string $path
  66. * @return \CF_Container
  67. */
  68. private function createContainer($path) {
  69. if ($path=='' or $path=='/' or $path=='.') {
  70. return $this->conn->create_container($this->getContainerName($path));
  71. }
  72. $parent=dirname($path);
  73. if ($parent=='' or $parent=='/' or $parent=='.') {
  74. $parentContainer=$this->rootContainer;
  75. } else {
  76. if ( ! $this->containerExists($parent)) {
  77. $parentContainer=$this->createContainer($parent);
  78. } else {
  79. $parentContainer=$this->getContainer($parent);
  80. }
  81. }
  82. $this->addSubContainer($parentContainer, basename($path));
  83. return $this->conn->create_container($this->getContainerName($path));
  84. }
  85. /**
  86. * get object by path
  87. * @param string $path
  88. * @return \CF_Object
  89. */
  90. private function getObject($path) {
  91. if (isset($this->objects[$path])) {
  92. return $this->objects[$path];
  93. }
  94. $container=$this->getContainer(dirname($path));
  95. if (is_null($container)) {
  96. return null;
  97. } else {
  98. if ($path=="/" or $path=='') {
  99. return null;
  100. }
  101. try {
  102. $obj=$container->get_object(basename($path));
  103. $this->objects[$path]=$obj;
  104. return $obj;
  105. } catch(\NoSuchObjectException $e) {
  106. return null;
  107. }
  108. }
  109. }
  110. /**
  111. * get the names of all objects in a container
  112. * @param CF_Container
  113. * @return array
  114. */
  115. private function getObjects($container) {
  116. if (is_null($container)) {
  117. return array();
  118. } else {
  119. $files=$container->get_objects();
  120. foreach ($files as &$file) {
  121. $file=$file->name;
  122. }
  123. return $files;
  124. }
  125. }
  126. /**
  127. * create object
  128. * @param string $path
  129. * @return \CF_Object
  130. */
  131. private function createObject($path) {
  132. $container=$this->getContainer(dirname($path));
  133. if ( ! is_null($container)) {
  134. $container=$this->createContainer(dirname($path));
  135. }
  136. return $container->create_object(basename($path));
  137. }
  138. /**
  139. * check if an object exists
  140. * @param string
  141. * @return bool
  142. */
  143. private function objectExists($path) {
  144. return !is_null($this->getObject($path));
  145. }
  146. /**
  147. * check if container for path exists
  148. * @param string $path
  149. * @return bool
  150. */
  151. private function containerExists($path) {
  152. return !is_null($this->getContainer($path));
  153. }
  154. /**
  155. * get the list of emulated sub containers
  156. * @param \CF_Container $container
  157. * @return array
  158. */
  159. private function getSubContainers($container) {
  160. $tmpFile=\OCP\Files::tmpFile();
  161. $obj=$this->getSubContainerFile($container);
  162. try {
  163. $obj->save_to_filename($tmpFile);
  164. } catch(\Exception $e) {
  165. return array();
  166. }
  167. $obj->save_to_filename($tmpFile);
  168. $containers=file($tmpFile);
  169. unlink($tmpFile);
  170. foreach ($containers as &$sub) {
  171. $sub=trim($sub);
  172. }
  173. return $containers;
  174. }
  175. /**
  176. * add an emulated sub container
  177. * @param \CF_Container $container
  178. * @param string $name
  179. * @return bool
  180. */
  181. private function addSubContainer($container, $name) {
  182. if ( ! $name) {
  183. return false;
  184. }
  185. $tmpFile=\OCP\Files::tmpFile();
  186. $obj=$this->getSubContainerFile($container);
  187. try {
  188. $obj->save_to_filename($tmpFile);
  189. $containers=file($tmpFile);
  190. foreach ($containers as &$sub) {
  191. $sub=trim($sub);
  192. }
  193. if(array_search($name, $containers) !== false) {
  194. unlink($tmpFile);
  195. return false;
  196. } else {
  197. $fh=fopen($tmpFile, 'a');
  198. fwrite($fh,$name . "\n");
  199. }
  200. } catch(\Exception $e) {
  201. file_put_contents($tmpFile, $name . "\n");
  202. }
  203. $obj->load_from_filename($tmpFile);
  204. unlink($tmpFile);
  205. return true;
  206. }
  207. /**
  208. * remove an emulated sub container
  209. * @param \CF_Container $container
  210. * @param string $name
  211. * @return bool
  212. */
  213. private function removeSubContainer($container, $name) {
  214. if ( ! $name) {
  215. return false;
  216. }
  217. $tmpFile=\OCP\Files::tmpFile();
  218. $obj=$this->getSubContainerFile($container);
  219. try {
  220. $obj->save_to_filename($tmpFile);
  221. $containers=file($tmpFile);
  222. } catch (\Exception $e) {
  223. return false;
  224. }
  225. foreach ($containers as &$sub) {
  226. $sub=trim($sub);
  227. }
  228. $i=array_search($name, $containers);
  229. if ($i===false) {
  230. unlink($tmpFile);
  231. return false;
  232. } else {
  233. unset($containers[$i]);
  234. file_put_contents($tmpFile, implode("\n", $containers)."\n");
  235. }
  236. $obj->load_from_filename($tmpFile);
  237. unlink($tmpFile);
  238. return true;
  239. }
  240. /**
  241. * ensure a subcontainer file exists and return it's object
  242. * @param \CF_Container $container
  243. * @return \CF_Object
  244. */
  245. private function getSubContainerFile($container) {
  246. try {
  247. return $container->get_object(self::SUBCONTAINER_FILE);
  248. } catch(\NoSuchObjectException $e) {
  249. return $container->create_object(self::SUBCONTAINER_FILE);
  250. }
  251. }
  252. public function __construct($params) {
  253. if (isset($params['token']) && isset($params['host']) && isset($params['user'])) {
  254. $this->token=$params['token'];
  255. $this->host=$params['host'];
  256. $this->user=$params['user'];
  257. $this->root=isset($params['root'])?$params['root']:'/';
  258. if (isset($params['secure'])) {
  259. if (is_string($params['secure'])) {
  260. $this->secure = ($params['secure'] === 'true');
  261. } else {
  262. $this->secure = (bool)$params['secure'];
  263. }
  264. } else {
  265. $this->secure = false;
  266. }
  267. if ( ! $this->root || $this->root[0]!='/') {
  268. $this->root='/'.$this->root;
  269. }
  270. } else {
  271. throw new \Exception();
  272. }
  273. }
  274. private function init(){
  275. if($this->ready){
  276. return;
  277. }
  278. $this->ready = true;
  279. $this->auth = new \CF_Authentication($this->user, $this->token, null, $this->host);
  280. $this->auth->authenticate();
  281. $this->conn = new \CF_Connection($this->auth);
  282. if ( ! $this->containerExists('/')) {
  283. $this->rootContainer=$this->createContainer('/');
  284. } else {
  285. $this->rootContainer=$this->getContainer('/');
  286. }
  287. }
  288. public function getId(){
  289. return $this->id;
  290. }
  291. public function mkdir($path) {
  292. $this->init();
  293. if ($this->containerExists($path)) {
  294. return false;
  295. } else {
  296. $this->createContainer($path);
  297. return true;
  298. }
  299. }
  300. public function rmdir($path) {
  301. $this->init();
  302. if (!$this->containerExists($path)) {
  303. return false;
  304. } else {
  305. $this->emptyContainer($path);
  306. if ($path!='' and $path!='/') {
  307. $parentContainer=$this->getContainer(dirname($path));
  308. $this->removeSubContainer($parentContainer, basename($path));
  309. }
  310. $this->conn->delete_container($this->getContainerName($path));
  311. unset($this->containers[$path]);
  312. return true;
  313. }
  314. }
  315. private function emptyContainer($path) {
  316. $container=$this->getContainer($path);
  317. if (is_null($container)) {
  318. return;
  319. }
  320. $subContainers=$this->getSubContainers($container);
  321. foreach ($subContainers as $sub) {
  322. if ($sub) {
  323. $this->emptyContainer($path.'/'.$sub);
  324. $this->conn->delete_container($this->getContainerName($path.'/'.$sub));
  325. unset($this->containers[$path.'/'.$sub]);
  326. }
  327. }
  328. $objects=$this->getObjects($container);
  329. foreach ($objects as $object) {
  330. $container->delete_object($object);
  331. unset($this->objects[$path.'/'.$object]);
  332. }
  333. }
  334. public function opendir($path) {
  335. $this->init();
  336. $container=$this->getContainer($path);
  337. $files=$this->getObjects($container);
  338. $i=array_search(self::SUBCONTAINER_FILE, $files);
  339. if ($i!==false) {
  340. unset($files[$i]);
  341. }
  342. $subContainers=$this->getSubContainers($container);
  343. $files=array_merge($files, $subContainers);
  344. $id=$this->getContainerName($path);
  345. \OC\Files\Stream\Dir::register($id, $files);
  346. return opendir('fakedir://'.$id);
  347. }
  348. public function filetype($path) {
  349. $this->init();
  350. if ($this->containerExists($path)) {
  351. return 'dir';
  352. } else {
  353. return 'file';
  354. }
  355. }
  356. public function isReadable($path) {
  357. return true;
  358. }
  359. public function isUpdatable($path) {
  360. return true;
  361. }
  362. public function file_exists($path) {
  363. $this->init();
  364. if ($this->is_dir($path)) {
  365. return true;
  366. } else {
  367. return $this->objectExists($path);
  368. }
  369. }
  370. public function file_get_contents($path) {
  371. $this->init();
  372. $obj=$this->getObject($path);
  373. if (is_null($obj)) {
  374. return false;
  375. }
  376. return $obj->read();
  377. }
  378. public function file_put_contents($path, $content) {
  379. $this->init();
  380. $obj=$this->getObject($path);
  381. if (is_null($obj)) {
  382. $container=$this->getContainer(dirname($path));
  383. if (is_null($container)) {
  384. return false;
  385. }
  386. $obj=$container->create_object(basename($path));
  387. }
  388. $this->resetMTime($obj);
  389. return $obj->write($content);
  390. }
  391. public function unlink($path) {
  392. $this->init();
  393. if ($this->containerExists($path)) {
  394. return $this->rmdir($path);
  395. }
  396. if ($this->objectExists($path)) {
  397. $container=$this->getContainer(dirname($path));
  398. $container->delete_object(basename($path));
  399. unset($this->objects[$path]);
  400. } else {
  401. return false;
  402. }
  403. }
  404. public function fopen($path, $mode) {
  405. $this->init();
  406. switch($mode) {
  407. case 'r':
  408. case 'rb':
  409. $obj=$this->getObject($path);
  410. if (is_null($obj)) {
  411. return false;
  412. }
  413. $fp = fopen('php://temp', 'r+');
  414. $obj->stream($fp);
  415. rewind($fp);
  416. return $fp;
  417. case 'w':
  418. case 'wb':
  419. case 'a':
  420. case 'ab':
  421. case 'r+':
  422. case 'w+':
  423. case 'wb+':
  424. case 'a+':
  425. case 'x':
  426. case 'x+':
  427. case 'c':
  428. case 'c+':
  429. $tmpFile=$this->getTmpFile($path);
  430. \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
  431. self::$tempFiles[$tmpFile]=$path;
  432. return fopen('close://'.$tmpFile, $mode);
  433. }
  434. }
  435. public function writeBack($tmpFile) {
  436. if (isset(self::$tempFiles[$tmpFile])) {
  437. $this->fromTmpFile($tmpFile, self::$tempFiles[$tmpFile]);
  438. unlink($tmpFile);
  439. }
  440. }
  441. public function free_space($path) {
  442. return 1024*1024*1024*8;
  443. }
  444. public function touch($path, $mtime=null) {
  445. $this->init();
  446. $obj=$this->getObject($path);
  447. if (is_null($obj)) {
  448. return false;
  449. }
  450. if (is_null($mtime)) {
  451. $mtime=time();
  452. }
  453. //emulate setting mtime with metadata
  454. $obj->metadata['Mtime']=$mtime;
  455. $obj->sync_metadata();
  456. }
  457. public function rename($path1, $path2) {
  458. $this->init();
  459. $sourceContainer=$this->getContainer(dirname($path1));
  460. $targetContainer=$this->getContainer(dirname($path2));
  461. $result=$sourceContainer->move_object_to(basename($path1), $targetContainer, basename($path2));
  462. unset($this->objects[$path1]);
  463. if ($result) {
  464. $targetObj=$this->getObject($path2);
  465. $this->resetMTime($targetObj);
  466. }
  467. return $result;
  468. }
  469. public function copy($path1, $path2) {
  470. $this->init();
  471. $sourceContainer=$this->getContainer(dirname($path1));
  472. $targetContainer=$this->getContainer(dirname($path2));
  473. $result=$sourceContainer->copy_object_to(basename($path1), $targetContainer, basename($path2));
  474. if ($result) {
  475. $targetObj=$this->getObject($path2);
  476. $this->resetMTime($targetObj);
  477. }
  478. return $result;
  479. }
  480. public function stat($path) {
  481. $this->init();
  482. $container=$this->getContainer($path);
  483. if ( ! is_null($container)) {
  484. return array(
  485. 'mtime'=>-1,
  486. 'size'=>$container->bytes_used,
  487. 'ctime'=>-1
  488. );
  489. }
  490. $obj=$this->getObject($path);
  491. if (is_null($obj)) {
  492. return false;
  493. }
  494. if (isset($obj->metadata['Mtime']) and $obj->metadata['Mtime']>-1) {
  495. $mtime=$obj->metadata['Mtime'];
  496. } else {
  497. $mtime=strtotime($obj->last_modified);
  498. }
  499. return array(
  500. 'mtime'=>$mtime,
  501. 'size'=>$obj->content_length,
  502. 'ctime'=>-1,
  503. );
  504. }
  505. private function getTmpFile($path) {
  506. $this->init();
  507. $obj=$this->getObject($path);
  508. if ( ! is_null($obj)) {
  509. $tmpFile=\OCP\Files::tmpFile();
  510. $obj->save_to_filename($tmpFile);
  511. return $tmpFile;
  512. } else {
  513. return \OCP\Files::tmpFile();
  514. }
  515. }
  516. private function fromTmpFile($tmpFile, $path) {
  517. $this->init();
  518. $obj=$this->getObject($path);
  519. if (is_null($obj)) {
  520. $obj=$this->createObject($path);
  521. }
  522. $obj->load_from_filename($tmpFile);
  523. $this->resetMTime($obj);
  524. }
  525. /**
  526. * remove custom mtime metadata
  527. * @param \CF_Object $obj
  528. */
  529. private function resetMTime($obj) {
  530. if (isset($obj->metadata['Mtime'])) {
  531. $obj->metadata['Mtime']=-1;
  532. $obj->sync_metadata();
  533. }
  534. }
  535. }