Fixes #6354, Fixes #6393 --Add StereoTool plugin version support.

This commit is contained in:
Buster Neece 2023-07-23 23:04:25 -05:00
parent 3afa6b65b9
commit 06e745e6ff
No known key found for this signature in database
9 changed files with 257 additions and 33 deletions

View File

@ -13,6 +13,12 @@ release channel, you can take advantage of these new features and fixes.
- `!var` becomes `var()`
- `var := value` optionally becomes `var.set(value)`
- **Support for Direct StereoTool/Liquidsoap Integration**: In Liquidsoap 2.2.x, StereoTool is now directly supported
within the software, resulting in better performance and significantly less delay in processing. We now support
uploading the plugin version of StereoTool from the vendor's web site. We will use either the CLI version or the
plugin version if it is uploaded. You can now also remove StereoTool from your installation entirely from the "Install
StereoTool" page.
## Code Quality/Technical Changes
- **Frontend Overhaul**: We have updated the code that powers the browser-facing frontend of our application. In

View File

@ -160,6 +160,11 @@ return static function (RouteCollectorProxy $group) {
'/stereo_tool',
Controller\Api\Admin\StereoTool\PostAction::class
);
$group->delete(
'/stereo_tool',
Controller\Api\Admin\StereoTool\DeleteAction::class
);
}
)->add(new Middleware\Permissions(GlobalPermissions::Settings));

View File

@ -43,7 +43,12 @@
</li>
<li>
{{
$gettext('For most installations, you should choose the "Command line version 64 bit". For Raspberry Pi devices, select "Raspberry Pi 3/4 64 bit command line".')
$gettext('For x86/64 installations, choose "x86/64 Linux Thimeo-ST plugin".')
}}
</li>
<li>
{{
$gettext('For ARM (Raspberry Pi, etc.) installations, choose "Raspberry Pi Thimeo-ST plugin".')
}}
</li>
<li>
@ -79,6 +84,19 @@
@complete="relist"
@error="onError"
/>
<div
v-if="version"
class="buttons block-buttons mt-3"
>
<button
type="button"
class="btn btn-danger"
@click="doDelete"
>
{{ $gettext('Uninstall') }}
</button>
</div>
</div>
</div>
</loading>
@ -94,6 +112,7 @@ import {useNotify} from "~/functions/useNotify";
import {useAxios} from "~/vendor/axios";
import Loading from "~/components/Common/Loading.vue";
import CardPage from "~/components/Common/CardPage.vue";
import {useSweetAlert} from "~/vendor/sweetalert";
const props = defineProps({
apiUrl: {
@ -132,5 +151,15 @@ const relist = () => {
});
};
const {confirmDelete} = useSweetAlert();
const doDelete = () => {
confirmDelete().then((result) => {
if (result.value) {
axios.delete(props.apiUrl).then(relist);
}
});
}
onMounted(relist);
</script>

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Admin\StereoTool;
use App\Controller\SingleActionInterface;
use App\Entity\Api\Status;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Radio\StereoTool;
use App\Utilities\File;
use Psr\Http\Message\ResponseInterface;
final class DeleteAction implements SingleActionInterface
{
public function __invoke(
ServerRequest $request,
Response $response,
array $params
): ResponseInterface {
$libraryPath = StereoTool::getLibraryPath();
File::clearDirectoryContents($libraryPath);
return $response->withJson(Status::success());
}
}

View File

@ -5,13 +5,16 @@ declare(strict_types=1);
namespace App\Controller\Api\Admin\StereoTool;
use App\Controller\SingleActionInterface;
use App\Entity\Api\Error;
use App\Entity\Api\Status;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Radio\StereoTool;
use App\Service\Flow;
use App\Utilities\File;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;
final class PostAction implements SingleActionInterface
{
@ -25,20 +28,59 @@ final class PostAction implements SingleActionInterface
return $flowResponse;
}
$binaryPath = StereoTool::getBinaryPath();
if (is_file($binaryPath)) {
unlink($binaryPath);
}
$fsUtils = new Filesystem();
$flowResponse->moveTo($binaryPath);
$sourceTempPath = $flowResponse->getUploadedPath();
chmod($binaryPath, 0744);
$libraryPath = StereoTool::getLibraryPath();
if (!StereoTool::getVersion()) {
@unlink($binaryPath);
return $response->withStatus(400)->withJson(
new Error(400, __('Invalid binary uploaded.'))
);
File::clearDirectoryContents($libraryPath);
if ('zip' === pathinfo($flowResponse->getClientFilename(), PATHINFO_EXTENSION)) {
$destTempPath = sys_get_temp_dir() . '/uploads/new_stereo_tool';
$fsUtils->remove($destTempPath);
$fsUtils->mkdir($destTempPath);
$process = new Process([
'unzip',
'-o',
$sourceTempPath,
]);
$process->setWorkingDirectory($destTempPath);
$process->setTimeout(600.0);
$process->run();
$flowResponse->delete();
$unzippedPath = $destTempPath . '/Stereo_Tool_Generic_plugin_1000';
if (is_dir($unzippedPath)) {
$fsUtils->rename(
$unzippedPath . '/libStereoTool.so',
$libraryPath . '/libStereoTool.so',
true
);
$fsUtils->rename(
$unzippedPath . '/libStereoTool_64.so',
$libraryPath . '/libStereoTool_64.so',
true
);
$fsUtils->dumpFile(
$libraryPath . '/' . StereoTool::VERSION_FILE,
'10.0',
);
} else {
throw new InvalidArgumentException('Uploaded file not recognized.');
}
$fsUtils->remove($destTempPath);
} else {
$binaryPath = $libraryPath . '/stereo_tool';
$flowResponse->moveTo($libraryPath);
chmod($binaryPath, 0744);
}
return $response->withJson(Status::success());

View File

@ -148,23 +148,45 @@ final class ConfigWriter implements EventSubscriberInterface
return;
}
$stereoToolBinary = StereoTool::getBinaryPath();
$stereoToolLibraryPath = StereoTool::getLibraryPath();
$stereoToolBinary = $stereoToolLibraryPath . '/stereo_tool';
$stereoToolConfiguration = $station->getRadioConfigDir()
. DIRECTORY_SEPARATOR . $settings->getStereoToolConfigurationPath();
$stereoToolProcess = $stereoToolBinary . ' --silent - - -s ' . $stereoToolConfiguration;
$stereoToolLicenseKey = $settings->getStereoToolLicenseKey();
if (!empty($stereoToolLicenseKey)) {
$stereoToolProcess .= ' -k "' . $stereoToolLicenseKey . '"';
}
$event->appendBlock(
<<<LIQ
# Stereo Tool Pipe
radio = pipe(replay_delay=1.0, process='{$stereoToolProcess}', radio)
LIQ
);
if (is_file($stereoToolBinary)) {
$stereoToolProcess = $stereoToolBinary . ' --silent - - -s ' . $stereoToolConfiguration;
if (!empty($stereoToolLicenseKey)) {
$stereoToolProcess .= ' -k "' . $stereoToolLicenseKey . '"';
}
$event->appendBlock(
<<<LIQ
# Stereo Tool Pipe
radio = pipe(replay_delay=1.0, process='{$stereoToolProcess}', radio)
LIQ
);
} else {
$stereoToolLibrary = match (php_uname('m')) {
'x86_64', 'arm64' => $stereoToolLibraryPath . '/libStereoTool_64.so',
default => $stereoToolLibraryPath . '/libStereoTool.so'
};
$event->appendBlock(
<<<LIQ
# Stereo Tool Pipe
radio = stereotool(
library_file="{$stereoToolLibrary}",
license_key="{$stereoToolLicenseKey}",
preset="{$stereoToolConfiguration}",
radio
)
LIQ
);
}
break;
case AudioProcessingMethods::None:

View File

@ -11,15 +11,19 @@ use Symfony\Component\Process\Process;
final class StereoTool
{
public const VERSION_FILE = '.currentversion';
public static function isInstalled(): bool
{
return file_exists(self::getBinaryPath());
$libraryPath = self::getLibraryPath();
return file_exists($libraryPath . '/stereo_tool')
|| file_exists($libraryPath . '/libStereoTool.so');
}
public static function getBinaryPath(): string
public static function getLibraryPath(): string
{
$environment = Environment::getInstance();
return $environment->getParentDirectory() . '/servers/stereo_tool/stereo_tool';
return Environment::getInstance()->getParentDirectory() . '/servers/stereo_tool';
}
public static function isReady(Station $station): bool
@ -38,7 +42,17 @@ final class StereoTool
return null;
}
$binaryPath = self::getBinaryPath();
$libraryPath = self::getLibraryPath();
if (file_exists($libraryPath . '/' . self::VERSION_FILE)) {
return file_get_contents($libraryPath . '/' . self::VERSION_FILE) . ' (Plugin)';
}
$binaryPath = $libraryPath . '/stereo_tool';
if (!file_exists($binaryPath)) {
return null;
}
$process = new Process([$binaryPath, '--help']);
$process->setWorkingDirectory(dirname($binaryPath));
@ -55,6 +69,10 @@ final class StereoTool
}
preg_match('/STEREO TOOL ([.\d]+) CONSOLE APPLICATION/i', $process->getErrorOutput(), $matches);
return $matches[1] ?? null;
if (!isset($matches[1])) {
return null;
}
return $matches[1] . ' (CLI)';
}
}

View File

@ -85,13 +85,19 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
$this->validateActive();
$contents = file_get_contents($this->file);
@unlink($this->file);
$this->moved = true;
$this->delete();
return $contents ?: '';
}
public function delete(): void
{
$this->validateActive();
@unlink($this->file);
$this->moved = true;
}
public function getClientMediaType(): ?string
{
$this->validateActive();

View File

@ -4,11 +4,16 @@ declare(strict_types=1);
namespace App\Utilities;
use FilesystemIterator;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
use function stripos;
use function strlen;
/**
* Static class that facilitates the uploading, reading and deletion of files in a controlled directory.
@ -84,4 +89,68 @@ final class File
return $path;
}
/**
* Clear all contents of a directory, without removing the directory itself.
*/
public static function clearDirectoryContents(string $targetDir): void
{
$targetDir = rtrim($targetDir, '/\\');
$flags = FilesystemIterator::SKIP_DOTS;
$deleteIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($targetDir, $flags),
RecursiveIteratorIterator::CHILD_FIRST
);
$fsUtils = new Filesystem();
/** @var SplFileInfo $file */
foreach ($deleteIterator as $file) {
$fsUtils->remove((string)$file);
}
}
public static function moveDirectoryContents(
string $originDir,
string $targetDir,
bool $clearDirectoryFirst = false
): void {
if ($clearDirectoryFirst) {
self::clearDirectoryContents($targetDir);
}
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
$originDirLen = strlen($originDir);
$flags = FilesystemIterator::SKIP_DOTS;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($originDir, $flags),
RecursiveIteratorIterator::SELF_FIRST
);
$fsUtils = new Filesystem();
$fsUtils->mkdir($targetDir);
/** @var SplFileInfo $file */
foreach ($iterator as $file) {
if (
$file->getPathname() === $targetDir
|| $file->getRealPath() === $targetDir
) {
continue;
}
$target = $targetDir . substr($file->getPathname(), $originDirLen);
if (is_link((string)$file)) {
$fsUtils->symlink($file->getLinkTarget(), $target);
} elseif (is_dir((string)$file)) {
$fsUtils->mkdir($target);
} elseif (is_file((string)$file)) {
$fsUtils->rename((string)$file, $target, true);
}
}
}
}