<?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;
}
}
?>