use Leafo\ScssPhp\Exception\ParserException;
use Leafo\ScssPhp\Formatter\Crunched;
use Leafo\ScssPhp\Formatter\Expanded;
+use OC\Memcache\NullCache;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\ILogger;
+use OCP\IMemcache;
use OCP\IURLGenerator;
use OC\Files\AppData\Factory;
use OC\Template\IconsCacher;
/** @var ITimeFactory */
private $timeFactory;
+ /** @var IMemcache */
+ private $lockingCache;
+
/**
* @param ILogger $logger
* @param Factory $appDataFactory
$this->defaults = $defaults;
$this->serverRoot = $serverRoot;
$this->cacheFactory = $cacheFactory;
- $this->depsCache = $cacheFactory->createDistributed('SCSS-' . md5($this->urlGenerator->getBaseUrl()));
+ $this->depsCache = $cacheFactory->createDistributed('SCSS-deps-' . md5($this->urlGenerator->getBaseUrl()));
$this->isCachedCache = $cacheFactory->createLocal('SCSS-cached-' . md5($this->urlGenerator->getBaseUrl()));
+ $lockingCache = $cacheFactory->createDistributed('SCSS-locks-' . md5($this->urlGenerator->getBaseUrl()));
+ if (!($lockingCache instanceof IMemcache)) {
+ $lockingCache = new NullCache();
+ }
+ $this->lockingCache = $lockingCache;
$this->iconsCacher = $iconsCacher;
$this->timeFactory = $timeFactory;
}
if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
// Inject icons vars css if any
- if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
- $this->iconsCacher->injectCss();
- }
- return true;
+ return $this->injectCssVariablesIfAny();
}
try {
$folder = $this->appData->newFolder($app);
}
- $cached = $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
+ $lockKey = $webDir . '/' . $fileNameSCSS;
+
+ if (!$this->lockingCache->add($lockKey, 'locked!', 120)) {
+ $retry = 0;
+ sleep(1);
+ while ($retry < 10) {
+ if (!$this->variablesChanged() && $this->isCached($fileNameCSS, $app)) {
+ // Inject icons vars css if any
+ $this->lockingCache->remove($lockKey);
+ $this->logger->debug('SCSSCacher: ' .$lockKey.' is now available after '.$retry.'s. Moving on...', ['app' => 'core']);
+ return $this->injectCssVariablesIfAny();
+ }
+ $this->logger->debug('SCSSCacher: scss cache file locked for '.$lockKey, ['app' => 'core']);
+ sleep($retry);
+ $retry++;
+ }
+ $this->logger->debug('SCSSCacher: Giving up scss caching for '.$lockKey, ['app' => 'core']);
+ return false;
+ }
+
+ try {
+ $cached = $this->cache($path, $fileNameCSS, $fileNameSCSS, $folder, $webDir);
+ } catch (\Exception $e) {
+ $this->lockingCache->remove($lockKey);
+ throw $e;
+ }
+
+ // Cleaning lock
+ $this->lockingCache->remove($lockKey);
// Inject icons vars css if any
if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
*/
private function isCached(string $fileNameCSS, string $app) {
$key = $this->config->getSystemValue('version') . '/' . $app . '/' . $fileNameCSS;
- if (!$this->config->getSystemValue('debug') && $cacheValue = $this->isCachedCache->get($key)) {
+
+ // If the file mtime is more recent than our cached one,
+ // let's consider the file is properly cached
+ if ($cacheValue = $this->isCachedCache->get($key)) {
if ($cacheValue > $this->timeFactory->getTime()) {
return true;
}
}
+ // Creating file cache if none for further checks
try {
$folder = $this->appData->getFolder($app);
} catch (NotFoundException $e) {
- // creating css appdata folder
- $folder = $this->appData->newFolder($app);
+ return false;
}
+ // Checking if file size is coherent
+ // and if one of the css dependency changed
try {
$cachedFile = $folder->getFile($fileNameCSS);
if ($cachedFile->getSize() > 0) {
if ($deps === null) {
$depFile = $folder->getFile($depFileName);
$deps = $depFile->getContent();
- //Set to memcache for next run
+ // Set to memcache for next run
$this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
}
$deps = json_decode($deps, true);
*/
private function variablesChanged(): bool {
$injectedVariables = $this->getInjectedVariables();
- if ($this->config->getAppValue('core', 'scss.variables') !== md5($injectedVariables)) {
+ if ($this->config->getAppValue('core', 'theming.variables') !== md5($injectedVariables)) {
$this->resetCache();
- $this->config->setAppValue('core', 'scss.variables', md5($injectedVariables));
-
+ $this->config->setAppValue('core', 'theming.variables', md5($injectedVariables));
return true;
}
-
return false;
}
'@import "functions.scss";' .
'@import "' . $fileNameSCSS . '";');
} catch (ParserException $e) {
- $this->logger->error($e, ['app' => 'core']);
+ $this->logger->logException($e, ['app' => 'core']);
return false;
}
*/
public function resetCache() {
$this->injectedVariables = null;
- $this->cacheFactory->createDistributed('SCSS-')->clear();
+
+ // do not clear locks
+ $this->cacheFactory->createDistributed('SCSS-deps-')->clear();
+ $this->cacheFactory->createDistributed('SCSS-cached-')->clear();
+
$appDirectory = $this->appData->getDirectoryListing();
foreach ($appDirectory as $folder) {
foreach ($folder->getDirectoryListing() as $file) {
}
}
}
+ $this->logger->debug('SCSSCacher: css cache cleared!');
}
/**
$scss->compile($variables);
$this->injectedVariables = $variables;
} catch (ParserException $e) {
- $this->logger->error($e, ['app' => 'core']);
+ $this->logger->logException($e, ['app' => 'core']);
}
return $variables;
return substr($this->urlGenerator->linkToRoute('core.Css.getCss', [
'fileName' => $fileName,
'appName' => $appName,
- 'v' => $this->config->getAppValue('core', 'scss.variables', '0')
+ 'v' => $this->config->getAppValue('core', 'theming.variables', '0')
]), \strlen(\OC::$WEBROOT) + 1);
}
return $webRoot . substr($path, strlen($serverRoot));
}
+
+ /**
+ * Add the icons css cache in the header if needed
+ *
+ * @return boolean true
+ */
+ private function injectCssVariablesIfAny() {
+ // Inject icons vars css if any
+ if ($this->iconsCacher->getCachedCSS() && $this->iconsCacher->getCachedCSS()->getSize() > 0) {
+ $this->iconsCacher->injectCss();
+ }
+ return true;
+ }
}