Location: PHPKode > projects > psDarBackup > psDarBackup-1.1/class.backup.php5
<?php
	
	/*
	 * psDarBackup
	 * Copyright (C) 2005, Patrick Schwarz
	 * 
	 * This program is free software; you can redistribute it and/or
	 * modify it under the terms of the GNU General Public License as
	 * published by the Free Software Foundation; either version 2 of
	 * the License, or (at your option) any later version.
	 * 
	 * This program is distributed in the hope that it will be useful,
	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	 * GNU General Public License for more details.
	 *
	 * You should have received a copy of the GNU General Public License
	 * along with this program; if not, visit http://www.gnu.org/licenses/gpl.txt
	 */
	
	require_once('class.exceptions.php5');
	require_once('class.backupDirectory.php5');
	
	// PEAR
	require_once('Config.php');
	require_once('Console/ProgressBar.php');

	class PsDarBackup {
		
//		const CONFIG_FILE = './config/config.xml';
		const BR = "\n";
		
		private $configReader;
		private $config;
		private $type;
		private $destinationDirectory;
		private $configPath;
		private $configXmlExtensions;
		private $backupDirectories;
		
		public function __construct($configPath, $configXmlExtensions) {
		
			$this->configPath = $configPath;
			$this->correctPath($this->configPath);
			$this->configXmlExtensions = $configXmlExtensions;
			
			$this->readConfig();
			
		}
		
		/**
		 * Hauptroutine, die für den Programmablauf verantwortlich ist
		 */
		public function make() {
		
			$this->chooseType();
			
			try {
				$this->setEnvironment();
			} catch(EnvironmentException $e) {
				$this->exitDueToException($e);
			}
			
			$this->makeBackup();
			
		}
		
		/**
		 * veranlasst, je nach Wahl des Benutzers, das Erstellen des
		 * entsprechenden Backups
		 */
		private function makeBackup() {
		
			switch($this->type) {
				
				case 'full':
					$this->createFullBackup();
					break;
				case 'incr':
					$this->createIncrementalBackup();
					break;
				
			}
		
		}
		
		private function createFullBackup() {
			$this->createBackup();
		}
		
		private function createIncrementalBackup() {
			
			$referenceBackup = $this->chooseReferenceBackup();
			$this->createBackup($referenceBackup);
			
		}
		
		private function chooseReferenceBackup() {
		
			$output = 'Bitte geben Sie die Daten zur Wahl des Referenzbackups ein:' . self::BR;
			
			switch($this->chooseTypeForReferenceBackup()) {
				case 1:
					$type = 'full';
					break;
				case 2:
					$type = 'incr';
					break;
			}
			
			$this->printToConsole('Datum des Referenzbackups (Format: yyyymmdd): ');
			$date = $this->readConsoleInput();
			
			$this->printToConsole('Tagesnummer des Referenzbackups: ');
			$number = $this->readConsoleInput();
			
			$referenceBackup = $this->config['@']['destDir'] . $type . '/' . $date . '_' . $number . '/';
			if(!is_dir($referenceBackup)) {
				$this->printToConsole('Das angegebene Backup konnte nicht gefunden werden.' . self::BR, true);
				$referenceBackup = $this->chooseReferenceBackup();
			}
			
			return $referenceBackup;
			
		}
		
		private function chooseTypeForReferenceBackup() {
		
			$output = 'Auswahl des Referenzbackuptyps: ' . self::BR;
			$output .= '(0) abbrechen' . self::BR;
			$output .= '(1) Volles Backup' . self::BR;
			$output .= '(2) Inkrementelles Backup' . self::BR;
			$this->printToConsole($output);
			
			$choice = $this->readConsoleInput();
			if($choice == 0)
				exit;
			
			if($choice < 0 || $choice > 2)
				$choice = $this->chooseTypeForReferenceBackup();
			
			return $choice;
		
		}
		
		/**
		 * erstellt ein volles Backup
		 */
		private function createBackup($referenceBackup = null) {
			
			// Dar-Verzeichnis
			$execute = '%sdar -N ';
			
			// Weitere Parameter
			$execute .= ' %s ';
			
			// Split-Angabe
			$execute .= '-s %s ';
			
			// Quellverzeichnis
			$execute .= '-m 256 -z -D -R "%s" ';
			
			// Zieldatei
			$execute .= '-c "%s" -Z "*.gz" -Z "*.bz2" -Z "*.zip" -Z "*.png"';
			
			// Beginndatum
			$this->printToLogfile('Beginn um ' . date('H:i:s') . ' am ' . date('d.m.Y') . str_repeat(self::BR, 2), true);
			
			// Gesamtverzeichnisgröße ermitteln
			$totalDirSize = 1;
			foreach($this->backupDirectories as $dir) {
				$totalDirSize += $dir->getSize();
			}
			
			// Progressbar hinzufügen und initialisieren
			$processedDirSize = 0;
			$progressBar = new Console_ProgressBar('[%bar%] %percent%', '=', ' ', 80, $totalDirSize);
			$progressBar->update($totalDirSize / 100); // Bug in der Progressbar, Mindestanzeige: 1%
			
			foreach($this->backupDirectories as $dir) {
				
				if(is_null($referenceBackup) && $dir->getFull() == 'no')
					continue;
				
				if(!is_null($referenceBackup) && $dir->getIncremental() == 'no')
					continue;
				
				$this->printToLogfile('Verarbeite ' . $dir->getName() . ' (' . $dir->getPath() .')');
				
				// weitere Parameter für DAR ausfüllen
				$additionalParameter = '';
				
				// bei einem inkrementellem Backup Referenz übergeben
				if(!is_null($referenceBackup)) {
					$referenceBackupForDirectory = $referenceBackup . $dir->getName();
					
					if(!is_file($referenceBackupForDirectory . '.1.dar')) {
						$this->printToConsole('Fuer "' . $dir->getName() . '" konnte kein Referenzbackup gefunden werden, bitte waehlen Sie _fuer dieses Backup_ ein neues Referenzbackup aus:' . self::BR);
						$referenceBackupForDirectory = $this->chooseReferenceBackup() . $dir->getName();
					}
					
					$additionalParameter .= '-A ' . $referenceBackupForDirectory;
					
					$this->printToLogfile(self::BR . 'Referenzbackup: ' . $referenceBackupForDirectory, true);
				}
				
				$output = shell_exec(sprintf($execute, $this->config['dar']['@']['directory'], $additionalParameter, $this->config['dar']['@']['split'], $dir->getPath(), $this->destinationDirectory . $dir->getName()));
				$this->printToLogfile($output, true);
				
				// evtl noch erstellte Datei testen
				if($this->config['dar']['@']['test'] == 'yes')
					$this->testBackup($dir);
				
				$this->printToLogfile(str_repeat(self::BR, 2), true);
				
				$processedDirSize += $dir->getSize();
				$progressBar->update($processedDirSize);
				
			}
			
			// Enddatum
			$this->printToLogfile('Fertig um ' . date('H:i:s') . ' am ' . date('d.m.Y'));
			
		}
		
		/**
		 * testet ein erstelltes Backup
		 */
		private function testBackup($dir) {
			
			// Dar-Verzeichnis
			$execute = '%sdar -N ';
			
			// zu testende Datei
			$execute .= '-t %s';
			
			$output = shell_exec(sprintf($execute, $this->config['dar']['@']['directory'], $this->destinationDirectory . $dir->getName()));
			$this->printToLogfile($output, true);
			
		}
		
		/**
		 * testet, ob die Umgebung nutzbar ist und erstellt das Backupverzeichnis
		 */
		private function setEnvironment() {
			
			$destinationDirectory = $this->config['@']['destDir'] . $this->type . '/';
			
			if(!is_dir($destinationDirectory) && is_writable(realpath($destinationDirectory . '../')))
				mkdir($destinationDirectory);
			
			if(!is_dir($destinationDirectory))
				throw new EnvironmentException('Das Backupverzeichnis ' . $destinationDirectory . ' existiert nicht.');
			
			if(!is_writable($destinationDirectory))
				throw new EnvironmentException('Das Backupverzeichnis ' . $destinationDirectory . ' kann nicht beschrieben werden.');
			
			$this->destinationDirectory = $this->extendDestinationDirectory($destinationDirectory);
			mkdir($this->destinationDirectory);
			
		}
		
		/**
		 * erweitert das übergebene Zielverzeichnis um das Datumsverzeichnis
		 * d.h. z.B. c:/backups/full => c:/backups/full/20050901_1/
		 */
		private function extendDestinationDirectory($destinationDirectory) {
		
			$i = 1;
			$destinationDirectory .= date('Ymd') . '_';
			
			while(is_dir($destinationDirectory . $i))
				$i++;
				
			return $destinationDirectory . $i . '/';
			
		}
		
		/**
		 * wählt aus, ob full oder incremental Backup
		 */
		private function chooseType() {
			
			$output = 'Bitte waehlen Sie den Backup-Typ aus:' . self::BR;
			$output .= '(0) abbrechen' . self::BR;
			$output .= '(1) Voll' . self::BR;
			$output .= '(2) Inkrementell' . self::BR;
			$this->printToConsole($output);
			
			$choice = $this->readConsoleInput();
			
			switch($choice) {
				case 0:
					exit;
					break;
				case 1:
					$this->type = 'full';
					break;
				case 2:
					$this->type = 'incr';
					break;
				default:
					print('Sie müssen eine gueltige Wahl treffen!' . self::BR);
					$this->chooseType();
					break;
			}
			
		}
		
		/**
		 * liest die Konfigurationseinstellungen ein
		 */
		private function readConfig() {
			
			try {
				$configFile = $this->chooseConfigFile();
			} catch(ConfigException $e) {
				$this->exitDueToException($e);
			}
			
			$this->configReader = new Config();
			
			$root = $this->configReader->parseConfig($configFile, 'XML');
			if(PEAR::isError($root))
				throw new ConfigException($root->getMessage());
			
			$tempSettings = $root->toArray();
            $this->config = $tempSettings['root'][$this->getConfigRootName($tempSettings['root'])];
            
            // Workaround, da wenn nur ein Backup-Verzeichnis angegeben wird,
            // dieses direkt unter $this->config['directories']['directory']
			// gespeichert wird
            if(!isset($this->config['directories']['directory'][0])) {
				$this->config['directories']['directory'] = array($this->config['directories']['directory']);
			}
            
			try {
            	$this->validateConfig();
            } catch(ConfigException $e) {
            	$this->exitDueToException($e);
            }
            
            try {
            	$this->readBackupDirectories();
            } catch(ConfigException $e) {
            	$this->exitDueToException($e);
            }
            
            $this->consolidateConfig();
            
		}
		
		/**
		 * liest Meta-Informationen der Verzeichnisse aus dem directories-Tag
		 * ein und speichert sie in Objekten der Klasse BackupDirectoy 
		 */
		private function readBackupDirectories() {
			
			$this->backupDirectories = array();
			BackupDirectory::setPsDarBackup($this);
			
			foreach($this->config['directories']['directory'] as $dir) {
				
				$name = (isset($dir['@']['name'])) ? $dir['@']['name'] : null;
				$path = (isset($dir['@']['path'])) ? $dir['@']['path'] : null;
				$incr = (isset($dir['@']['incr'])) ? $dir['@']['incr'] : null;
				$full = (isset($dir['@']['full'])) ? $dir['@']['full'] : null;
				
				try {
					
					$backupDirectory = new BackupDirectory($name, $path, $incr, $full);
					
					if(isset($this->backupDirectories[$backupDirectory->getName()]))
						throw new ConfigException('Der Backupname "' . $backupDirectory->getName() . '" ist nicht eindeutig.');
					
					$this->backupDirectories[$backupDirectory->getName()] = $backupDirectory;
					
				} catch(ConfigException $e) {
					$this->exitDueToException($e);
				}
				
			}
			
		}
		
		/**
		 * setzt die Default-Werte bei nicht angegebenen Einstellungen
		 */
		private function consolidateConfig() {
			
			$this->correctPath($this->config['@']['destDir']);
			$this->correctPath($this->config['dar']['@']['directory']);
			
			if(!isset($this->config['dar']['@']['split']))
				$this->config['dar']['@']['split'] = '1G';
			
			if(!isset($this->config['dar']['@']['test']))
				$this->config['dar']['@']['test'] = 'yes';
			
		}
		
		/**
		 * korrigiert den Stil von Pfadangaben
		 * (Slash am Ende, kein Backslash, keine doppelten Slashs)
		 */
		public function correctPath(&$path) {
		
			$path = str_replace('\\', '/', $path);
			$path = str_replace('//', '/', $path);
			if(substr($path, -1) !== '/')
				$path .= '/';
			
		}
		
		/**
		 * lässt den Benutzer die gewünschte Konfigurationsdatei auswählen
		 */
		private function chooseConfigFile() {
			
			$configFiles = glob($this->configPath . '{' . $this->configXmlExtensions . '}', GLOB_BRACE);
			
			$this->printToConsole('Bitte waehlen Sie eine Konfigurationsdatei aus:' . self::BR, true);
			
			if(count($configFiles) === 0)
				throw new ConfigException('Es wurde keine Konfigurationsdateien gefunden. Bitte legen Sie zuerst eine an.');
			
			// Konfigurationsdateien ausgeben
			$output = '(0) abbrechen' . self::BR;
			foreach($configFiles as $number => $configFile)
				$output .= '(' . ($number + 1) . ') ' . str_replace($this->configPath, '', $configFile) . self::BR;
			$this->printToConsole($output, true);
			
			// Gewählte Datei bestimmen
			$choice = $this->readConsoleInput();
			
			if($choice < 0 || $choice > count($configFiles)) {
			
				$this->printToConsole('Bitte waehlen Sie eine gueltige Konfigurationsdatei aus.');
				return $this->chooseConfigFile();
				
			} elseif($choice == 0) {
			
				exit;
				
			} else {
			
				return $configFiles[$choice - 1];
				
			}
						
		}
		 
//		private function chooseConfigFile() {
//		
//			$configFile = PsDarBackup::CONFIG_FILE;
//			
//			while(!is_file($configFile)) {
//				print('Die Datei "' . $configFile . '" wurde nicht gefunden.');
//				$file = $this->chooseConfigFile();
//			}
//			
//			print('Benutze "' . $configFile . '" als Konfigurationsdatei.' . PsDarBackup::BR);
//			
//			return $configFile;
//		}
		
		/**
		 * bricht die Programmausführung mit der Ausgabe einer Fehlermeldung ab
		 */
		private function exitDueToException($e) {
			print($e);
			exit;
		}
		
		/**
		 * überprüft, ob es sich um eine valide Konfigurationsdatei handelt
		 */
		private function validateConfig() {
			
			if(!is_array($this->config))
				throw new ConfigException('Die angegebene Config-Datei muss eine XML-Datei sein.');
			
			if(!isset($this->config['dar']))
				throw new ConfigException('Sie muessen das "dar"-Tag angeben.');
			
			if(!isset($this->config['dar']['@']['directory']))
				throw new ConfigException('Sie muessen das "dar"-Programmverzeichnis angeben.');
			
			if(!preg_match('#^(([1-3]?[0-9]{1,3}M)|([1-3]G))$#', $this->config['dar']['@']['split']))
				throw new ConfigException('Sie muessen eine gültige Split-Angabe machen oder das Attribut loeschen.');
			
			if(!isset($this->config['@']['destDir']))
				throw new ConfigException('Sie muessen das Root-Zielverzeichnis für die Backups angeben.');
			
			if(!isset($this->config['directories']))
				throw new ConfigException('Sie muessen das "directory"-Tag angeben.');
			
		}
		
		/**
		 * @return gibt den Name des Root-Tags einer von Config geparsten XML-Datei zurück
		 */
		private function getConfigRootName($array) {
		
			foreach($array as $key => $value)
				return $key;
			
		}
		
		/**
		 * gibt einen String auf der Konsole aus
		 */
		private function printToConsole($string) {
			
			@ob_end_flush();
			ob_implicit_flush(true);
			print($string);
			ob_implicit_flush(false);
			
		}
		
		/**
		 * schreibt einen String in die Logdatei des jeweiligen Backups
		 */
		private function printToLogfile($string, $normalise = false) {
			
			if($normalise === true)
				$string = str_replace("\n", "\r\n", $string);
			
			file_put_contents($this->destinationDirectory . 'log.txt', $string, FILE_APPEND);
			
		}
		
		/**
		 * @return gibt die Konsoleneingabe zurück
		 */
		private function readConsoleInput() {
		
			$fp = fopen("php://stdin", "r");
			$in = fgets($fp, 4094);
			fclose($fp);
		
			$read = str_replace("\r\n", "", $in);
		
			return $read;
			
		}
	
	}

?>
Return current item: psDarBackup