JSCombiner.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. <?php
  2. /**
  3. * @copyright 2017, Roeland Jago Douma <roeland@famdouma.nl>
  4. *
  5. * @author Lukas Reschke <lukas@statuscode.ch>
  6. * @author Morris Jobke <hey@morrisjobke.de>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. *
  9. * @license GNU AGPL version 3 or any later version
  10. *
  11. * This program is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU Affero General Public License as
  13. * published by the Free Software Foundation, either version 3 of the
  14. * License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Affero General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Affero General Public License
  22. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. *
  24. */
  25. namespace OC\Template;
  26. use OC\SystemConfig;
  27. use OCP\ICache;
  28. use OCP\Files\IAppData;
  29. use OCP\Files\NotFoundException;
  30. use OCP\Files\NotPermittedException;
  31. use OCP\Files\SimpleFS\ISimpleFolder;
  32. use OCP\ICacheFactory;
  33. use OCP\ILogger;
  34. use OCP\IURLGenerator;
  35. class JSCombiner {
  36. /** @var IAppData */
  37. protected $appData;
  38. /** @var IURLGenerator */
  39. protected $urlGenerator;
  40. /** @var ICache */
  41. protected $depsCache;
  42. /** @var SystemConfig */
  43. protected $config;
  44. /** @var ILogger */
  45. protected $logger;
  46. /** @var ICacheFactory */
  47. private $cacheFactory;
  48. /**
  49. * @param IAppData $appData
  50. * @param IURLGenerator $urlGenerator
  51. * @param ICacheFactory $cacheFactory
  52. * @param SystemConfig $config
  53. * @param ILogger $logger
  54. */
  55. public function __construct(IAppData $appData,
  56. IURLGenerator $urlGenerator,
  57. ICacheFactory $cacheFactory,
  58. SystemConfig $config,
  59. ILogger $logger) {
  60. $this->appData = $appData;
  61. $this->urlGenerator = $urlGenerator;
  62. $this->cacheFactory = $cacheFactory;
  63. $this->depsCache = $this->cacheFactory->createDistributed('JS-' . md5($this->urlGenerator->getBaseUrl()));
  64. $this->config = $config;
  65. $this->logger = $logger;
  66. }
  67. /**
  68. * @param string $root
  69. * @param string $file
  70. * @param string $app
  71. * @return bool
  72. */
  73. public function process($root, $file, $app) {
  74. if ($this->config->getValue('debug') || !$this->config->getValue('installed')) {
  75. return false;
  76. }
  77. $path = explode('/', $root . '/' . $file);
  78. $fileName = array_pop($path);
  79. $path = implode('/', $path);
  80. try {
  81. $folder = $this->appData->getFolder($app);
  82. } catch(NotFoundException $e) {
  83. // creating css appdata folder
  84. $folder = $this->appData->newFolder($app);
  85. }
  86. if($this->isCached($fileName, $folder)) {
  87. return true;
  88. }
  89. return $this->cache($path, $fileName, $folder);
  90. }
  91. /**
  92. * @param string $fileName
  93. * @param ISimpleFolder $folder
  94. * @return bool
  95. */
  96. protected function isCached($fileName, ISimpleFolder $folder) {
  97. $fileName = str_replace('.json', '.js', $fileName);
  98. if (!$folder->fileExists($fileName)) {
  99. return false;
  100. }
  101. $fileName = $fileName . '.deps';
  102. try {
  103. $deps = $this->depsCache->get($folder->getName() . '-' . $fileName);
  104. if ($deps === null || $deps === '') {
  105. $depFile = $folder->getFile($fileName);
  106. $deps = $depFile->getContent();
  107. }
  108. // check again
  109. if ($deps === null || $deps === '') {
  110. $this->logger->info('JSCombiner: deps file empty: ' . $fileName);
  111. return false;
  112. }
  113. $deps = json_decode($deps, true);
  114. if ($deps === NULL) {
  115. return false;
  116. }
  117. foreach ($deps as $file=>$mtime) {
  118. if (!file_exists($file) || filemtime($file) > $mtime) {
  119. return false;
  120. }
  121. }
  122. return true;
  123. } catch(NotFoundException $e) {
  124. return false;
  125. }
  126. }
  127. /**
  128. * @param string $path
  129. * @param string $fileName
  130. * @param ISimpleFolder $folder
  131. * @return bool
  132. */
  133. protected function cache($path, $fileName, ISimpleFolder $folder) {
  134. $deps = [];
  135. $fullPath = $path . '/' . $fileName;
  136. $data = json_decode(file_get_contents($fullPath));
  137. $deps[$fullPath] = filemtime($fullPath);
  138. $res = '';
  139. foreach ($data as $file) {
  140. $filePath = $path . '/' . $file;
  141. if (is_file($filePath)) {
  142. $res .= file_get_contents($filePath);
  143. $res .= PHP_EOL . PHP_EOL;
  144. $deps[$filePath] = filemtime($filePath);
  145. }
  146. }
  147. $fileName = str_replace('.json', '.js', $fileName);
  148. try {
  149. $cachedfile = $folder->getFile($fileName);
  150. } catch(NotFoundException $e) {
  151. $cachedfile = $folder->newFile($fileName);
  152. }
  153. $depFileName = $fileName . '.deps';
  154. try {
  155. $depFile = $folder->getFile($depFileName);
  156. } catch (NotFoundException $e) {
  157. $depFile = $folder->newFile($depFileName);
  158. }
  159. try {
  160. $gzipFile = $folder->getFile($fileName . '.gzip'); # Safari doesn't like .gz
  161. } catch (NotFoundException $e) {
  162. $gzipFile = $folder->newFile($fileName . '.gzip'); # Safari doesn't like .gz
  163. }
  164. try {
  165. $cachedfile->putContent($res);
  166. $deps = json_encode($deps);
  167. $depFile->putContent($deps);
  168. $this->depsCache->set($folder->getName() . '-' . $depFileName, $deps);
  169. $gzipFile->putContent(gzencode($res, 9));
  170. $this->logger->debug('JSCombiner: successfully cached: ' . $fileName);
  171. return true;
  172. } catch (NotPermittedException $e) {
  173. $this->logger->error('JSCombiner: unable to cache: ' . $fileName);
  174. return false;
  175. }
  176. }
  177. /**
  178. * @param string $appName
  179. * @param string $fileName
  180. * @return string
  181. */
  182. public function getCachedJS($appName, $fileName) {
  183. $tmpfileLoc = explode('/', $fileName);
  184. $fileName = array_pop($tmpfileLoc);
  185. $fileName = str_replace('.json', '.js', $fileName);
  186. return substr($this->urlGenerator->linkToRoute('core.Js.getJs', array('fileName' => $fileName, 'appName' => $appName)), strlen(\OC::$WEBROOT) + 1);
  187. }
  188. /**
  189. * @param string $root
  190. * @param string $file
  191. * @return string[]
  192. */
  193. public function getContent($root, $file) {
  194. /** @var array $data */
  195. $data = json_decode(file_get_contents($root . '/' . $file));
  196. if(!is_array($data)) {
  197. return [];
  198. }
  199. $path = explode('/', $file);
  200. array_pop($path);
  201. $path = implode('/', $path);
  202. $result = [];
  203. foreach ($data as $f) {
  204. $result[] = $path . '/' . $f;
  205. }
  206. return $result;
  207. }
  208. /**
  209. * Clear cache with combined javascript files
  210. *
  211. * @throws NotFoundException
  212. */
  213. public function resetCache() {
  214. $this->cacheFactory->createDistributed('JS-')->clear();
  215. $appDirectory = $this->appData->getDirectoryListing();
  216. foreach ($appDirectory as $folder) {
  217. foreach ($folder->getDirectoryListing() as $file) {
  218. $file->delete();
  219. }
  220. }
  221. }
  222. }