public function save()
{
$isUpdate = false;
if ($this->getId()) {
$isUpdate = true;
\Pimcore::getEventManager()->trigger("asset.preUpdate", $this);
} else {
\Pimcore::getEventManager()->trigger("asset.preAdd", $this);
}
$this->correctPath();
// we wrap the save actions in a loop here, so that we can restart the database transactions in the case it fails
// if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out
// this is especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...)
$maxRetries = 5;
for ($retries = 0; $retries < $maxRetries; $retries++) {
$this->beginTransaction();
try {
if (!$isUpdate) {
$this->getDao()->create();
}
// get the old path from the database before the update is done
$oldPath = null;
if ($isUpdate) {
$oldPath = $this->getDao()->getCurrentFullPath();
}
$this->update();
// if the old path is different from the new path, update all children
$updatedChildren = [];
if ($oldPath && $oldPath != $this->getRealFullPath()) {
$oldFullPath = PIMCORE_ASSET_DIRECTORY . $oldPath;
if (is_file($oldFullPath) || is_dir($oldFullPath)) {
if (!@File::rename(PIMCORE_ASSET_DIRECTORY . $oldPath, $this->getFileSystemPath())) {
$error = error_get_last();
throw new \Exception("Unable to rename asset " . $this->getId() . " on the filesystem: " . $oldFullPath . " - Reason: " . $error["message"]);
}
$this->getDao()->updateWorkspaces();
$updatedChildren = $this->getDao()->updateChildsPaths($oldPath);
}
}
$this->commit();
break;
// transaction was successfully completed, so we cancel the loop here -> no restart required
} catch (\Exception $e) {
try {
$this->rollBack();
} catch (\Exception $er) {
// PDO adapter throws exceptions if rollback fails
Logger::error($er);
}
// we try to start the transaction $maxRetries times again (deadlocks, ...)
if ($retries < $maxRetries - 1) {
$run = $retries + 1;
$waitTime = 100000;
// microseconds
Logger::warn("Unable to finish transaction (" . $run . ". run) because of the following reason '" . $e->getMessage() . "'. --> Retrying in " . $waitTime . " microseconds ... (" . ($run + 1) . " of " . $maxRetries . ")");
usleep($waitTime);
// wait specified time until we restart the transaction
} else {
// if the transaction still fail after $maxRetries retries, we throw out the exception
throw $e;
}
}
}
$additionalTags = [];
if (isset($updatedChildren) && is_array($updatedChildren)) {
foreach ($updatedChildren as $assetId) {
$tag = "asset_" . $assetId;
$additionalTags[] = $tag;
// remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI
\Zend_Registry::set($tag, null);
}
}
$this->clearDependentCache($additionalTags);
$this->setDataChanged(false);
if ($isUpdate) {
\Pimcore::getEventManager()->trigger("asset.postUpdate", $this);
} else {
\Pimcore::getEventManager()->trigger("asset.postAdd", $this);
}
return $this;
}