parent
3afa6b65b9
commit
06e745e6ff
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue