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

/administrator/components/com_akeeba/akeeba/core/domain/pack.php

https://bitbucket.org/kraymitchell/saiu
PHP | 822 lines | 622 code | 80 blank | 120 comment | 113 complexity | 757ff150b552eff78e1d7186fd68b0a7 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, BSD-3-Clause, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * Akeeba Engine
  4. * The modular PHP5 site backup engine
  5. * @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
  6. * @license GNU GPL version 3 or, at your option, any later version
  7. * @package akeebaengine
  8. *
  9. */
  10. // Protection against direct access
  11. defined('AKEEBAENGINE') or die();
  12. // @todo Remove me!
  13. // Large file threshold (default: 10Mb)
  14. define('AELargeFileThreshold', 10247680);
  15. /* Windows system detection */
  16. if(!defined('_AKEEBA_IS_WINDOWS'))
  17. {
  18. if (function_exists('php_uname'))
  19. define('_AKEEBA_IS_WINDOWS', stristr(php_uname(), 'windows'));
  20. else
  21. define('_AKEEBA_IS_WINDOWS', DIRECTORY_SEPARATOR == '\\');
  22. }
  23. /**
  24. * Packing engine. Takes care of putting gathered files (the file list) into
  25. * an archive.
  26. */
  27. class AECoreDomainPack extends AEAbstractPart {
  28. /** @var array Directories left to be scanned */
  29. private $directory_list;
  30. /** @var array Files left to be put into the archive */
  31. private $file_list;
  32. /**
  33. * Operation toggle. When it is true, files are added in the archive. When it is off, the
  34. * directories are scanned for files and directories.
  35. *
  36. * @var bool
  37. */
  38. private $done_scanning = false;
  39. /**
  40. * Operation toggle #2. Scanning is separated in two sub-operations: scanning for
  41. * subdirectories (when this flag is false) and scanning for files (when this flag is
  42. * true).
  43. *
  44. * @var bool
  45. */
  46. private $done_subdir_scanning = false;
  47. /**
  48. * Operation toggle #3. Since the scanning of a folder for files might be interrupted
  49. * for some reason, when this variable is false the algorithm is forced NOT to skip
  50. * to the next item of the directory list.
  51. *
  52. * @var bool
  53. */
  54. private $done_file_scanning = true;
  55. /** @var string Path to add to scanned files */
  56. private $path_prefix;
  57. /** @var string Path to remove from scanned files */
  58. private $remove_path_prefix;
  59. /** @var array An array of root directories to scan */
  60. private $root_definitions = array();
  61. /** @var int How many files have been processed in the current step */
  62. private $processed_files_counter;
  63. /** @var string Current directory being scanned */
  64. private $current_directory;
  65. /** @var string Current root directory being processed */
  66. private $root = '[SITEROOT]';
  67. private $total_roots = 0;
  68. private $total_files = 0;
  69. private $done_files = 0;
  70. private $total_folders = 0;
  71. private $done_folders = 0;
  72. /**
  73. * Public constructor of the class
  74. *
  75. * @return AECoreDomainPack
  76. */
  77. public function __construct()
  78. {
  79. parent::__construct();
  80. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: new instance");
  81. }
  82. /**
  83. * Implements the _prepare() abstract method
  84. *
  85. */
  86. protected function _prepare()
  87. {
  88. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Starting _prepare()");
  89. // Get a list of directories to include
  90. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Getting directory inclusion filters");
  91. $filters = AEFactory::getFilters();
  92. $this->root_definitions = $filters->getInclusions('dir');
  93. $this->total_roots = count($this->root_definitions);
  94. // Add the mapping text file if there are external directories defined!
  95. if(count($this->root_definitions) > 1)
  96. {
  97. // We add a README.txt file in our virtual directory...
  98. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Creating README.txt in the EFF virtual folder");
  99. $virtualContents = <<<ENDVCONTENT
  100. This directory contains directories above the web site's root you chose to
  101. include in the backup set. This file helps you figure out which directory
  102. in the backup set corresponds to which directory in the original site's
  103. structure. You'll have to restore these files manually!
  104. ENDVCONTENT;
  105. $counter = 0;
  106. foreach($this->root_definitions as $dir)
  107. {
  108. $counter++;
  109. // Skip over the first filter, because it's the site's root
  110. if($counter == 1) continue;
  111. $virtualContents .= $dir[1]."\tis the backup of\t".$dir[0]."\n";
  112. }
  113. // Add the file to our archive
  114. $registry = AEFactory::getConfiguration();
  115. $archiver = AEFactory::getArchiverEngine();
  116. $archiver->addVirtualFile('README.txt', $registry->get('akeeba.advanced.virtual_folder'), $virtualContents);
  117. }
  118. // Find the site's root element and shift it into the directory list
  119. $dir_definition = array_shift($this->root_definitions);
  120. $count = 0;
  121. $max_dir_count = count( $this->root_definitions );
  122. while( !is_null($dir_definition[1]) && ($count < $max_dir_count) )
  123. {
  124. $count++;
  125. array_push($this->root_definitions, $dir_definition);
  126. $dir_definition = array_shift($this->root_definitions);
  127. }
  128. // Settling with whatever we have, let's put it to use, shall we?
  129. $this->remove_path_prefix = $dir_definition[0]; // Remove absolute path to directory when storing the file
  130. if(is_null($dir_definition[1]))
  131. {
  132. $this->path_prefix = ''; // No added path for main site
  133. if(empty($dir_definition[0])) {
  134. $this->root = '[SITEROOT]';
  135. } else {
  136. $this->root = $dir_definition[0];
  137. }
  138. }
  139. else
  140. {
  141. $this->path_prefix = $registry->get('akeeba.advanced.virtual_folder').'/'.$dir_definition[1];
  142. $this->root = $dir_definition[0];
  143. }
  144. // Translate the root into an absolute path
  145. $stock_dirs = AEPlatform::getInstance()->get_stock_directories();
  146. $absolute_dir = substr($this->root,0);
  147. if(!empty($stock_dirs))
  148. {
  149. foreach($stock_dirs as $key => $replacement)
  150. {
  151. $absolute_dir = str_replace($key, $replacement, $absolute_dir);
  152. }
  153. }
  154. $this->directory_list[] = $absolute_dir;
  155. $this->remove_path_prefix = $absolute_dir;
  156. $registry = AEFactory::getConfiguration();
  157. $registry->set('volatile.filesystem.current_root', $absolute_dir);
  158. $this->done_scanning = false; // Instruct the class to scan for files and directories
  159. $this->done_subdir_scanning = true;
  160. $this->done_file_scanning = true;
  161. $this->total_files = 0;
  162. $this->done_files = 0;
  163. $this->total_folders = 0;
  164. $this->done_folders = 0;
  165. $this->setState('prepared');
  166. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: prepared");
  167. }
  168. protected function _run()
  169. {
  170. // Run in a loop until we run out of time, or breakflag is set
  171. $registry = AEFactory::getConfiguration();
  172. $timer = AEFactory::getTimer();
  173. while( ($timer->getTimeLeft() > 0) && (!$registry->get('volatile.breakflag', false)) )
  174. {
  175. if ($this->getState() == 'postrun') {
  176. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Already finished");
  177. $this->setStep("-");
  178. $this->setSubstep("");
  179. break;
  180. }
  181. else
  182. {
  183. if($this->done_scanning)
  184. {
  185. $this->pack_files();
  186. if($this->getError()) return false;
  187. }
  188. else
  189. {
  190. $result = $this->scan_directory();
  191. if($this->getError()) return false;
  192. if(!$result)
  193. {
  194. // We have finished with our directory list. Hmm... Do we have extra directories?
  195. if(count($this->root_definitions) > 0)
  196. {
  197. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "More off-site directories detected");
  198. $registry = AEFactory::getConfiguration();
  199. $dir_definition = array_shift($this->root_definitions);
  200. $this->remove_path_prefix = $dir_definition[0]; // Remove absolute path to directory when storing the file
  201. if(is_null($dir_definition[1]))
  202. {
  203. $this->path_prefix = ''; // No added path for main site
  204. }
  205. else
  206. {
  207. $this->path_prefix = $registry->get('akeeba.advanced.virtual_folder').'/'.$dir_definition[1];
  208. }
  209. $this->done_scanning = false; // Make sure we process this file list!
  210. $this->root = $dir_definition[0];
  211. // Translate the root into an absolute path
  212. $stock_dirs = AEPlatform::getInstance()->get_stock_directories();
  213. $absolute_dir = substr($this->root,0);
  214. if(!empty($stock_dirs))
  215. {
  216. foreach($stock_dirs as $key => $replacement)
  217. {
  218. $absolute_dir = str_replace($key, $replacement, $absolute_dir);
  219. }
  220. }
  221. $this->directory_list[] = $absolute_dir;
  222. $this->remove_path_prefix = $absolute_dir;
  223. $registry->set('volatile.filesystem.current_root', $absolute_dir);
  224. $this->total_files = 0;
  225. $this->done_files = 0;
  226. $this->total_folders = 0;
  227. $this->done_folders = 0;
  228. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Including new off-site directory to ".$dir_definition[1]);
  229. }
  230. else
  231. // Nope, we are completely done!
  232. $this->setState('postrun');
  233. } // if not result
  234. } // if not doneScanning
  235. } // if not postrun
  236. } // while
  237. return true;
  238. }
  239. /**
  240. * Implements the _finalize() abstract method
  241. *
  242. */
  243. protected function _finalize()
  244. {
  245. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Finalizing archive");
  246. $archive = AEFactory::getArchiverEngine();
  247. $archive->finalize();
  248. // Error propagation
  249. $this->propagateFromObject($archive);
  250. if($this->getError())
  251. {
  252. return false;
  253. }
  254. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Archive is finalized");
  255. $this->setState('finished');
  256. }
  257. // ============================================================================================
  258. // PRIVATE METHODS
  259. // ============================================================================================
  260. /**
  261. * Scans a directory for files and directories, updating the directory_list and file_list
  262. * private fields
  263. *
  264. * @return bool True if more work has to be done, false if the dirextory stack is empty
  265. */
  266. private function scan_directory()
  267. {
  268. // Are we supposed to scan for more files?
  269. if( $this->done_scanning ) return true;
  270. // Get the next directory to scan, if the folders and files of the last directory
  271. // have been scanned.
  272. if($this->done_subdir_scanning && $this->done_file_scanning)
  273. {
  274. if( count($this->directory_list) == 0 )
  275. {
  276. // No directories left to scan
  277. return false;
  278. }
  279. else
  280. {
  281. // Get and remove the last entry from the $directory_list array
  282. $this->current_directory = array_pop($this->directory_list);
  283. $this->setStep($this->current_directory);
  284. $this->done_subdir_scanning = false;
  285. $this->done_file_scanning = false;
  286. $this->processed_files_counter = 0;
  287. }
  288. }
  289. $engine = AEFactory::getScanEngine();
  290. // Break directory components
  291. if(AEFactory::getConfiguration()->get('akeeba.platform.override_root',0)) {
  292. $siteroot = AEFactory::getConfiguration()->get('akeeba.platform.newroot', '[SITEROOT]');
  293. } else {
  294. $siteroot = '[SITEROOT]';
  295. }
  296. $root = $this->root;
  297. if($this->root == $siteroot) {
  298. $translated_root = AEUtilFilesystem::translateStockDirs($siteroot, true);
  299. } else {
  300. $translated_root = $this->remove_path_prefix;
  301. }
  302. $dir = AEUtilFilesystem::TrimTrailingSlash($this->current_directory);
  303. if(strtoupper(substr(PHP_OS,0,3)) == 'WIN') {
  304. $translated_root = AEUtilFilesystem::TranslateWinPath($translated_root);
  305. $dir = AEUtilFilesystem::TranslateWinPath($dir);
  306. }
  307. if(substr($dir,0,strlen($translated_root)) == $translated_root) {
  308. $dir = substr($dir,strlen($translated_root));
  309. } elseif(in_array(substr($translated_root,-1),array('/','\\'))) {
  310. $new_translated_root = rtrim($translated_root,'/\\');
  311. if(substr($dir,0,strlen($new_translated_root)) == $new_translated_root) {
  312. $dir = substr($dir,strlen($new_translated_root));
  313. }
  314. }
  315. if(substr($dir,0,1) == '/') $dir = substr($dir,1);
  316. // get a filters instance
  317. $filters = AEFactory::getFilters();
  318. // Scan subdirectories, if they have not yet been scanned.
  319. if(!$this->done_subdir_scanning)
  320. {
  321. // Apply DEF (directory exclusion filters)
  322. // Note: the !empty($dir) prevents the site's root from being filtered out
  323. if($filters->isFiltered($dir, $root, 'dir', 'all') && !empty($dir) ) {
  324. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping directory ".$this->current_directory);
  325. $this->done_subdir_scanning = true;
  326. $this->done_file_scanning = true;
  327. return true;
  328. }
  329. // Apply Skip Contained Directories Filters
  330. if($filters->isFiltered($dir, $root, 'dir', 'children') ) {
  331. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping subdirectories of directory ".$this->current_directory);
  332. $this->done_subdir_scanning = true;
  333. }
  334. else
  335. {
  336. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Scanning directories of ".$this->current_directory);
  337. // Get subdirectories
  338. $subdirs = $engine->getFolders($this->current_directory);
  339. // Error propagation
  340. $this->propagateFromObject($engine);
  341. // If the list contains "too many" items, please break this step!
  342. $registry = AEFactory::getConfiguration();
  343. if($registry->get('volatile.breakflag', false))
  344. {
  345. // Log the step break decision, for debugging reasons
  346. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Large directory ".$this->current_directory." while scanning for subdirectories; I will resume scanning in next step.");
  347. // Return immediately, marking that we are not done yet!
  348. return true;
  349. }
  350. // Error control
  351. if($this->getError())
  352. {
  353. return false;
  354. }
  355. if(!empty($subdirs) && is_array($subdirs))
  356. {
  357. $registry = AEFactory::getConfiguration();
  358. $dereferencesymlinks = $registry->get('engine.archiver.common.dereference_symlinks');
  359. if($dereferencesymlinks)
  360. {
  361. // Treat symlinks to directories as actual directories
  362. foreach($subdirs as $subdir)
  363. {
  364. $this->directory_list[] = $subdir;
  365. $this->progressAddFolder();
  366. }
  367. }
  368. else
  369. {
  370. // Treat symlinks to directories as simple symlink files (ONLY WORKS WITH CERTAIN ARCHIVERS!)
  371. foreach($subdirs as $subdir)
  372. {
  373. if(is_link($subdir))
  374. {
  375. // Symlink detected; apply file filters to it
  376. if(empty($dir)) {
  377. $dirSlash = $dir;
  378. } else {
  379. $dirSlash = $dir.'/';
  380. }
  381. $check = $dir.basename($subdir);
  382. if(_AKEEBA_IS_WINDOWS) $check = AEUtilFilesystem::TranslateWinPath ($check);
  383. // Do I need this? $dir contains a path relative to the root anyway...
  384. $check = ltrim(str_replace($translated_root, '', $check),'/');
  385. if($filters->isFiltered($check, $root, 'file', 'all') ) {
  386. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping directory symlink ".$check);
  387. } else {
  388. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Adding symlink to folder as file: '.$check);
  389. $this->file_list[] = $subdir;
  390. $this->progressAddFile();
  391. }
  392. }
  393. else
  394. {
  395. $this->directory_list[] = $subdir;
  396. $this->progressAddFolder();
  397. }
  398. }
  399. }
  400. }
  401. }
  402. $this->done_subdir_scanning = true;
  403. return true; // Break operation
  404. }
  405. // If we are here, we have not yet scanned the directory for files, so there
  406. // is no need to test for done_file_scanning (saves a tiny amount of CPU time)
  407. // Apply Skipfiles
  408. if($filters->isFiltered($dir, $root, 'dir', 'content') ) {
  409. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping files of directory ".$this->current_directory);
  410. // Try to find and include .htaccess and index.htm(l) files
  411. // # Fix 2.4: Do not add DIRECTORY_SEPARATOR if we are on the site's root and it's an empty string
  412. $ds = ($this->current_directory == '') || ($this->current_directory == '/') ? '' : DIRECTORY_SEPARATOR;
  413. $checkForTheseFiles = array(
  414. $this->current_directory.$ds.'.htaccess',
  415. $this->current_directory.$ds.'index.html',
  416. $this->current_directory.$ds.'index.htm',
  417. $this->current_directory.$ds.'robots.txt'
  418. );
  419. $this->processed_files_counter = 0;
  420. foreach($checkForTheseFiles as $fileName)
  421. {
  422. if(@file_exists($fileName))
  423. {
  424. // Fix 3.3 - We have to also put them through other filters, ahem!
  425. if(!$filters->isFiltered($fileName, $root, 'file', 'all')) {
  426. $this->file_list[] = $fileName;
  427. $this->processed_files_counter++;
  428. }
  429. }
  430. }
  431. $this->done_file_scanning = true;
  432. }
  433. else
  434. {
  435. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Scanning files of ".$this->current_directory);
  436. // Get file listing
  437. $fileList = $engine->getFiles( $this->current_directory );
  438. // Error propagation
  439. $this->propagateFromObject($engine);
  440. // If the list contains "too many" items, please break this step!
  441. $registry = AEFactory::getConfiguration();
  442. if($registry->get('volatile.breakflag', false))
  443. {
  444. // Log the step break decision, for debugging reasons
  445. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Large directory ".$this->current_directory." while scanning for files; I will resume scanning in next step.");
  446. // Return immediately, marking that we are not done yet!
  447. return true;
  448. }
  449. // Error control
  450. if($this->getError())
  451. {
  452. return false;
  453. }
  454. $this->processed_files_counter = 0;
  455. if (($fileList === false)) {
  456. // A non-browsable directory; however, it seems that I never get FALSE reported here?!
  457. $this->setWarning('Unreadable directory '.$this->current_directory);
  458. }
  459. else
  460. {
  461. if(is_array($fileList) && !empty($fileList))
  462. {
  463. // Add required trailing slash to $dir
  464. if(!empty($dir)) $dir.='/';
  465. // Scan all directory entries
  466. foreach($fileList as $fileName) {
  467. $check = $dir.basename($fileName);
  468. if(_AKEEBA_IS_WINDOWS) $check = AEUtilFilesystem::TranslateWinPath ($check);
  469. // Do I need this? $dir contains a path relative to the root anyway...
  470. $check = ltrim(str_replace($translated_root, '', $check),'/');
  471. $skipThisFile = $filters->isFiltered($check, $root, 'file', 'all');
  472. if ($skipThisFile) {
  473. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping file $fileName");
  474. } else {
  475. $this->file_list[] = $fileName;
  476. $this->processed_files_counter++;
  477. $this->progressAddFile();
  478. }
  479. } // end foreach
  480. } // end if
  481. } // end filelist not false
  482. $this->done_file_scanning = true;
  483. }
  484. // Check to see if there were no contents of this directory added to our search list
  485. if ( $this->processed_files_counter == 0 ) {
  486. $archiver = AEFactory::getArchiverEngine();
  487. if($this->current_directory != $this->remove_path_prefix) {
  488. $archiver->addFile($this->current_directory, $this->remove_path_prefix, $this->path_prefix);
  489. }
  490. // Error propagation
  491. $this->propagateFromObject($archiver);
  492. if($this->getError())
  493. {
  494. return false;
  495. }
  496. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Empty directory ".$this->current_directory);
  497. unset($archiver);
  498. $this->done_scanning = false; // Because it was an empty dir $file_list is empty and we have to scan for more files
  499. }
  500. else
  501. {
  502. // Next up, add the files to the archive!
  503. $this->done_scanning = true;
  504. }
  505. // We're done listing the contents of this directory
  506. unset($engine);
  507. return true;
  508. }
  509. /**
  510. * Try to pack some files in the $file_list, restraining ourselves not to reach the max
  511. * number of files or max fragment size while doing so. If this process is over and we are
  512. * left without any more files, reset $done_scanning to false in order to instruct the class
  513. * to scan for more files.
  514. *
  515. * @return bool True if there were files packed, false otherwise (empty filelist)
  516. */
  517. private function pack_files()
  518. {
  519. // Get a reference to the archiver and the timer classes
  520. $archiver = AEFactory::getArchiverEngine();
  521. $timer = AEFactory::getTimer();
  522. $configuration = AEFactory::getConfiguration();
  523. // If post-processing after part creation is enabled, make sure we do post-process each part before moving on
  524. if($configuration->get('engine.postproc.common.after_part',0))
  525. {
  526. if(!empty($archiver->finishedPart))
  527. {
  528. $filename = array_shift($archiver->finishedPart);
  529. AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Preparing to post process '.basename($filename));
  530. $post_proc = AEFactory::getPostprocEngine();
  531. $result = $post_proc->processPart( $filename );
  532. $this->propagateFromObject($post_proc);
  533. if($result === false)
  534. {
  535. $this->setWarning('Failed to process file '.basename($filename));
  536. }
  537. else
  538. {
  539. AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Successfully processed file '.basename($filename));
  540. }
  541. // Should we delete the file afterwards?
  542. if(
  543. $configuration->get('engine.postproc.common.delete_after',false)
  544. && $post_proc->allow_deletes
  545. && ($result !== false)
  546. )
  547. {
  548. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Deleting already processed file '.basename($filename));
  549. AEPlatform::getInstance()->unlink($filename);
  550. }
  551. if($post_proc->break_after && ($result !== false)) {
  552. $configuration->set('volatile.breakflag', true);
  553. return true;
  554. }
  555. // This is required to let the backup continue even after a post-proc failure
  556. $this->resetErrors();
  557. $this->setState('running');
  558. }
  559. }
  560. // If the archiver has work to do, make sure it finished up before continuing
  561. if( $configuration->get('volatile.engine.archiver.processingfile',false) )
  562. {
  563. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Continuing file packing from previous step");
  564. $result = $archiver->addFile('', '', '');
  565. $this->propagateFromObject($archiver);
  566. if($this->getError())
  567. {
  568. return false;
  569. }
  570. // If that was the last step, mark a file done
  571. if( !$configuration->get('volatile.engine.archiver.processingfile',false) ) {
  572. $this->progressMarkFileDone();
  573. }
  574. }
  575. // Did it finish, or does it have more work to do?
  576. if( $configuration->get('volatile.engine.archiver.processingfile',false) )
  577. {
  578. // More work to do. Let's just tell our parent that we finished up successfully.
  579. return true;
  580. }
  581. // Normal file backup loop; we keep on processing the file list, packing files as we go.
  582. if( count($this->file_list) == 0 )
  583. {
  584. // No files left to pack -- This should never happen! We catch this condition at the end of this method!
  585. $this->done_scanning = false;
  586. $this->progressMarkFolderDone();
  587. return false;
  588. }
  589. else
  590. {
  591. AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Packing files");
  592. $packedSize = 0;
  593. $numberOfFiles = 0;
  594. list($usec, $sec) = explode(" ", microtime());
  595. $opStartTime = ((float)$usec + (float)$sec);
  596. while( (count($this->file_list) > 0) )
  597. {
  598. $file = @array_shift($this->file_list);
  599. $size = 0;
  600. if(file_exists($file)) $size = @filesize($file);
  601. // Anticipatory file size algorithm
  602. if( ($numberOfFiles > 0) && ($size > AELargeFileThreshold) )
  603. {
  604. if(!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.beforelargefile',0)) {
  605. // If the file is bigger than the big file threshold, break the step
  606. // to avoid potential timeouts
  607. $this->setBreakFlag();
  608. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Breaking step _before_ large file: ".$file." - size: ".$size);
  609. // Push the file back to the list.
  610. array_unshift($this->file_list, $file);
  611. // Mark that we are not done packing files
  612. $this->done_scanning = true;
  613. return true;
  614. }
  615. }
  616. // Proactive potential timeout detection
  617. // Rough estimation of packing speed in bytes per second
  618. list($usec, $sec) = explode(" ", microtime());
  619. $opEndTime = ((float)$usec + (float)$sec);
  620. if( ($opEndTime - $opStartTime) == 0 )
  621. {
  622. $_packSpeed = 0;
  623. }
  624. else
  625. {
  626. $_packSpeed = $packedSize / ($opEndTime - $opStartTime);
  627. }
  628. // Estimate required time to pack next file. If it's the first file of this operation,
  629. // do not impose any limitations.
  630. $_reqTime = ($_packSpeed - 0.01) <= 0 ? 0 : $size / $_packSpeed;
  631. // Do we have enough time?
  632. if($timer->getTimeLeft() < $_reqTime )
  633. {
  634. if(!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.proactive',0)) {
  635. array_unshift($this->file_list, $file);
  636. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Proactive step break - file: ".$file." - size: ".$size." - req. time ".sprintf('%2.2f',$_reqTime) );
  637. $this->setBreakFlag();
  638. $this->done_scanning = true;
  639. return true;
  640. }
  641. }
  642. $packedSize += $size;
  643. $numberOfFiles++;
  644. $ret = $archiver->addFile($file, $this->remove_path_prefix, $this->path_prefix);
  645. // If no more processing steps are required, mark a done file
  646. if( !$configuration->get('volatile.engine.archiver.processingfile',false) ) {
  647. $this->progressMarkFileDone();
  648. }
  649. // Error propagation
  650. $this->propagateFromObject($archiver);
  651. if($this->getError())
  652. {
  653. return false;
  654. }
  655. // If this was the first file of the fragment and it exceeded the fragment's capacity,
  656. // break the step. Continuing with more operations after packing such a big file is
  657. // increasing the risk to hit a timeout.
  658. if( ($packedSize > AELargeFileThreshold) && ($numberOfFiles == 1) )
  659. {
  660. if(!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.afterlargefile',0)) {
  661. AEUtilLogger::WriteLog(_AE_LOG_INFO, "Breaking step *after* large file: ".$file." - size: ".$size);
  662. $this->setBreakFlag();
  663. return true;
  664. }
  665. }
  666. // If we have to continue processing the file, break the file packing loop forcibly
  667. if( $configuration->get('volatile.engine.archiver.processingfile',false) ) {
  668. return true;
  669. }
  670. }
  671. $this->done_scanning = count($this->file_list) > 0;
  672. if(!$this->done_scanning) $this->progressMarkFolderDone();
  673. return true;
  674. }
  675. }
  676. /**
  677. * Implements the getProgress() percentage calculation based on how many
  678. * roots we have fully backed up and how much of the current root we
  679. * have backed up.
  680. *
  681. * @see backend/akeeba/abstract/AEAbstractPart#getProgress()
  682. */
  683. public function getProgress()
  684. {
  685. if(empty($this->total_roots)) return 0;
  686. // Get the overall percentage (based on databases fully dumped so far)
  687. $remaining_steps = count($this->root_definitions);
  688. $remaining_steps++;
  689. $overall = 1 - ($remaining_steps / $this->total_roots);
  690. // How much is this step worth?
  691. $this_max = 1 / $this->total_roots;
  692. // Get the percentage done of the current root. Hey, the calculation *is* dodgy, I know it!
  693. $local = 0;
  694. if($this->total_files > 0)
  695. {
  696. $local += 0.05 * $this->done_files / $this->total_files;
  697. }
  698. if($this->total_folders > 0)
  699. {
  700. $local += 0.95 * $this->done_folders / $this->total_folders;
  701. }
  702. $percentage = $overall + $local * $this_max;
  703. if($percentage < 0) $percentage = 0;
  704. if($percentage > 1) $percentage = 1;
  705. return $percentage;
  706. }
  707. private function progressAddFile()
  708. {
  709. $this->total_files++;
  710. }
  711. private function progressMarkFileDone()
  712. {
  713. $this->done_files++;
  714. }
  715. private function progressAddFolder()
  716. {
  717. $this->total_folders++;
  718. }
  719. private function progressMarkFolderDone()
  720. {
  721. $this->done_folders++;
  722. }
  723. }