PageRenderTime 45ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Gaufrette/Adapter/MogileFS.php

https://bitbucket.org/ajalovec/gaufrette
PHP | 357 lines | 218 code | 64 blank | 75 comment | 25 complexity | 06b3ae543cf53506bbc1f42492a35656 MD5 | raw file
  1. <?php
  2. namespace Gaufrette\Adapter;
  3. use Gaufrette\Adapter;
  4. use Gaufrette\Util;
  5. /**
  6. * Adapter for the MogileFS filesystem.
  7. *
  8. * @author Mikko Tarvainen 2011 <mtarvainen@gmail.com>
  9. * @author Antoine Hérault <antoine.herault@gmail.com>
  10. *
  11. * Bases partly on Wikimedia MogileFS client code by Jens Frank and Domas Mituzas, 2007.
  12. * See more: http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/MogileClient/
  13. */
  14. class MogileFS implements Adapter
  15. {
  16. const ERR_OTHER = 0;
  17. const ERR_UNKNOWN_KEY = 1;
  18. const ERR_EMPTY_FILE = 2;
  19. const ERR_NONE_MATCH = 3;
  20. const ERR_KEY_EXISTS = 4;
  21. protected $domain;
  22. protected $hosts;
  23. protected $socket;
  24. /**
  25. * Constructor
  26. *
  27. * @param domain MogileFS domain
  28. * @param hosts Array of MogileFS trackers
  29. */
  30. public function __construct($domain, array $hosts)
  31. {
  32. if (strlen($domain) < 1 || count($hosts) < 1) {
  33. throw new \InvalidArgumentException('Invalid parameters. Given domain is too short or you not given any host.');
  34. }
  35. $this->domain = $domain;
  36. $this->hosts = $hosts;
  37. }
  38. /**
  39. * {@inheritDoc}
  40. */
  41. public function read($key)
  42. {
  43. $paths = $this->getPaths($key);
  44. $data = '';
  45. if ($paths) {
  46. foreach ($paths as $path) {
  47. $fh = fopen($path, 'r');
  48. if (!$fh) {
  49. continue;
  50. }
  51. while (!feof($fh)) {
  52. $data .= fread($fh, 8192);
  53. }
  54. fclose($fh);
  55. }
  56. }
  57. return $data ?: false;
  58. }
  59. /**
  60. * {@inheritDoc}
  61. */
  62. public function write($key, $content, array $metadata = null)
  63. {
  64. $closeres = false;
  65. if (mb_strlen($content) > 0) {
  66. $res = $this->doRequest("CREATE_OPEN", array("key" => $key, "class" => $metadata['mogile_class']));
  67. if ($res && preg_match('/^http:\/\/([a-z0-9.-]*):([0-9]*)\/(.*)$/', $res['path'], $matches)) {
  68. $host = $matches[1];
  69. $port = $matches[2];
  70. $path = $matches[3];
  71. $status = $this->putFile($res['path'], $content);
  72. if ($status) {
  73. $params = array("key" => $key, "class" => $metadata['mogile_class'], "devid" => $res['devid'],
  74. "fid" => $res['fid'], "path" => urldecode($res['path']));
  75. $closeres = $this->doRequest("CREATE_CLOSE", $params);
  76. }
  77. }
  78. }
  79. if (!is_array($closeres)) {
  80. return false;
  81. }
  82. return Util\Size::fromContent($content);
  83. }
  84. /**
  85. * {@inheritDoc}
  86. */
  87. public function delete($key)
  88. {
  89. $this->doRequest('DELETE', array('key' => $key));
  90. return true;
  91. }
  92. /**
  93. * {@inheritDoc}
  94. */
  95. public function rename($sourceKey, $targetKey)
  96. {
  97. $this->doRequest('RENAME', array(
  98. 'from_key' => $sourceKey,
  99. 'to_key' => $targetKey
  100. ));
  101. return true;
  102. }
  103. /**
  104. * {@inheritDoc}
  105. */
  106. public function exists($key)
  107. {
  108. try {
  109. $this->getPaths($key);
  110. } catch (\RuntimeException $e) {
  111. return false;
  112. }
  113. return true;
  114. }
  115. /**
  116. * {@inheritDoc}
  117. */
  118. public function keys()
  119. {
  120. try {
  121. $result = $this->doRequest('LIST_KEYS');
  122. } catch (\RuntimeException $e) {
  123. if (self::ERR_NONE_MATCH === $e->getCode()) {
  124. return array();
  125. }
  126. throw $e;
  127. }
  128. unset($result['key_count'], $result['next_after']);
  129. return array_values($result);
  130. }
  131. /**
  132. * {@inheritDoc}
  133. */
  134. public function mtime($key)
  135. {
  136. return false;
  137. }
  138. /**
  139. * {@inheritDoc}
  140. */
  141. public function isDirectory($key)
  142. {
  143. return false;
  144. }
  145. /**
  146. * Get available domains and classes from tracker
  147. *
  148. * @return mixed Array on success, false on failure
  149. */
  150. public function getDomains()
  151. {
  152. $res = $this->doRequest('GET_DOMAINS');
  153. if (!$res) {
  154. return false;
  155. }
  156. $domains = array();
  157. for ($i = 1; $i <= $res['domains']; $i++) {
  158. $dom = 'domain' . $i;
  159. $classes = array();
  160. // Associate classes to current domain (class name => mindevcount)
  161. for ($j = 1; $j <= $res[$dom.'classes']; $j++) {
  162. $classes[$res[$dom . 'class' . $j . 'name']] = $res[$dom . 'class' . $j . 'mindevcount'];
  163. }
  164. $domains[] = array('name' => $res[$dom], 'classes' => $classes);
  165. }
  166. return $domains;
  167. }
  168. /**
  169. * Tries to connect MogileFS tracker
  170. *
  171. * @return Socket
  172. */
  173. private function connect()
  174. {
  175. if ($this->socket) {
  176. return $this->socket;
  177. }
  178. shuffle($this->hosts);
  179. foreach ($this->hosts as $host) {
  180. list($ip, $port) = explode(':', $host);
  181. $this->socket = @fsockopen($ip, $port, $err_no, $err_str, 1);
  182. if ($this->socket) {
  183. break;
  184. }
  185. }
  186. if (!$this->socket) {
  187. throw new \RuntimeException('Unable to connect to the tracker.');
  188. }
  189. return $this->socket;
  190. }
  191. /**
  192. * Close connection to MogileFS tracker
  193. *
  194. * @return boolean
  195. */
  196. private function close()
  197. {
  198. if ($this->socket) {
  199. return fclose($this->socket);
  200. }
  201. return true;
  202. }
  203. /**
  204. * Makes request to MogileFS tracker
  205. *
  206. * @param cmd Command
  207. * @param args Array of arguments
  208. * @return mixed Array on success, false on failure
  209. */
  210. private function doRequest($cmd, $args = array())
  211. {
  212. clearstatcache();
  213. $args['domain'] = $this->domain;
  214. $params = http_build_query($args);
  215. $this->connect();
  216. fwrite($this->socket, "{$cmd} {$params}\n");
  217. $line = fgets($this->socket);
  218. $words = explode(' ', $line);
  219. if ($words[0] == 'OK') {
  220. parse_str(trim($words[1]), $result);
  221. } else {
  222. $errorName = empty($words[1]) ? null : $words[1];
  223. switch ($errorName) {
  224. case 'unknown_key':
  225. $errorCode = static::ERR_UNKNOWN_KEY;
  226. break;
  227. case 'empty_file':
  228. $errorCode = static::ERR_EMPTY_FILE;
  229. break;
  230. case 'none_match':
  231. $errorCode = static::ERR_NONE_MATCH;
  232. break;
  233. case 'key_exists':
  234. $errorCode = static::ERR_KEY_EXISTS;
  235. break;
  236. default:
  237. $errorCode = static::ERR_OTHER;
  238. }
  239. throw new \RuntimeException(
  240. sprintf('Error response: %s', $line),
  241. $errorCode
  242. );
  243. }
  244. return $result;
  245. }
  246. /**
  247. * Get file location at server from MogileFS tracker
  248. *
  249. * @param key File key
  250. * @return mixed Array on success, false on failure
  251. */
  252. private function getPaths($key)
  253. {
  254. $res = $this->doRequest("GET_PATHS", array("key" => $key));
  255. unset($res['paths']);
  256. return $res;
  257. }
  258. /**
  259. * Sends file to MogileFS tracker
  260. *
  261. * @param path Save path at server
  262. * @param data Data to save
  263. * @return boolean
  264. */
  265. private function putFile($path, $data)
  266. {
  267. $info = false;
  268. $url = parse_url($path);
  269. $fp = fsockopen($url['host'], $url['port'], $errno, $errstr, 5);
  270. if (!$fp) {
  271. return false;
  272. }
  273. $buffer = '';
  274. $b = "\r\n";
  275. stream_set_blocking($fp, true);
  276. stream_set_timeout($fp, 30, 200000);
  277. $out = "PUT ". $url['path']. " HTTP/1.1". $b;
  278. $out .= "Host: ". $url['host']. $b;
  279. $out .= "Content-Length: ". Util\Size::fromContent($data). $b. $b;
  280. $out .= $data;
  281. $out .= $b. $b;
  282. fwrite($fp, $out);
  283. fflush($fp);
  284. stream_set_blocking($fp, true);
  285. stream_set_timeout($fp, 30, 200000);
  286. while (!feof($fp) && !$info['timed_out']) {
  287. $info = stream_get_meta_data($fp);
  288. $buffer .= fgets($fp, 128);
  289. }
  290. fclose($fp);
  291. return true;
  292. }
  293. }