PageRenderTime 25ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/library/filerepository/backend/FileRepository_Backend_FileSystem.class.php

https://github.com/cj/Project-Pier
PHP | 443 lines | 204 code | 63 blank | 176 comment | 49 complexity | 5bc1826037a738772afc9c1b67f9d01a MD5 | raw file
  1. <?php
  2. /**
  3. * File repository backend that stores file in destination folder on file system
  4. *
  5. * @package FileRepository.backend
  6. * @http://www.projectpier.org/
  7. */
  8. class FileRepository_Backend_FileSystem implements FileRepository_Backend {
  9. /**
  10. * Path to repository directory in the file system
  11. *
  12. * @var string
  13. */
  14. private $repository_dir;
  15. /**
  16. * Array of attributes indexed by file ID
  17. *
  18. * @var array
  19. */
  20. private $attributes = null;
  21. /**
  22. * Construct the FileRepository_Backend_FileSystem
  23. *
  24. * @param string $repository_dir Path to the file system repository
  25. * @return FileRepository_Backend_FileSystem
  26. */
  27. function __construct($repository_dir) {
  28. $this->setRepositoryDir($repository_dir);
  29. $this->loadFileAttributes();
  30. } // __construct
  31. // ---------------------------------------------------
  32. // Backend implementation
  33. // ---------------------------------------------------
  34. /**
  35. * Return array of all files in repository
  36. *
  37. * @param void
  38. * @return null
  39. */
  40. function listFiles() {
  41. return array_keys($this->attributes);
  42. } // listFiles
  43. /**
  44. * Return number of files in repository
  45. *
  46. * @param void
  47. * @return integer
  48. */
  49. function countFiles() {
  50. return count($this->attributes);
  51. } // countFiles
  52. /**
  53. * Read the content of the file and return it
  54. *
  55. * @param string $file_id
  56. * @return string
  57. */
  58. function getFileContent($file_id) {
  59. if (!$this->isInRepository($file_id)) {
  60. throw new FileNotInRepositoryError($file_id);
  61. } // if
  62. $file_path = $this->getFilePath($file_id);
  63. if (!is_file($file_path) || !is_readable($file_path)) {
  64. throw new FileNotInRepositoryError($file_id);
  65. } // if
  66. return file_get_contents($file_path);
  67. } // getFileContent
  68. /**
  69. * Return all file attributes for specific file. If file has no attributes empty array is
  70. * returned
  71. *
  72. * @param string $file_id
  73. * @return array
  74. * @throws FileNotInRepositoryError
  75. */
  76. function getFileAttributes($file_id) {
  77. if (!$this->isInRepository($file_id)) {
  78. throw new FileNotInRepositoryError($file_id);
  79. } // if
  80. return is_array($this->attributes[$file_id]) ? $this->attributes[$file_id] : array();
  81. } // getFileAttributes
  82. /**
  83. * Return value of specific file attribute
  84. *
  85. * @param string $file_id
  86. * @param string $attribute_name
  87. * @param mixed $default Default value is returned when attribute is not found
  88. * @return mixed
  89. * @throws FileNotInRepositoryError if file is not in repository
  90. */
  91. function getFileAttribute($file_id, $attribute_name, $default = null) {
  92. if (!$this->isInRepository($file_id)) {
  93. throw new FileNotInRepositoryError($file_id);
  94. } // if
  95. if (isset($this->attributes[$file_id]) && is_array($this->attributes[$file_id]) && isset($this->attributes[$file_id][$attribute_name])) {
  96. return $this->attributes[$file_id][$attribute_name];
  97. } // if
  98. return $default;
  99. } // getFileAttribute
  100. /**
  101. * Set attribute value for specific file
  102. *
  103. * @param string $file_id
  104. * @param string $attribute_name
  105. * @param mixed $attribute_value Objects and resources are not supported. Scalars and arrays are
  106. * @return null
  107. * @throws FileNotInRepositoryError If $file_id does not exists in repository
  108. * @throws InvalidParamError If we have an object or a resource as attribute value
  109. */
  110. function setFileAttribute($file_id, $attribute_name, $attribute_value) {
  111. if (!$this->isInRepository($file_id)) {
  112. throw new FileNotInRepositoryError($file_id);
  113. } // if
  114. if (!isset($this->attributes[$file_id]) || !is_array($this->attributes[$file_id])) {
  115. $this->attributes[$file_id] = array();
  116. } // if
  117. if (is_object($attribute_value) || is_resource($attribute_value)) {
  118. throw new InvalidParamError('$attribute_value', $attribute_value, 'Objects and resources are not supported as attribute values');
  119. } // if
  120. if (!isset($this->attributes[$file_id][$attribute_name]) || ($this->attributes[$file_id][$attribute_name] <> $attribute_value)) {
  121. $this->attributes[$file_id][$attribute_name] = $attribute_value;
  122. $this->saveFileAttributes();
  123. } // if
  124. } // setFileAttribute
  125. /**
  126. * Add file to the repository
  127. *
  128. * @param string $source Path of the source file
  129. * @param array $attributes Array of file attributes
  130. * @return string File ID
  131. * @throws FileDnxError if source is not readable
  132. * @throws FailedToCreateFolderError if we fail to create subdirectory
  133. * @throws FileRepositoryAddError if we fail to move file to the repository
  134. */
  135. function addFile($source, $attributes = null) {
  136. if (!is_readable($source)) {
  137. throw new FileDnxError($source);
  138. } // if
  139. $file_id = $this->getUniqueId();
  140. $file_path = $this->getFilePath($file_id);
  141. $destination_dir = dirname($file_path);
  142. if (!is_dir($destination_dir)) {
  143. if (!force_mkdir($destination_dir, 0777)) {
  144. throw new FailedToCreateFolderError($destination_dir);
  145. } // if
  146. } // if
  147. if (!copy($source, $file_path)) {
  148. throw new FileRepositoryAddError($source, $file_id);
  149. } // if
  150. $this->attributes[$file_id] = true; // register file
  151. if (is_array($attributes)) {
  152. foreach ($attributes as $attribute_name => $attribute_value) {
  153. $this->setFileAttribute($file_id, $attribute_name, $attribute_value);
  154. } // foreach
  155. } // if
  156. return $file_id;
  157. } // addFile
  158. /**
  159. * Update content of specific file
  160. *
  161. * @param string $file_id
  162. * @param string $source
  163. * @return boolean
  164. * @throws FileDnxError if source file is not readable
  165. * @throws FileNotInRepositoryError if $file_id is not in the repository
  166. * @throws FileRepositoryAddError if we fail to update file
  167. */
  168. function updateFileContent($file_id, $source) {
  169. if (!is_readable($source)) {
  170. throw new FileDnxError($source);
  171. } // if
  172. if (!$this->isInRepository($file_id)) {
  173. throw new FileNotInRepositoryError($file_id);
  174. } // if
  175. $file_path = $this->getFilePath($file_id);
  176. if (!copy($source, $file_path)) {
  177. throw new FileRepositoryAddError($source, $file_id);
  178. } // if
  179. return true;
  180. } // updateFileContent
  181. /**
  182. * Delete file from the repository
  183. *
  184. * @param string $file_id
  185. * @return boolean
  186. * @throws FileNotInRepositoryError if $file_id is not in the repository
  187. * @throws FileRepositoryDeleteError if we fail to delete file
  188. */
  189. function deleteFile($file_id) {
  190. if (!$this->isInRepository($file_id)) {
  191. throw new FileNotInRepositoryError($file_id);
  192. } // if
  193. $file_path = $this->getFilePath($file_id);
  194. if (!unlink($file_path)) {
  195. throw new FileRepositoryDeleteError($file_id);
  196. } // if
  197. if (isset($this->attributes[$file_id])) {
  198. unset($this->attributes[$file_id]);
  199. $this->saveFileAttributes();
  200. } // if
  201. $this->cleanUpDir($file_id);
  202. return true;
  203. } // deleteFile
  204. /**
  205. * Drop all files from repository
  206. *
  207. * @param void
  208. * @return null
  209. */
  210. function cleanUp() {
  211. $dir = dir($this->getRepositoryDir());
  212. if ($dir) {
  213. while (false !== ($entry = $dir->read())) {
  214. if (str_starts_with($entry, '.')) continue; // '.', '..' and hidden files ('.svn' for instance)
  215. $path = with_slash($this->getRepositoryDir()) . $entry;
  216. if (is_dir($path)) {
  217. delete_dir($path);
  218. } elseif (is_file($path)) {
  219. unlink($path);
  220. } // if
  221. } // while
  222. } // if
  223. } // cleanUp
  224. /**
  225. * Check if specific file is in repository
  226. *
  227. * @param string $file_id
  228. * @return boolean
  229. */
  230. function isInRepository($file_id) {
  231. return isset($this->attributes[$file_id]) && is_file($this->getFilePath($file_id));
  232. } // isInRepository
  233. // ---------------------------------------------------
  234. // Utils
  235. // ---------------------------------------------------
  236. /**
  237. * Return file path by file_id. This function does not check if file really
  238. * exists in repository, it just creates and returns the path
  239. *
  240. * @param string $file_id
  241. * @return string
  242. */
  243. private function getFilePath($file_id) {
  244. return with_slash($this->getRepositoryDir()) . $this->idToPath($file_id);
  245. } // getFilePath
  246. /**
  247. * This function will clean up the file dir after the file was deleted
  248. *
  249. * @param string $file_id
  250. * @return null
  251. */
  252. private function cleanUpDir($file_id) {
  253. $path = $this->idToPath($file_id);
  254. if (!$path) return;
  255. $path_parts = explode('/', $path);
  256. $repository_path = with_slash($this->getRepositoryDir());
  257. $for_cleaning = array(
  258. $repository_path . $path_parts[0] . '/' . $path_parts[1] . '/' . $path_parts[2],
  259. $repository_path . $path_parts[0] . '/' . $path_parts[1],
  260. $repository_path . $path_parts[0],
  261. ); // array
  262. foreach ($for_cleaning as $dir) {
  263. if (is_dir_empty($dir)) {
  264. delete_dir($dir);
  265. } else {
  266. return; // break, not empty
  267. } // if
  268. } // foreach
  269. } // cleanUpDir
  270. /**
  271. * Convert file ID to repository file path
  272. *
  273. * @param string $file_id
  274. * @return string
  275. */
  276. private function idToPath($file_id) {
  277. if (strlen($file_id) == 40) {
  278. $parts = array();
  279. for ($i = 0; $i < 3; $i++) {
  280. $parts[] = substr($file_id, $i * 5, 5);
  281. } // for
  282. $parts[] = substr($file_id, 15, 25);
  283. return implode('/', $parts);
  284. } else {
  285. return null;
  286. } // if
  287. } // idToPath
  288. /**
  289. * Return unique file ID
  290. *
  291. * @param void
  292. * @return string
  293. */
  294. private function getUniqueId() {
  295. do {
  296. $id = sha1(uniqid(rand(), true));
  297. $file_path = $this->getFilePath($id);
  298. } while (is_file($file_path));
  299. return $id;
  300. } // getUniqueId
  301. // ---------------------------------------------------
  302. // Attribute handling
  303. // ---------------------------------------------------
  304. /**
  305. * Load file attributes
  306. *
  307. * @param void
  308. * @return null
  309. */
  310. protected function loadFileAttributes() {
  311. $file = $this->getAttributesFilePath();
  312. if (is_file($file)) {
  313. if (!is_readable($file)) {
  314. throw new FileDnxError($file);
  315. } // if
  316. $attributes = include $file; // read from file
  317. if (is_array($attributes)) {
  318. $this->attributes = $attributes;
  319. } else {
  320. $this->attributes = array();
  321. $this->saveFileAttributes();
  322. } // if
  323. } else {
  324. $this->attributes = array();
  325. $this->saveFileAttributes();
  326. } // if
  327. } // loadFileAttributes
  328. /**
  329. * Safe file attribute value to a file
  330. *
  331. * @param void
  332. * @return boolean
  333. */
  334. protected function saveFileAttributes() {
  335. $file = $this->getAttributesFilePath();
  336. if (is_file($file) && !file_is_writable($file)) {
  337. throw new FileNotWriableError($file);
  338. } // if
  339. return file_put_contents($file, "<?php\n\nreturn " . var_export($this->attributes, true) . ";\n\n?>");
  340. } // saveFileAttributes
  341. /**
  342. * Return path of file where we save file attributes
  343. *
  344. * @param void
  345. * @return string
  346. */
  347. protected function getAttributesFilePath() {
  348. return with_slash($this->getRepositoryDir()) . 'attributes.php';
  349. } // getAttributesFilePath
  350. // ---------------------------------------------------
  351. // Getters and setters
  352. // ---------------------------------------------------
  353. /**
  354. * Get repository_dir
  355. *
  356. * @param null
  357. * @return string
  358. */
  359. function getRepositoryDir() {
  360. return $this->repository_dir;
  361. } // getRepositoryDir
  362. /**
  363. * Set repository_dir value
  364. *
  365. * @param string $value
  366. * @return null
  367. * @throws DirDnxError
  368. * @throws DirNotWritableError
  369. */
  370. function setRepositoryDir($value) {
  371. if (!is_null($value) && !is_dir($value)) {
  372. throw new DirDnxError($value);
  373. } // if
  374. if (!folder_is_writable($value)) {
  375. throw new DirNotWritableError($value);
  376. } // if
  377. $this->repository_dir = $value;
  378. } // setRepositoryDir
  379. } // FileRepository_Backend_FileSystem
  380. ?>