Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use associative array to represent directory structure #25

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 61 additions & 32 deletions src/services/Compress.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@
use yii\db\Exception;
use yii\db\StaleObjectException;
use ZipArchive;
use RecursiveArrayIterator, RecursiveIteratorIterator;


// https://www.lambda-out-loud.com/posts/flatten-arrays-php/#a-general-solution-for-arbitrarily-nested-arrays
function flatten_array(array $array): array {
$recursiveArrayIterator = new RecursiveArrayIterator(
$array,
RecursiveArrayIterator::CHILD_ARRAYS_ONLY
);
$iterator = new RecursiveIteratorIterator($recursiveArrayIterator);
return iterator_to_array($iterator, false);
}


function getAssetsFlat($structure) {
return flatten_array($structure);
}


/**
Expand Down Expand Up @@ -58,19 +75,25 @@ class Compress extends Component
* @param null $filename
* @return ArchiveModel|null
*/
public function getArchiveModelForQuery($query, $lazy = false, $filename = null): ?ArchiveModel
public function getArchiveModelForQuery(array $structure, $lazy = false, $filename = null): ?ArchiveModel
{
// Get the assets and create a unique hash to represent them
if ($query instanceof AssetQuery) {
$assets = $query->all();
} elseif ($query instanceof \ArrayAccess) {
$assets = $query;
} else {
// we expect an associative array à la:
/* [
'dir' => [
'nested-dir' => [
$asset1,
$asset2
],
],
] */
if (!is_array($structure)) {
Craft::error('Unexpected input provided for asset query', __METHOD__);
return null;
}

$hash = $this->getHashForAssets($assets, $filename);
// Get the assets and create a unique hash to represent them
$allAssets = getAssetsFlat($structure);
$hash = $this->getHashForAssets($allAssets, $filename);

// Make sure we haven't already hashed these assets. If so, return the
// archive.
Expand All @@ -81,7 +104,7 @@ public function getArchiveModelForQuery($query, $lazy = false, $filename = null)

// No existing record, let's create a new one
if (!$record instanceof ArchiveRecord) {
$record = $this->createArchiveRecord($assets, null, $filename);
$record = $this->createArchiveRecord($structure, null, $filename);
}

// We'll use the cache to keep track of the status of the archive to
Expand All @@ -106,16 +129,16 @@ public function getArchiveModelForQuery($query, $lazy = false, $filename = null)

// We'll do it live!
try {
return $this->createArchiveAsset($record);
return $this->createArchiveAsset($structure, $record);
} catch (\Exception $e) {
Craft::error($e->getMessage(), __METHOD__);
return null;
}
}

public function createArchiveRecord($assets, $archiveAsset = null, ?string $filename = null): ArchiveRecord
public function createArchiveRecord($structure, $archiveAsset = null, ?string $filename = null): ArchiveRecord
{
$archive = $this->createArchiveRecords($assets, $archiveAsset, null, $filename);
$archive = $this->createArchiveRecords($structure, $archiveAsset, null, $filename);
return $archive;
}

Expand All @@ -127,18 +150,9 @@ public function createArchiveRecord($assets, $archiveAsset = null, ?string $file
* @throws \craft\errors\VolumeException
* @throws \Exception
*/
public function createArchiveAsset(ArchiveRecord $archiveRecord): ?ArchiveModel
public function createArchiveAsset(array $structure, ArchiveRecord $archiveRecord): ?ArchiveModel
{
$uuid = StringHelper::UUID();
$fileAssetRecords = $archiveRecord->fileAssets;
$assetIds = [];
/** @var FileRecord $fileAssetRecord */
foreach ($fileAssetRecords as $fileAssetRecord) {
$assetIds[] = $fileAssetRecord->assetId;
}
$assetQuery = new AssetQuery(Asset::class);
$assetQuery->id($assetIds);
$assets = $assetQuery->all();
$assetName = $uuid . '.zip';
if ($archiveRecord->filename) {
$assetName = $archiveRecord->filename . '.zip';
Expand All @@ -157,21 +171,35 @@ public function createArchiveAsset(ArchiveRecord $archiveRecord): ?ArchiveModel
throw new CompressException('Cannot create zip file at: ' . $zipPath);
}

$allAssets = getAssetsFlat($structure);
$maxFileCount = Plugin::getInstance()->getSettings()->maxFileCount;
if ($maxFileCount > 0 && count($assets) > $maxFileCount) {
if ($maxFileCount > 0 && count($allAssets) > $maxFileCount) {
throw new CompressException('Cannot create zip; maxFileCount exceeded.');
}

$totalFileSize = array_reduce($assets, static fn($carry, $asset) => $carry + $asset->size, 0);
$totalFileSize = array_reduce($allAssets, static fn($carry, $asset) => $carry + $asset->size, 0);
$maxFileSize = Plugin::getInstance()->getSettings()->maxFileSize;
if ($maxFileSize > 0 && $totalFileSize > $maxFileSize) {
throw new CompressException('Cannot create zip; maxFileSize exceeded.');
}
App::maxPowerCaptain();

foreach ($assets as $asset) {
$zip->addFromString($asset->filename, $asset->getContents());
function addFiles(ZipArchive $zip, array $structure, string $path) {
foreach ($structure as $dirName => $value) {
if (is_array($value)) {
$newPath = $path . DIRECTORY_SEPARATOR . $dirName;
$zip->addEmptyDir($newPath);
addFiles($zip, $value, $newPath);
} else {
$asset = $value;
$zip->addFromString(
$path . DIRECTORY_SEPARATOR . $asset->filename,
$asset->getContents()
);
}
}
}
addFiles($zip, $structure, '');

$zip->close();
} catch (\Exception $e) {
Expand Down Expand Up @@ -213,13 +241,15 @@ public function createArchiveAsset(ArchiveRecord $archiveRecord): ?ArchiveModel
* @return ArchiveRecord
* @throws Exception
*/
private function createArchiveRecords(array $zippedAssets, ?Asset $asset = null, ?ArchiveRecord $archiveRecord = null, ?string $filename = null): ArchiveRecord
private function createArchiveRecords(array $structure, ?Asset $asset = null, ?ArchiveRecord $archiveRecord = null, ?string $filename = null): ArchiveRecord
{
$allAssets = getAssetsFlat($structure);

if (!$archiveRecord) {
$archiveRecord = new ArchiveRecord();
$archiveRecord->dateLastAccessed = DateTimeHelper::currentUTCDateTime();
$archiveRecord->assetId = $asset->id ?? null;
$archiveRecord->hash = $this->getHashForAssets($zippedAssets, $filename);
$archiveRecord->hash = $this->getHashForAssets($allAssets, $filename);
if ($filename) {
$archiveRecord->filename = FileHelper::sanitizeFilename($filename, ['separator' => null]);
}
Expand All @@ -228,15 +258,14 @@ private function createArchiveRecords(array $zippedAssets, ?Asset $asset = null,

$event = new CompressEvent([
'archiveRecord' => $archiveRecord,
'assets' => $zippedAssets
'assets' => $allAssets
]);
$this->trigger(self::EVENT_BEFORE_CONFIGURE_ARCHIVE, $event);

$zippedAssets = $event->assets;
$archiveRecord = $event->archiveRecord;

$rows = [];
foreach ($zippedAssets as $zippedAsset) {
foreach ($allAssets as $zippedAsset) {
$rows[] = [
$archiveRecord->id,
$zippedAsset->id
Expand All @@ -247,7 +276,7 @@ private function createArchiveRecords(array $zippedAssets, ?Asset $asset = null,

$event = new CompressEvent([
'archiveRecord' => $archiveRecord,
'assets' => $zippedAssets
'assets' => $allAssets
]);
$this->trigger(self::EVENT_AFTER_CONFIGURE_ARCHIVE, $event);

Expand Down