Skip to content

Commit

Permalink
Implement ZIP backup reader.
Browse files Browse the repository at this point in the history
  • Loading branch information
yunosh committed May 16, 2017
1 parent 235de68 commit 42a6c99
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 14 deletions.
122 changes: 113 additions & 9 deletions framework/Backup/lib/Horde/Backup/Reader.php
Expand Up @@ -16,8 +16,8 @@
use Horde_Compress_Zip as Zip;
use Horde_Pack_Driver_Json as Json;
use Horde\Backup\Exception;
use Horde\Backup\Reader\ZipIterator;
use Horde\Backup\Translation;
use Horde\Backup\Users;

/**
* The backup reader class that reads backups from the backup directory.
Expand All @@ -37,13 +37,6 @@ class Reader
*/
protected $_dir;

/**
* Handles for backup data.
*
* @var array
*/
protected $_backups = array();

/**
* Constructor.
*
Expand Down Expand Up @@ -71,10 +64,121 @@ public function __construct($directory)
}
}

/**
* Returns the available user backups.
*
* @return Iterator A list of user backups.
*/
public function listBackups()
{
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$this->_dir,
\FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS
)
);
}

/**
* Returns user data from backups.
*
* @param array $users A list of users to restore. Defaults to all
* backups.
* @param array $applications A list of applications to restore. Defaults
* to all backups.
*/
public function restore(
array $users = array(), $applications = array()
)
{
if ($users) {
$backups = new ArrayIterator($this->_getBackupFiles($users));
} else {
$backups = $this->listBackups();
}

$data = array();
foreach ($backups as $file) {
switch (substr($file, -4)) {
case '.zip':
$data = array_merge_recursive(
$data,
$this->_restoreFromZip($file, $applications)
);
break;
case '.tar':
$data = array_merge_recursive(
$data,
$this->_restoreFromTar($file, $applications)
);
break;
}
}

return $data;
}

/**
* Restores user data from a ZIP file.
*
* @param string $file Pathname to a ZIP backup file.
* @param array $applications A list of applications to restore. Defaults
* to all backups.
*
* @return \Horde\Backup\Collection[] All restored object collections.
*/
protected function _restoreFromZip($file, $applications)
{
$user = basename($file, '.zip');
$contents = file_get_contents($file);
$packer = new Json();
$compress = new Zip();
$files = $compress->decompress(
$contents, array('action' => Zip::ZIP_LIST)
);

$data = array();
foreach ($files as $key => $info) {
$path = explode('/', $info['name']);
if (!$applications || in_array($path[0], $applications)) {
$data[$path[0]][$path[1]] = true;
}
}

$collections = array();
foreach ($data as $application => $resources) {
$collections[$application] = array();
foreach (array_keys($resources) as $resource) {
$collections[$application][] = new Collection(
new ZipIterator($contents, $application, $resource, $files),
$user,
$resource
);
}
}

return $collections;
}


/**
* Builds a backup iterator for individual users.
*
* @param array $users A list of users.
*
* @return array A list of backup files.
*/
public function restore()
protected function _getBackupFiles(array $users)
{
$files = array();
foreach ($users as $user) {
$file = $this->_temp . '/' . $user;
if (file_exists($file . '.zip')) {
$files[] = $file . '.zip';
} elseif (file_exists($file . '.tar')) {
$files[] = $file . '.tar';
}
}
return $files;
}
}
106 changes: 106 additions & 0 deletions framework/Backup/lib/Horde/Backup/Reader/ZipIterator.php
@@ -0,0 +1,106 @@
<?php
/**
* Copyright 2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
*
* @author Jan Schneider <jan@horde.org>
* @category Horde
* @license http://www.horde.org/licenses/bsd BSD
* @package Backup
*/

namespace Horde\Backup\Reader;

use ArrayIterator;
use CallbackFilterIterator;
use IteratorIterator;
use Horde_Compress_Zip as Zip;
use Horde_Pack_Driver_Json as Json;

/**
* Iterates over certain files in a ZIP archive.
*
* @author Jan Schneider <jan@horde.org>
* @category Horde
* @copyright 2017 Horde LLC
* @license http://www.horde.org/licenses/bsd BSD
* @package Backup
*/
class ZipIterator extends IteratorIterator
{
/**
* The ZIP file contents.
*
* @var string
*/
protected $_contents;

/**
* Archive info from Horde_Compress_Zip.
*
* @var array
*/
protected $_info;

/**
* The ZIP uncompressor.
*
* @var Horde_Compress_Zip
*/
protected $_compress;

/**
* The JSON unpacker.
*
* @var Horde_Pack_Driver_Json
*/
protected $_packer;

/**
* Constructor.
*
* @param string $contents The ZIP file contents.
* @param string $application An application name.
* @param string $type A collection type like "calendar" or
* "contact".
* @param array $info ZIP archive info from Horde_Compress_Zip.
*/
public function __construct($contents, $application, $type, $info)
{
$this->_contents = $contents;
$this->_info = $info;
$this->_packer = new Json();
$this->_compress = new Zip();

$iterator = new CallbackFilterIterator(
new ArrayIterator($info),
function ($current, $key, $iterator) use ($application, $type)
{
$path = explode('/', $current['name']);
return $application == $path[0] && $type == $path[1];
}
);
parent::__construct($iterator);
}

/**
* Returns the unpacked backup data.
*
* @return \Horde\Backup\User
*/
public function current()
{
return $this->_packer->unpack(
$this->_compress->decompress(
$this->_contents,
array(
'action' => Zip::ZIP_DATA,
'info' => $this->_info,
'key' => $this->key()
)
)
);
}
}
4 changes: 4 additions & 0 deletions framework/Backup/package.xml
Expand Up @@ -35,6 +35,9 @@
<dir name="lib">
<dir name="Horde">
<dir name="Backup">
<dir name="Reader">
<file name="ZipIterator.php" role="php" />
</dir> <!-- /lib/Horde/Backup/Reader -->
<file name="Collection.php" role="php" />
<file name="Exception.php" role="php" />
<file name="Reader.php" role="php" />
Expand Down Expand Up @@ -89,6 +92,7 @@
<install as="Horde/Backup/User.php" name="lib/Horde/Backup/User.php" />
<install as="Horde/Backup/Users.php" name="lib/Horde/Backup/Users.php" />
<install as="Horde/Backup/Writer.php" name="lib/Horde/Backup/Writer.php" />
<install as="Horde/Backup/Reader/ZipIterator.php" name="lib/Horde/Backup/Reader/ZipIterator.php" />
<install as="Horde/Backup/AllTests.php" name="test/Horde/Backup/AllTests.php" />
<install as="Horde/Backup/ApplicationTest.php" name="test/Horde/Backup/ApplicationTest.php" />
<install as="Horde/Backup/BackupRestoreTest.php" name="test/Horde/Backup/BackupRestoreTest.php" />
Expand Down
63 changes: 60 additions & 3 deletions framework/Backup/test/Horde/Backup/BackupRestoreTest.php
Expand Up @@ -35,11 +35,11 @@ class BackupRestoreTest extends TestCase

protected function _createBackup()
{
$this->_temp = sys_get_temp_dir() . '/' . uniqid();
$this->_temp = sys_get_temp_dir() . '/' . uniqid('horde_backup_');
mkdir($this->_temp);
}

protected function _cleanBackup()
public function tearDown()
{
if (!is_dir($this->_temp) || !$this->_clean) {
return;
Expand All @@ -57,11 +57,68 @@ public function testBackupMultipleUsers()
$application = new Stub\Application();
$application2 = new Stub\Application2();
$backup->backup('calendar', $application->backup());
$backup->backup('adressbook', $application2->backup());
$backup->backup('addressbook', $application2->backup());
$backup->save();
$this->assertFileExists($this->_temp . '/john.zip');
$this->assertFileExists($this->_temp . '/jane.zip');
$this->_clean = false;
return $this->_temp;
}

/**
* @depends testBackupMultipleUsers
*/
public function testRestoreMultipleUsers($temp)
{
$this->_temp = $temp;
$backup = new Reader($this->_temp);
$users = iterator_to_array($backup->listBackups());
sort($users);
$this->assertEquals(
array($this->_temp . '/jane.zip', $this->_temp . '/john.zip'),
$users
);
$data = $backup->restore();
$this->assertInternalType('array', $data);
$this->assertCount(2, $data);
$this->assertArrayHasKey('calendar', $data);
$this->assertArrayHasKey('addressbook', $data);
$matrix = array();
$applications = array(
'calendar' => new Stub\Application(),
'addressbook' => new Stub\Application2()
);
foreach ($data as $application => $collections) {
foreach ($collections as $collection) {
$user = $collection->getUser();
$type = $collection->getType();
$matrix[$user][$application][$type] = true;
$this->assertTrue(
$applications[$application]->restore($collection)
);
}
}
ksort($matrix);
$this->assertEquals(
array(
'jane' => array(
'calendar' => array(
'events' => true
),
'addressbook' => array(
'addressbooks' => true,
'contacts' => true,
),
),
'john' => array(
'calendar' => array(
'events' => true,
'calendars' => true,
),
),
),
$matrix
);
}

public function testBackupSingleUser()
Expand Down
14 changes: 12 additions & 2 deletions framework/Backup/test/Horde/Backup/Stub/Application.php
Expand Up @@ -79,12 +79,22 @@ public function getUserBackup($user)
{
$backup = new User($user);
foreach ($this->userData[$user] as $type => $data) {
$backup->collections[] = new Collection($data, $user, $type);
$backup->collections[] = new Collection(
new ArrayIterator($data), $user, $type
);
}
return $backup;
}

public function restore($user, Collection $data)
public function restore(Collection $data)
{
foreach ($data as $backup) {
foreach ($this->userData[$data->getUser()][$data->getType()] as $original) {
if ($original == $backup) {
return true;
}
}
}
return false;
}
}

0 comments on commit 42a6c99

Please sign in to comment.