Beispiel #1
0
	/**
	 * Resets the Kettenrad state, wipping out any pending backups and/or stale
	 * temporary data.
	 * @param array $config Configuration parameters for the reset operation
	 */
	public static function reset( $config = array() )
	{
		$default_config = array(
			'global'	=> true,	// Reset all origins when true
			'log'		=> false,	// Log our actions
		);

		$config = (object)array_merge($default_config, $config);

		// Pause logging if so desired
		if(!$config->log) AEUtilLogger::WriteLog(false,'');

		$tag = null;
		if(!$config->global)
		{
			// If we're not resetting globally, get a list of running backups per tag
			$tag = AEPlatform::get_backup_origin();
		}

		// Cache the factory before proceeding
		$factory = AEFactory::serialize();

		$runningList = AEPlatform::get_running_backups($tag);
		// Origins we have to clean
		$origins = array(
			AEPlatform::get_backup_origin()
		);

		// 1. Detect failed backups
		if(is_array($runningList) && !empty($runningList))
		{
			// The current timestamp
			$now = time();

			// Mark running backups as failed
			foreach($runningList as $running)
			{
				if(empty($tag))
				{
					// Check the timestamp of the log file to decide if it's stuck,
					// but only if a tag is not set
					$tstamp = @filemtime( AEUtilLogger::logName($running['origin']) );
					if($tstamp !== false)
					{
						// We can only check the timestamp if it's returned. If not, we assume the backup is stale
						$difference = abs($now - $tstamp);
						// Backups less than 3 minutes old are not considered stale
						if($difference < 180) continue;
					}
				}

				$filenames = AEUtilStatistics::get_all_filenames($running);
				// Process if there are files to delete...
				if(!is_null($filenames))
				{
					// Delete the failed backup's archive, if exists
					foreach($filenames as $failedArchive)
					{
						AEPlatform::unlink($failedArchive);
					}
				}

				// Mark the backup failed
				$running['status'] = 'fail';
				$running['multipart'] = 0;
				$dummy = null;
				AEPlatform::set_or_update_statistics( $running['id'], $running, $dummy );

				$origins[] = $running['origin'];
			}
		}

		if(!empty($origins))
		{
			$origins = array_unique($origins);
			foreach($origins as $tag)
			{
				AECoreKettenrad::load($tag);
				// Remove temporary files
				AEUtilTempfiles::deleteTempFiles();
				// Delete any stale temporary data
				AEUtilTempvars::reset($tag);
			}
		}

		// Reload the factory
		AEFactory::unserialize($factory);
		unset($factory);

		// Unpause logging if it was previously paused
		if(!$config->log) AEUtilLogger::WriteLog(true,'');
	}
Beispiel #2
0
	/**
	 * Try to pack some files in the $file_list, restraining ourselves not to reach the max
	 * number of files or max fragment size while doing so. If this process is over and we are
	 * left without any more files, reset $done_scanning to false in order to instruct the class
	 * to scan for more files.
	 *
	 * @return bool True if there were files packed, false otherwise (empty filelist)
	 */
	private function pack_files()
	{
		// Get a reference to the archiver and the timer classes
		$archiver =& AEFactory::getArchiverEngine();
		$timer =& AEFactory::getTimer();
		$configuration =& AEFactory::getConfiguration();

		// If post-processing after part creation is enabled, make sure we do post-process each part before moving on
		if($configuration->get('engine.postproc.common.after_part',0))
		{
			if(!empty($archiver->finishedPart))
			{
				$filename = array_shift($archiver->finishedPart);
				AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Preparing to post process '.basename($filename));
				$post_proc =& AEFactory::getPostprocEngine();
				$result = $post_proc->processPart( $filename );
				$this->propagateFromObject($post_proc);

				if($result === false)
				{
					$this->setWarning('Failed to process file '.basename($filename));
				}
				else
				{
					AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Successfully processed file '.basename($filename));
				}

				// Should we delete the file afterwards?
				if(
					$configuration->get('engine.postproc.common.delete_after',false)
					&& $post_proc->allow_deletes
					&& ($result !== false)
				)
				{
					AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Deleting already processed file '.basename($filename));
					AEPlatform::unlink($filename);
				}

				if($post_proc->break_after && ($result !== false)) {
					$configuration->set('volatile.breakflag', true);
					return true;
				}
				
				// This is required to let the backup continue even after a post-proc failure
				$this->resetErrors();
				$this->setState('running');
			}
		}

		// If the archiver has work to do, make sure it finished up before continuing
		if( $configuration->get('volatile.engine.archiver.processingfile',false) )
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Continuing file packing from previous step");
			$result = $archiver->addFile('', '', '');
			$this->propagateFromObject($archiver);
			if($this->getError())
			{
				return false;
			}
			
			// If that was the last step, mark a file done
			if( !$configuration->get('volatile.engine.archiver.processingfile',false) ) {
				$this->progressMarkFileDone();
			}
		}

		// Did it finish, or does it have more work to do?
		if( $configuration->get('volatile.engine.archiver.processingfile',false) )
		{
			// More work to do. Let's just tell our parent that we finished up successfully.
			return true;
		}

		// Normal file backup loop; we keep on processing the file list, packing files as we go.
		if( count($this->file_list) == 0 )
		{
			// No files left to pack -- This should never happen! We catch this condition at the end of this method!
			$this->done_scanning = false;
			$this->progressMarkFolderDone();
			
			return false;
		}
		else
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Packing files");
			$packedSize = 0;
			$numberOfFiles = 0;

			list($usec, $sec) = explode(" ", microtime());
			$opStartTime = ((float)$usec + (float)$sec);

			while( (count($this->file_list) > 0) )
			{
				$file = @array_shift($this->file_list);
				$size = @filesize($file);
				// Anticipatory file size algorithm
				if( ($numberOfFiles > 0) && ($size > AELargeFileThreshold) )
				{
					if(!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.beforelargefile',0)) {
						// If the file is bigger than the big file threshold, break the step
						// to avoid potential timeouts
						$this->setBreakFlag();
						AEUtilLogger::WriteLog(_AE_LOG_INFO, "Breaking step _before_ large file: ".$file." - size: ".$size);
						// Push the file back to the list.
						array_unshift($this->file_list, $file);
						// Mark that we are not done packing files
						$this->done_scanning = true;
						return true;
					}
				}

				// Proactive potential timeout detection
				// Rough estimation of packing speed in bytes per second
				list($usec, $sec) = explode(" ", microtime());
				$opEndTime = ((float)$usec + (float)$sec);
				if( ($opEndTime - $opStartTime) == 0 )
				{
					$_packSpeed = 0;
				}
				else
				{
					$_packSpeed = $packedSize / ($opEndTime - $opStartTime);
				}
				// Estimate required time to pack next file. If it's the first file of this operation,
				// do not impose any limitations.
				$_reqTime = ($_packSpeed - 0.01) <= 0 ? 0 : $size / $_packSpeed;
				// Do we have enough time?
				if($timer->getTimeLeft() < $_reqTime )
				{
					if(!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.proactive',0)) {
						array_unshift($this->file_list, $file);
						AEUtilLogger::WriteLog(_AE_LOG_INFO, "Proactive step break - file: ".$file." - size: ".$size." - req. time ".sprintf('%2.2f',$_reqTime) );
						$this->setBreakFlag();
						$this->done_scanning = true;
						return true;
					}
				}

				$packedSize += $size;
				$numberOfFiles++;
				$ret = $archiver->addFile($file, $this->remove_path_prefix, $this->path_prefix);
				// If no more processing steps are required, mark a done file
				if( !$configuration->get('volatile.engine.archiver.processingfile',false) ) {
					$this->progressMarkFileDone();
				}

				// Error propagation
				$this->propagateFromObject($archiver);
				if($this->getError())
				{
					return false;
				}

				// If this was the first file of the fragment and it exceeded the fragment's capacity,
				// break the step. Continuing with more operations after packing such a big file is
				// increasing the risk to hit a timeout.
				if( ($packedSize > AELargeFileThreshold) && ($numberOfFiles == 1) )
				{
					if(!AEFactory::getConfiguration()->get('akeeba.tuning.nobreak.afterlargefile',0)) {
						AEUtilLogger::WriteLog(_AE_LOG_INFO, "Breaking step *after* large file: ".$file." - size: ".$size);
						$this->setBreakFlag();
						return true;
					}
				}
				
				// If we have to continue processing the file, break the file packing loop forcibly
				if( $configuration->get('volatile.engine.archiver.processingfile',false) ) {
					return true;
				}				
			}

			$this->done_scanning = count($this->file_list) > 0;
			if(!$this->done_scanning) $this->progressMarkFolderDone();
			
			return true;
		}
	}
Beispiel #3
0
	/**
	 * Implements the _run() abstract method
	 */
	protected function _run()
	{
		// Check if we are already done
		if ($this->getState() == 'postrun') {
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, __CLASS__." :: Already finished");
			$this->setStep("");
			$this->setSubstep("");
			return;
		}

		// Mark ourselves as still running (we will test if we actually do towards the end ;) )
		$this->setState('running');

		// Check if we are still adding a database dump part to the archive, or if
		// we have to post-process a part
		if( AEUtilScripting::getScriptingParameter('db.saveasname','normal') != 'output' )
		{
			$archiver =& AEFactory::getArchiverEngine();
			$configuration =& AEFactory::getConfiguration();

			if($configuration->get('engine.postproc.common.after_part',0))
			{
				if(!empty($archiver->finishedPart))
				{
					$filename = array_shift($archiver->finishedPart);
					AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Preparing to post process '.basename($filename));
					$post_proc =& AEFactory::getPostprocEngine();
					$result = $post_proc->processPart( $filename );
					$this->propagateFromObject($post_proc);

					if($result === false)
					{
						$this->setWarning('Failed to process file '.basename($filename));
					}
					else
					{
						AEUtilLogger::WriteLog(_AE_LOG_INFO, 'Successfully processed file '.basename($filename));
					}

					// Should we delete the file afterwards?
					if(
						$configuration->get('engine.postproc.common.delete_after',false)
						&& $post_proc->allow_deletes
						&& ($result !== false)
					)
					{
						AEUtilLogger::WriteLog(_AE_LOG_DEBUG, 'Deleting already processed file '.basename($filename));
						AEPlatform::unlink($filename);
					}

					if($post_proc->break_after) {
						$configuration->set('volatile.breakflag', true);
						return;
					}
				}
			}

			if($configuration->get('volatile.engine.archiver.processingfile',false))
			{
				// We had already started archiving the db file, but it needs more time
				$finished = true;
				AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Continuing adding the SQL dump part to the archive");
				$archiver->addFile(null,null,null);
				$this->propagateFromObject($archiver);
				if($this->getError()) return;
				$finished = !$configuration->get('volatile.engine.archiver.processingfile',false);
				if($finished)
				{
					$this->getNextDumpPart();
				}
				else
				{
					return;
				}
			}
		}

		// Initialize local variables
		$db =& $this->getDB();
		if($this->getError()) return;

		if( !is_object($db) || ($db === false) )
		{
			$this->setError(__CLASS__.'::_run() Could not connect to database?!');
			return;
		}

		$outData	= ''; // Used for outputting INSERT INTO commands

		$this->enforceSQLCompatibility(); // Apply MySQL compatibility option
		if($this->getError()) return;

		// Touch SQL dump file
		$nada = "";
		$this->writeline($nada);

		// Get this table's information
		$tableName = $this->nextTable;
		$tableAbstract = trim( $this->table_name_map[$tableName] );
		$dump_records = $this->tables_data[$tableName]['dump_records'];

		// If it is the first run, find number of rows and get the CREATE TABLE command
		if( $this->nextRange == 0 )
		{
			if($this->getError()) return;
			$outCreate = '';
			if(is_array($this->tables_data[$tableName])) {
				if(array_key_exists('create', $this->tables_data[$tableName])) {
					$outCreate = $this->tables_data[$tableName]['create'];
				}
			}
			
			if(empty($outCreate) && !empty($tableName)) {
				// The CREATE command wasn't cached. Time to create it. The $type and $dependencies
				// variables will be thrown away.
				$type = 'table';
				$outCreate = $this->get_create($tableAbstract, $tableName, $type, $dependencies);
			}

			// Write the CREATE command
			if(!$this->writeDump($outCreate)) return;

			// Create drop statements if required (the key is defined by the scripting engine)
			$configuration =& AEFactory::getConfiguration();
			if( AEUtilScripting::getScriptingParameter('db.dropstatements',0) )
			{
				if(array_key_exists('create', $this->tables_data[$tableName])) {
					// @todo This looks cheesy...
					$dropStatement = $this->createDrop($this->tables_data[$tableName]['create']);
				} else {
					$type = 'table';
					$createStatement = $this->get_create($tableAbstract, $tableName, $type, $dependencies);
					$dropStatement = $this->createDrop($createStatement);
				}
				if(!empty($dropStatement))
				{
					if(!$this->writeDump($outCreate)) return;
				}
			}

			if( $dump_records )
			{
				// We are dumping data from a table, get the row count
				$this->getRowCount( $tableAbstract );
			}
			else
			{
				// We should not dump any data
				AEUtilLogger::WriteLog(_AE_LOG_INFO, "Skipping dumping data of " . $tableAbstract);
				$this->maxRange = 0;
				$this->nextRange = 1;
				$outData = '';
				$numRows = 0;
			}
		}

		// Check if we have more work to do on this table
		$configuration =& AEFactory::getConfiguration();
		$batchsize = intval($configuration->get('engine.dump.common.batchsize', 1000));
		if($batchsize <= 0) $batchsize = 1000;
		if( ($this->nextRange < $this->maxRange) )
		{
			$timer =& AEFactory::getTimer();

			// Get the number of rows left to dump from the current table
			$sql = "SELECT * FROM `$tableAbstract`";
			if( $this->nextRange == 0 )
			{
				// First run, get a cursor to all records
				$db->setQuery( $sql, 0, $batchsize );
				AEUtilLogger::WriteLog(_AE_LOG_INFO, "Beginning dump of " . $tableAbstract);
			}
			else
			{
				// Subsequent runs, get a cursor to the rest of the records
				$db->setQuery( $sql, $this->nextRange, $batchsize );
				AEUtilLogger::WriteLog(_AE_LOG_INFO, "Continuing dump of " . $tableAbstract . " from record #{$this->nextRange}");
			}

			$this->query = '';
			$numRows = 0;
			$use_abstract = AEUtilScripting::getScriptingParameter('db.abstractnames', 1);
			while( is_array($myRow = $db->loadAssoc(false)) && ( $numRows < ($this->maxRange - $this->nextRange) ) ) {
				$this->createNewPartIfRequired();
				$numRows++;
				$numOfFields = count( $myRow );

				if(
					(!$this->extendedInserts) || // Add header on simple INSERTs, or...
					( $this->extendedInserts && empty($this->query) ) //...on extended INSERTs if there are no other data, yet
				)
				{
					$newQuery = true;
					if( $numOfFields > 0 ) $this->query = "INSERT INTO `" . (!$use_abstract ? $tableName : $tableAbstract) . "` VALUES ";
				}
				else
				{
					// On other cases, just mark that we should add a comma and start a new VALUES entry
					$newQuery = false;
				}

				$outData = '(';

				// Step through each of the row's values
				$fieldID = 0;

				// Used in running backup fix
				$isCurrentBackupEntry = false;

				// Fix 1.2a - NULL values were being skipped
				if( $numOfFields > 0 ) foreach( $myRow as $value )
				{
					// The ID of the field, used to determine placement of commas
					$fieldID++;

					// Fix 2.0: Mark currently running backup as successful in the DB snapshot
					if($tableAbstract == '#__ak_stats')
					{
						if($fieldID == 1)
						{
							// Compare the ID to the currently running
							$statistics =& AEFactory::getStatistics();
							$isCurrentBackupEntry = ($value == $statistics->getId());
						}
						elseif ($fieldID == 6)
						{
							// Treat the status field
							$value = $isCurrentBackupEntry ? 'complete' : $value;
						}
					}

					// Post-process the value
					if( is_null($value) )
					{
						$outData .= "NULL"; // Cope with null values
					} else {
						// Accommodate for runtime magic quotes
						$value = @get_magic_quotes_runtime() ? stripslashes( $value ) : $value;
						$outData .= $db->Quote($value);
					}
					if( $fieldID < $numOfFields ) $outData .= ', ';
				} // foreach
				$outData .= ')';

				if( $numOfFields )
				{
					// If it's an existing query and we have extended inserts
					if($this->extendedInserts && !$newQuery)
					{
						// Check the existing query size
						$query_length = strlen($this->query);
						$data_length = strlen($outData);
						if( ($query_length + $data_length) > $this->packetSize )
						{
							// We are about to exceed the packet size. Write the data so far.
							$this->query .= ";\n";
							if(!$this->writeDump($this->query)) return;
							// Then, start a new query
							$this->query = '';
							$this->query = "INSERT INTO `" . (!$use_abstract ? $tableName : $tableAbstract) . "` VALUES ";
							$this->query .= $outData;
						}
						else
						{
							// We have room for more data. Append $outData to the query.
							$this->query .= ', ';
							$this->query .= $outData;
						}
					}
					elseif($this->extendedInserts && $newQuery)
					// If it's a brand new insert statement in an extended INSERTs set
					{
						// Append the data to the INSERT statement
						$this->query .= $outData;
						// Let's see the size of the dumped data...
						$query_length = strlen($this->query);
						if($query_length >= $this->packetSize)
						{
							// This was a BIG query. Write the data to disk.
							$this->query .= ";\n";
							if(!$this->writeDump($this->query)) return;
							// Then, start a new query
							$this->query = '';
						}
					}
					else
					// It's a normal (not extended) INSERT statement
					{
						// Append the data to the INSERT statement
						$this->query .= $outData;
						// Write the data to disk.
						$this->query .= ";\n";
						if(!$this->writeDump($this->query)) return;
						// Then, start a new query
						$this->query = '';
					}
				}
				$outData = '';

				// Check for imminent timeout
				if( $timer->getTimeLeft() <= 0 ) {
					AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Breaking dump of $tableAbstract after $numRows rows; will continue on next step");
					break;
				}
			} // for (all rows left)

			// Advance the _nextRange pointer
			$this->nextRange += ($numRows != 0) ? $numRows : 1;

			$this->setStep($tableName);
			$this->setSubstep($this->nextRange . ' / ' . $this->maxRange);
		} // if more work on the table

		// Finalize any pending query
		// WARNING! If we do not do that now, the query will be emptied in the next operation and all
		// accumulated data will go away...
		if(!empty($this->query))
		{
			$this->query .= ";\n";
			if(!$this->writeDump($this->query)) return;
			$this->query = '';
		}

		// Check for end of table dump (so that it happens inside the same operation)
		if( !($this->nextRange < $this->maxRange) )
		{
			// Tell the user we are done with the table
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Done dumping " . $tableAbstract);

			if(count($this->tables) == 0)
			{
				// We have finished dumping the database!
				AEUtilLogger::WriteLog(_AE_LOG_INFO, "End of database detected; flushing the dump buffers...");
				$null = null;
				$this->writeDump($null);
				AEUtilLogger::WriteLog(_AE_LOG_INFO, "Database has been successfully dumped to SQL file(s)");
				$this->setState('postrun');
				$this->setStep('');
				$this->setSubstep('');
				$this->nextTable = '';
				$this->nextRange = 0;
			} elseif(count($this->tables) != 0) {
				// Switch tables
				$this->nextTable = array_shift( $this->tables );
				$this->nextRange = 0;
				$this->setStep($this->nextTable);
				$this->setSubstep('');
			}
		}

		$null = null;
		$this->writeline($null);
	}
Beispiel #4
0
	public function apply_srp_quotas($parent) {
		$parent->relayStep('Applying quotas');
		$parent->relaySubstep('');
		
		// If no quota settings are enabled, quit
		$registry =& AEFactory::getConfiguration();
		$srpQuotas = $registry->get('akeeba.quota.srp_size_quota');
		
		if($srpQuotas <= 0)
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "No restore point quotas were defined; old restore point files will be kept intact" );
			return true; // No quota limits were requested
		}
		
		// Get valid-looking backup ID's
		$validIDs =& AEPlatform::get_valid_backup_records(true, array('restorepoint'));
		
		$statistics =& AEFactory::getStatistics();
		$latestBackupId = $statistics->getId();
		
		// Create a list of valid files
		$allFiles = array();
		if(count($validIDs))
		{
			foreach($validIDs as $id)
			{
				$stat = AEPlatform::get_statistics($id);
				// Multipart processing
				$filenames = AEUtilStatistics::get_all_filenames($stat, true);
				if(!is_null($filenames))
				{
					// Only process existing files
					$filesize = 0;
					foreach($filenames as $filename)
					{
						$filesize += @filesize($filename);
					}
					$allFiles[] = array('id' => $id, 'filenames' => $filenames, 'size' => $filesize);
				}
			}
		}
		unset($validIDs);
		
		// If there are no files, exit early
		if(count($allFiles) == 0)
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "There were no old restore points to apply quotas on" );
			return true;
		}
		
		// Init arrays
		$killids = array();
		$ret = array();
		$leftover = array();
		
		// Do we need to apply size quotas?
		AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Processing restore point size quotas" );
		// OK, let's start counting bytes!
		$runningSize = 0;
		while(count($allFiles) > 0)
		{
			// Each time, remove the last element of the backup array and calculate
			// running size. If it's over the limit, add the archive to the return array.
			$def = array_pop($allFiles);
			$runningSize += $def['size'];
			if($runningSize >= $srpQuotas)
			{
				if($latestBackupId == $def['id'])
				{
					$runningSize -= $def['size'];
				}
				else
				{
					$ret[] = $def['filenames'];
					$killids[] = $def['filenames'];
				}
			}
		}
		
		// Convert the $ret 2-dimensional array to single dimensional
		$quotaFiles = array();
		foreach($ret as $temp)
		{
			foreach($temp as $filename)
			{
				$quotaFiles[] = $filename;
			}
		}
		
		// Update the statistics record with the removed remote files
		if(!empty($killids)) foreach($killids as $id) {
			$data = array('filesexist' => '0');
			AEPlatform::set_or_update_statistics($id, $data, $parent);
		}
		
		// Apply quotas
		if(count($quotaFiles) > 0)
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Applying quotas" );
			jimport('joomla.filesystem.file');
			foreach($quotaFiles as $file)
			{
				if(!@AEPlatform::unlink($file))
				{
					$parent->setWarning("Failed to remove old system restore point file ".$file );
				}
			}
		}
		
		return true;
	}
Beispiel #5
0
	/**
	 * Applies the size and count quotas
	 * @return bool
	 */
	private function apply_quotas()
	{
		$this->setStep('Applying quotas');
		$this->setSubstep('');

		// If no quota settings are enabled, quit
		$registry =& AEFactory::getConfiguration();
		$useDayQuotas = $registry->get('akeeba.quota.maxage.enable');
		$useCountQuotas = $registry->get('akeeba.quota.enable_count_quota');
		$useSizeQuotas = $registry->get('akeeba.quota.enable_size_quota');
		if(! ($useDayQuotas || $useCountQuotas || $useSizeQuotas) )
		{
			$this->apply_obsolete_quotas();

			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "No quotas were defined; old backup files will be kept intact" );
			return true; // No quota limits were requested
		}

		// Try to find the files to be deleted due to quota settings
		$statistics =& AEFactory::getStatistics();
		$latestBackupId = $statistics->getId();

		// Get quota values
		$countQuota = $registry->get('akeeba.quota.count_quota');
		$sizeQuota = $registry->get('akeeba.quota.size_quota');
		$daysQuota = $registry->get('akeeba.quota.maxage.maxdays');
		$preserveDay = $registry->get('akeeba.quota.maxage.keepday');

		// Get valid-looking backup ID's
		$validIDs =& AEPlatform::get_valid_backup_records(true, array('NOT','restorepoint'));

		// Create a list of valid files
		$allFiles = array();
		if(count($validIDs))
		{
			foreach($validIDs as $id)
			{
				$stat = AEPlatform::get_statistics($id);
				try {
					$backupstart = new DateTime($stat['backupstart']);
					$backupTS = $backupstart->format('U');
					$backupDay = $backupstart->format('d');
				} catch (Exception $e) {
					$backupTS = 0;
					$backupDay = 0;
				}
				// Multipart processing
				$filenames = AEUtilStatistics::get_all_filenames($stat, true);
				if(!is_null($filenames))
				{
					// Only process existing files
					$filesize = 0;
					foreach($filenames as $filename)
					{
						$filesize += @filesize($filename);
					}
					$allFiles[] = array('id' => $id, 'filenames' => $filenames, 'size' => $filesize, 'backupstart' => $backupTS, 'day' => $backupDay);
				}
			}
		}
		unset($validIDs);

		// If there are no files, exit early
		if(count($allFiles) == 0)
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "There were no old backup files to apply quotas on" );
			return true;
		}

		// Init arrays
		$killids = array();
		$ret = array();
		$leftover = array();
		
		// Do we need to apply maximum backup age quotas?
		if($useDayQuotas) {
			$killDatetime = new DateTime();
			$killDatetime->sub(new DateInterval('P'.$daysQuota.'D'));
			$killTS = $killDatetime->format('U');
			foreach($allFiles as $file) {
				// Is this on a preserve day?
				if($preserveDay > 0) {
					if($preserveDay == $file['day']) {
						$leftover[] = $file;
						continue;
					}
				}
				// Otherwise, check the timestamp
				if($file['backupstart'] < $killTS) {
					$ret[] = $file['filenames'];
					$killids[] = $file['id'];
				} else {
					$leftover[] = $file;
				}
			}
		}

		// Do we need to apply count quotas?
		if($useCountQuotas && is_numeric($countQuota) && !($countQuota <= 0) && !$useDayQuotas )
		{
			// Are there more files than the quota limit?
			if( !(count($allFiles) > $countQuota) )
			{
				// No, effectively skip the quota checking
				$leftover =& $allFiles;
			}
			else
			{
				AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Processing count quotas" );
				// Yes, aply the quota setting. Add to $ret all entries minus the last
				// $countQuota ones.
				$totalRecords = count($allFiles);
				$checkLimit = $totalRecords - $countQuota;
				// Only process if at least one file (current backup!) is to be left
				for($count = 0; $count < $totalRecords; $count++)
				{
					$def = array_pop($allFiles);
					if(count($ret) < $checkLimit)
					{
						if($latestBackupId != $def['id']) {
							$ret[] = $def['filenames'];
							$killids[] = $def['id'];
						}
					}
					else
					{
						$leftover[] = $def;
					}
				}
				unset($allFiles);
			}
		}
		else
		{
			// No count quotas are applied
			$leftover =& $allFiles;
		}

		// Do we need to apply size quotas?
		if( $useSizeQuotas && is_numeric($sizeQuota) && !($sizeQuota <= 0) && (count($leftover) > 0) && !$useDayQuotas )
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Processing size quotas" );
			// OK, let's start counting bytes!
			$runningSize = 0;
			while(count($leftover) > 0)
			{
				// Each time, remove the last element of the backup array and calculate
				// running size. If it's over the limit, add the archive to the return array.
				$def = array_pop($leftover);
				$runningSize += $def['size'];
				if($runningSize >= $sizeQuota)
				{
					if($latestBackupId == $def['id'])
					{
						$runningSize -= $def['size'];
					}
					else
					{
						$ret[] = $def['filenames'];
						$killids[] = $def['filenames'];
					}
				}
			}
		}

		// Convert the $ret 2-dimensional array to single dimensional
		$quotaFiles = array();
		foreach($ret as $temp)
		{
			foreach($temp as $filename)
			{
				$quotaFiles[] = $filename;
			}
		}
		
		// Update the statistics record with the removed remote files
		if(!empty($killids)) foreach($killids as $id) {
			$data = array('filesexist' => '0');
			AEPlatform::set_or_update_statistics($id, $data, $this);
		}

		// Apply quotas
		if(count($quotaFiles) > 0)
		{
			AEUtilLogger::WriteLog(_AE_LOG_DEBUG, "Applying quotas" );
			jimport('joomla.filesystem.file');
			foreach($quotaFiles as $file)
			{
				if(!@AEPlatform::unlink($file))
				{
					$this->setWarning("Failed to remove old backup file ".$file );
				}
			}
		}

		$this->apply_obsolete_quotas();

		return true;
	}