diff --git a/CHANGELOG.md b/CHANGELOG.md
index db1da963..6e7376cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.
+## [0.1.5 - 2023-02-25]
+
+### Changed
+
+- Changed binary package format from one file to folder to speedup startup
+
## [0.1.4 - 2023-01-23]
### Removed
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 5f9bf20d..ee7449d7 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -34,7 +34,7 @@ You can support us in several ways:
[![Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/cloud_py_api/donate)
]]>
- 0.1.4
+ 0.1.5
agpl
Andrey Borysenko
Alexander Piskun
diff --git a/lib/Service/UtilsService.php b/lib/Service/UtilsService.php
index d55d6253..067b5a0b 100644
--- a/lib/Service/UtilsService.php
+++ b/lib/Service/UtilsService.php
@@ -342,6 +342,229 @@ public function compareBinaryHash(string $url, string $binaryPath) {
return true;
}
+ /**
+ * Perform cURL to download python binary (in directory format)
+ *
+ * @param string $url
+ * @param array $binariesFolder appdata binaries folder
+ * @param string $appId target Application::APP_ID
+ * @param string $filename archive and extracted folder name
+ * @param bool $update flag to determine whether to update already downloaded binary or not
+ *
+ * @return array
+ */
+ public function downloadPythonBinaryDir(
+ string $url,
+ array $binariesFolder,
+ string $appId,
+ string $filename = 'main',
+ bool $update = false
+ ): array {
+ if (isset($binariesFolder['success']) && $binariesFolder['success']) {
+ $dir = $binariesFolder['path'] . '/';
+ } else {
+ return $binariesFolder; // Return getAppDataFolder result
+ }
+ $file_name = $filename . '.tar.gz';
+ $save_file_loc = $dir . $file_name;
+ $shouldDownloadBinary = $this->compareBinaryDirectoryHashes($url, $binariesFolder, $appId);
+
+ if (!file_exists($dir . $filename) || ($update && $shouldDownloadBinary)) {
+ $cURL = curl_init($url);
+ $fp = fopen($save_file_loc, 'wb');
+ if ($fp) {
+ curl_setopt_array($cURL, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FILE => $fp,
+ CURLOPT_FOLLOWLOCATION => true,
+ ]);
+ curl_exec($cURL);
+ curl_close($cURL);
+ fclose($fp);
+ $unpacked = $this->unTarGz($binariesFolder, $filename . '.tar.gz');
+ unlink($save_file_loc);
+ return [
+ 'downloaded' => true,
+ 'unpacked' => $unpacked
+ ];
+ }
+ }
+
+ return [
+ 'downloaded' => true,
+ 'unpacked' => true,
+ ];
+ }
+
+ /**
+ * Extract tar.gz file
+ *
+ * @param array $binariesFolder appdata binaries folder
+ * @param string $src_filename source tar.gz file name
+ *
+ * @return array
+ */
+ public function unTarGz(array $binariesFolder, string $src_filename): array {
+ if (isset($binariesFolder['success']) && $binariesFolder['success']) {
+ $dir = $binariesFolder['path'] . '/';
+ $src_file = $dir . $src_filename;
+ $phar = new \PharData($src_file);
+ $extracted = $phar->extractTo($dir, null, true);
+ $filename = $phar->getFilename();
+ return [
+ 'extracted' => $extracted,
+ 'filename' => $filename
+ ];
+ }
+ return [
+ 'extracted' => false,
+ ];
+ }
+
+ /**
+ * Perform cURL to get binary folder hashes sha256 sum
+ *
+ * @param string $url url to the binary hashsums file
+ *
+ * @return array
+ */
+ public function downloadBinaryDirHashes(string $url): array {
+ $cURL = curl_init($url);
+ curl_setopt_array($cURL, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FOLLOWLOCATION => true,
+ ]);
+ $binaryHash = curl_exec($cURL);
+ curl_close($cURL);
+ return [
+ 'success' => $binaryHash != false,
+ 'binaryHash' => json_decode($binaryHash, true),
+ ];
+ }
+
+ /**
+ * Compare binary folder hashes from release.
+ * If hash not exists return `true` (download anyway)
+ *
+ * @param string $url
+ * @param array $binariesFolder
+ *
+ * @return bool
+ */
+ public function compareBinaryDirectoryHashes(
+ string $url, array $binariesFolder, string $appId
+ ): bool {
+ $currentBinaryHashes = $this->getCurrentBinaryDirHashes($binariesFolder, $appId);
+ $newBinaryHashes = $this->downloadBinaryDirHashes(str_replace('.tar.gz', '.json', $url));
+ if ($newBinaryHashes['success'] && $currentBinaryHashes['success']) {
+ // Skip hash check of archive file
+ $archiveFilename = $appId . '_' . $this->getBinaryName() . '.tar.gz';
+ if (isset($newBinaryHashes['binaryHashes'][$archiveFilename])) {
+ unset($newBinaryHashes['binaryHashes'][$archiveFilename]);
+ }
+ foreach ($newBinaryHashes['binaryHashes'] as $filename => $hash) {
+ $fileExists = !isset($currentBinaryHashes[$filename]);
+ $currentHash = $currentBinaryHashes['binaryHashes'][$filename];
+ $hashEqual = $currentHash == $hash;
+ if (!$fileExists || !$hashEqual) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get current binary folder files hashes
+ *
+ * @param array $binariesFolder
+ *
+ * @return array
+ */
+ public function getCurrentBinaryDirHashes(array $binariesFolder, string $appId): array {
+ $currentBinaryHashes = [];
+ $archiveFilename = $appId . '_' . $this->getBinaryName() . '.tar.gz';
+ if (file_exists($binariesFolder['path'] . '/' . $archiveFilename)) {
+ $currentBinaryHashes[$archiveFilename] = hash_file(
+ 'sha256',
+ $binariesFolder['path'] . '/' . $archiveFilename
+ );
+ }
+ $extractedBinaryFolder = $binariesFolder['path'] . '/' . $appId . '_'. $this->getBinaryName();
+ $files = scandir($extractedBinaryFolder);
+ if ($files !== false) {
+ foreach ($files as $file) {
+ if ($file != '.' && $file != '..') {
+ // Get sha256 hash of each file
+ // If file is directory, get sha256 hash of each file in directory
+ if (is_dir($extractedBinaryFolder . '/' . $file)) {
+ $dirFiles = scandir($extractedBinaryFolder . '/' . $file);
+ $currentBinaryHashes = $this->getFolderHashes(
+ $dirFiles,
+ $file,
+ $extractedBinaryFolder . '/' . $file,
+ $currentBinaryHashes,
+ $appId
+ );
+ } else {
+ $binaryFolderFilePath = $appId . '_' . $this->getBinaryName() . '/' . $file;
+ $currentBinaryHashes[$binaryFolderFilePath] = hash_file(
+ 'sha256',
+ $extractedBinaryFolder . '/' . $file
+ );
+ }
+ }
+ }
+ }
+ return [
+ 'success' => count($currentBinaryHashes) > 0,
+ 'binaryHashes' => $currentBinaryHashes
+ ];
+ }
+
+ /**
+ * Get sha256 hashes of each file in binary folder
+ * Recursive function call if file is directory
+ *
+ * @param array $files
+ * @param string $folder
+ * @param string $extractedBinaryFolder
+ * @param array $currentBinaryHashes
+ * @param string $appId
+ *
+ * @return array
+ */
+ private function getFolderHashes(
+ array $files,
+ string $folder,
+ string $extractedBinaryFolder,
+ array $currentBinaryHashes,
+ string $appId
+ ): array {
+ foreach ($files as $file) {
+ if ($file != '.' && $file != '..') {
+ // Get sha256 hash of each file
+ // If file is directory, get sha256 hash of each file in directory
+ if (is_dir($extractedBinaryFolder . '/' . $file)) {
+ $dirFiles = scandir($extractedBinaryFolder . '/' . $file);
+ $currentBinaryHashes = $this->getFolderHashes(
+ $dirFiles,
+ $folder. '/' . $file, $extractedBinaryFolder . '/' . $file,
+ $currentBinaryHashes, $appId
+ );
+ } else {
+ $binaryFolderFilePath = $appId . '_'
+ . $this->getBinaryName() . '/' . $folder . '/' . $file;
+ $currentBinaryHashes[$binaryFolderFilePath] = hash_file(
+ 'sha256', $extractedBinaryFolder . '/' . $file
+ );
+ }
+ }
+ }
+ return $currentBinaryHashes;
+ }
+
/**
* Perform cURL to get binary's sha256 sum
*
diff --git a/package-lock.json b/package-lock.json
index 513d80ed..2543cadb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cloud_py_api",
- "version": "0.1.4",
+ "version": "0.1.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cloud_py_api",
- "version": "0.1.4",
+ "version": "0.1.5",
"license": "agpl",
"dependencies": {
"@nextcloud/auth": "^2.0.0",
diff --git a/package.json b/package.json
index 1afd21a7..f24b8100 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cloud_py_api",
"description": "Nextcloud Python API (Framework)",
- "version": "0.1.4",
+ "version": "0.1.5",
"keywords": [
"nextcloud",
"python",