PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/storage/Filesystem.php

https://github.com/mamayoleksandr/xtbackup
PHP | 317 lines | 231 code | 21 blank | 65 comment | 16 complexity | ef372cc7769b8a4daa0d6fa2a8dcc511 MD5 | raw file
  1. <?php
  2. class Storage_Filesystem implements Storage_Interface
  3. {
  4. /**
  5. *
  6. * @var Core_Engine
  7. */
  8. protected $_engine;
  9. /**
  10. * @var Output_Stack
  11. */
  12. protected $_out;
  13. /**
  14. *
  15. * @var array
  16. */
  17. protected $_options;
  18. /** @var bool */
  19. protected $_asRemote;
  20. /** @var array */
  21. protected $_drivers;
  22. /**
  23. * Not sure if it is correct way to do it
  24. *
  25. * @var string
  26. */
  27. protected $_trimFromBegin;
  28. /**
  29. *
  30. * @var boolean
  31. */
  32. protected $_isWindows;
  33. /**
  34. *
  35. * @var Storage_Filesystem_FileStat
  36. */
  37. protected $_fileStat;
  38. /**
  39. * @param Core_Engine $engine
  40. * @param Output_Stack $output
  41. * @param array $options
  42. *
  43. * @return \Storage_Filesystem
  44. */
  45. public function __construct($engine, $output, $options)
  46. {
  47. // merge options with default options
  48. $options = $engine::array_merge_recursive_distinct(self::getConfigOptions(CfgPart::DEFAULTS), $options);
  49. $this->_out = $output;
  50. $this->_options = $options;
  51. $this->_engine = $engine;
  52. $this->_baseDir = $this->_options['basedir']; // for faster access
  53. $this->_baseDir = rtrim($this->_baseDir, '/'). '/';
  54. $this->_isWindows = (strpos(strtolower(php_uname('s')), 'win') !== false);
  55. $this->_fileStat = new Storage_Filesystem_FileStat();
  56. if (array_key_exists('windows', $this->_options) && array_key_exists('encoding', $this->_options['windows'])) {
  57. $this->_fileStat->setWindowsEncoding($this->_options['windows']['encoding']);
  58. }
  59. }
  60. public function init($myrole, $drivers)
  61. {
  62. $this->_out->logNotice(">>>init Filesystem driver as $myrole");
  63. $this->_asRemote = $myrole==Core_Engine::ROLE_REMOTE;
  64. $this->_drivers = $drivers;
  65. if (!file_exists($this->_baseDir)) {
  66. $this->_out->stop("local folder {$this->_baseDir} doesn't exists");
  67. }
  68. return true;
  69. }
  70. public function refreshLocal($myrole, $drivers)
  71. {
  72. if ($this->_asRemote) {
  73. // nothing to do if I am not remote driver
  74. return;
  75. }
  76. $this->_out->logNotice(">>>refresh from local file system");
  77. if ($this->_isWindows) {
  78. $this->_refreshLocalWindows($myrole, $drivers);
  79. } else {
  80. $this->_refreshLocal($myrole, $drivers);
  81. }
  82. }
  83. protected function _refreshLocalWindows($myrole, $drivers)
  84. {
  85. $job = $this->_out->jobStart("traversing file system starting at {$this->_baseDir} using COM");
  86. //let create context for our closure
  87. /** @var $compare Compare_Interface */
  88. $compare = $drivers[Core_Engine::ROLE_COMPARE];
  89. $stat = $this->_fileStat;
  90. $baseDir = $this->_baseDir;
  91. $compare->updateFromLocalStart();
  92. $out = $this->_out;
  93. $root = true;
  94. $count = 0;
  95. $makePath = function($path) use (&$baseDir) {
  96. return str_replace("\\", "/", substr($path, strlen($baseDir)));
  97. };
  98. //another closure for compare
  99. $c = function($path) use (&$compare, &$stat, &$count, &$makePath)
  100. {
  101. /** @var $compare Compare_Interface */
  102. /** @var $stat Storage_Filesystem_FileStat */
  103. $obj = Core_FsObject::factoryFromStat($makePath($path), $stat->getStat($path));
  104. $compare->updateFromLocal($obj);
  105. $count++;
  106. };
  107. //currently regexp filter for windows is implemented in closure
  108. //for other platforms it should support Filter_Interface
  109. $options = $this->_options;
  110. $regExpFilter = function($name, $isDir) use (&$options) {
  111. // TODO fix notification
  112. $filterConf = @$options['filter'][$options['filterType']];
  113. if ($isDir) {
  114. if (isset($filterConf['config']['dirnameFilter'])) {
  115. if (preg_match($filterConf['config']['dirnameFilter'], $name)) {
  116. return false;
  117. }
  118. }
  119. } else {
  120. if (isset($filterConf['config']['filenameFilter'])) {
  121. if (preg_match($filterConf['config']['filenameFilter'], $name)) {
  122. return false;
  123. }
  124. }
  125. }
  126. return true;
  127. };
  128. //empty closure will help
  129. $empty = function ($folder) use (&$regExpFilter)
  130. {
  131. //fix: without it folder that containes only files excluded by filter
  132. //will not be created, but it should be created as empty folder
  133. if ($folder->Files->Count > 0 && $folder->SubFolders->Count == 0) {
  134. foreach ($folder->Files as $file) {
  135. /** @var $file SplFileObject */
  136. if ($regExpFilter($file->Name, false)) {
  137. return false;
  138. }
  139. }
  140. return true;
  141. }
  142. if($folder->Files->Count > 0 || $folder->SubFolders->Count > 0) {
  143. return false;
  144. }
  145. return true;
  146. };
  147. //this closure recursively iterates diretory, updating pathes with UTF8 support
  148. $magicScan = function($dir) use (&$c, &$baseDir, &$root, &$magicScan, &$empty, &$out, &$regExpFilter)
  149. {
  150. //initialize windows's FileSystemObject
  151. /** @noinspection PhpUndefinedClassInspection, PhpUndefinedConstantInspection */
  152. $fso = new COM('Scripting.FileSystemObject', null, CP_UTF8);
  153. if ($root) {
  154. $curDir = $baseDir;
  155. $root = false;
  156. } else {
  157. $curDir = $dir;
  158. }
  159. //excluding folders
  160. //see Scripting.FileSystemObject on http://msdn.microsoft.com
  161. /** @noinspection PhpUndefinedMethodInspection */
  162. $folder = $fso->GetFolder($curDir);
  163. foreach ($folder->Files as $file) {
  164. //excluding files
  165. if (!$regExpFilter($file->Name, false)) {
  166. continue;
  167. }
  168. $c($file->Path);
  169. }
  170. foreach ($folder->SubFolders as $folder) {
  171. if (!$regExpFilter($folder->Name, true)) {
  172. continue;
  173. }
  174. if($empty($folder)) {
  175. //empty folders we update
  176. $c($folder->Path.'/');
  177. } else {
  178. //let's dig deeper :)
  179. $magicScan($folder);
  180. }
  181. }
  182. };
  183. $magicScan($baseDir);
  184. $compare->updateFromLocalEnd();
  185. $this->_out->jobEnd($job, "done: updated info about $count files");
  186. }
  187. protected function _refreshLocal($myrole, $drivers)
  188. {
  189. $job = $this->_out->jobStart("traversing file system starting at {$this->_baseDir}");
  190. /* @var $compare Compare_Sqlite */
  191. $compare = $drivers[Core_Engine::ROLE_COMPARE];
  192. $stat = $this->_fileStat;
  193. $it = new RecursiveDirectoryIterator($this->_baseDir);
  194. $it->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
  195. //$it = new DirnameFilter($it, "/informa/i");
  196. if (isset($this->_options['filter'])) {
  197. $this->_out->logNotice(">>>applying filter ".$this->_options['filterType']);
  198. $filterConf = $this->_options['filter'][$this->_options['filterType']];
  199. /** @var $filter Filter_Interface */
  200. $filter = new $filterConf['class'] ($filterConf);
  201. $filter->setIterator($it);
  202. $it = $filter->applyFilters();
  203. }
  204. $itemCount = 0;
  205. $this->_trimFromBegin = strlen($this->_baseDir);
  206. $compare->updateFromLocalStart();
  207. foreach(new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST) as $file) {
  208. /** @var $file SplFileObject */
  209. //we don't need make entry for not empty folder
  210. if ($file->isDir() && !Storage_Filesystem_FileStat::isEmptyDir((string)$file))
  211. {
  212. continue;
  213. }
  214. $file = (string) $file;
  215. $obj = Core_FsObject::factoryFromStat($this->_makePath($file), $stat->getStat($file));
  216. $compare->updateFromLocal($obj);
  217. $itemCount++;
  218. }
  219. $compare->updateFromLocalEnd();
  220. $this->_out->jobEnd($job, "updated info about $itemCount files");
  221. }
  222. protected function _makePath($file)
  223. {
  224. return str_replace("\\", "/", substr($file, $this->_trimFromBegin));
  225. }
  226. public function getBaseDir()
  227. {
  228. return $this->_baseDir;
  229. }
  230. /**
  231. *
  232. *
  233. * @param string $path
  234. * @return string
  235. */
  236. public function getMd5($path)
  237. {
  238. return $this->_fileStat->defineMd5($path);
  239. }
  240. public function isWindows()
  241. {
  242. return $this->_isWindows;
  243. }
  244. public function convertEncodingPath($path)
  245. {
  246. if ($this->_isWindows) {
  247. return mb_convert_encoding($path, $this->_options['windows']['encoding'], "auto");
  248. } else {
  249. return $path;
  250. }
  251. }
  252. static public function getConfigOptions($part=null)
  253. {
  254. $opt = array(
  255. CfgPart::DEFAULTS=>array(
  256. 'refresh'=>false,
  257. // ??? 'filter'=>false,
  258. //'basedir'=>,
  259. 'windows'=>array('encoding'=>'utf8'),
  260. ),
  261. CfgPart::DESCRIPTIONS=>array(
  262. 'refresh'=>'read actual data about file system and feed compare driver ?',
  263. 'basedir'=>'root directory to backup',
  264. 'windows.encoding'=>'....',
  265. ),
  266. CfgPart::REQUIRED=>array('basedir')
  267. );
  268. if (is_null($part)) {
  269. return $opt;
  270. } else {
  271. return array_key_exists($part, $opt) ? $opt[$part] : array();
  272. }
  273. }
  274. /*
  275. function refreshRemote($myrole, $drivers)
  276. {
  277. throw new Exception("refreshRemote not supported in Filesystem driver");
  278. }
  279. function updateRemote($myrole, $drivers)
  280. {
  281. throw new Exception("updateRemote not supported in Filesystem driver");
  282. }
  283. */
  284. }