mirror of
https://github.com/chenasraf/nextcloud-jukebox.git
synced 2026-05-18 01:39:00 +00:00
feat: music scanner, settings page & api
This commit is contained in:
171
CHANGELOG.md
Normal file
171
CHANGELOG.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# Changelog
|
||||
|
||||
## [0.7.0](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.6.4...v0.7.0) (2025-05-28)
|
||||
|
||||
### Features
|
||||
|
||||
- add localizations
|
||||
([5910320](https://github.com/chenasraf/nextcloud-jukebox/commit/5910320b90507cc65a89d2bffb2d24f39d2a15ca))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **ApiController:** use argument param in updateSettings
|
||||
([aba72a1](https://github.com/chenasraf/nextcloud-jukebox/commit/aba72a13f2a6379ee128ee5ffb21a3fe1ea8ccdc))
|
||||
- rounding
|
||||
([ff02782](https://github.com/chenasraf/nextcloud-jukebox/commit/ff027827de1ac3d70cf7eeb818dbebdaf5b2e4a2))
|
||||
|
||||
## [0.6.4](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.6.3...v0.6.4) (2025-01-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- skip projects with missing default currency
|
||||
([d015f26](https://github.com/chenasraf/nextcloud-jukebox/commit/d015f26bc2a953367cdcb4c365f2633b486f1b4d))
|
||||
|
||||
## [0.6.3](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.6.2...v0.6.3) (2024-12-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- support cospend v3.0.7
|
||||
([39f1a9e](https://github.com/chenasraf/nextcloud-jukebox/commit/39f1a9efc0af68ae6a2f3cf5b3c769957da75405)),
|
||||
closes [#33](https://github.com/chenasraf/nextcloud-jukebox/issues/33)
|
||||
|
||||
## [0.6.2](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.6.1...v0.6.2) (2024-12-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- log level + methods docstring for currency
|
||||
([dff6d94](https://github.com/chenasraf/nextcloud-jukebox/commit/dff6d947d3fe857d95ae028eb9383a7600ad27f4))
|
||||
|
||||
## [0.6.1](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.6.0...v0.6.1) (2024-12-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- appstore script path resolution
|
||||
([1c02b79](https://github.com/chenasraf/nextcloud-jukebox/commit/1c02b796c55074a6afd1fec3c5aaf815f0947f75))
|
||||
|
||||
## [0.6.0](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.5.2...v0.6.0) (2024-12-08)
|
||||
|
||||
### Features
|
||||
|
||||
- add supported currencies table
|
||||
([b05290b](https://github.com/chenasraf/nextcloud-jukebox/commit/b05290beab361e44354df489074d40b93c4ea2e5))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- sort currencies table
|
||||
([56e160f](https://github.com/chenasraf/nextcloud-jukebox/commit/56e160f3a19bd6e0a399c0236c14899edc25a4b2))
|
||||
|
||||
## [0.5.2](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.5.1...v0.5.2) (2024-12-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add missing eur currency info
|
||||
([7948fd9](https://github.com/chenasraf/nextcloud-jukebox/commit/7948fd9a456654c0d81ab73501d0a87056ea49cb))
|
||||
|
||||
## [0.5.1](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.5.0...v0.5.1) (2024-12-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- settings section priority
|
||||
([da05142](https://github.com/chenasraf/nextcloud-jukebox/commit/da0514250882472b7b3ef0f9f293e0cf6f5568a5))
|
||||
|
||||
## [0.5.0](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.4.0...v0.5.0) (2024-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
- update admin page
|
||||
([ab029a3](https://github.com/chenasraf/nextcloud-jukebox/commit/ab029a3ecdec763dbe79ef38d8e0bf1676ef00b4))
|
||||
- update admin page ui
|
||||
([32c2c94](https://github.com/chenasraf/nextcloud-jukebox/commit/32c2c94526148efe767584c79ef8a380f26c0252))
|
||||
- update app+settings icons
|
||||
([b85256f](https://github.com/chenasraf/nextcloud-jukebox/commit/b85256f5a236b5701013878b18e03e1c8baabd07))
|
||||
|
||||
## [0.4.0](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.3.0...v0.4.0) (2024-12-05)
|
||||
|
||||
### Features
|
||||
|
||||
- add admin page intro section
|
||||
([023b2fd](https://github.com/chenasraf/nextcloud-jukebox/commit/023b2fd61c28cfdcb9a787b4cb4b5d853dffcdad))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- only include available currencies
|
||||
([902522f](https://github.com/chenasraf/nextcloud-jukebox/commit/902522f20f29382a837c0062a0c08c3f681cef73))
|
||||
|
||||
## [0.3.0](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.2.1...v0.3.0) (2024-12-04)
|
||||
|
||||
### Features
|
||||
|
||||
- improve currency matching
|
||||
([8738623](https://github.com/chenasraf/nextcloud-jukebox/commit/87386235c22a6dcd09f17cbeaa094152ccfd8540))
|
||||
|
||||
## [0.2.1](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.2.0...v0.2.1) (2024-12-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- release artifact tar
|
||||
([8d632ef](https://github.com/chenasraf/nextcloud-jukebox/commit/8d632ef7f215255246f209ab6e0593ef786e2bfc))
|
||||
|
||||
## [0.2.0](https://github.com/chenasraf/nextcloud-jukebox/compare/v0.1.0...v0.2.0) (2024-12-03)
|
||||
|
||||
### Features
|
||||
|
||||
- add admin example
|
||||
([1b3804b](https://github.com/chenasraf/nextcloud-jukebox/commit/1b3804ba0d8f73687c4260fbb2f20aac4470b758))
|
||||
- admin page view
|
||||
([68ae6eb](https://github.com/chenasraf/nextcloud-jukebox/commit/68ae6eb09e35057e426072c9986c7965d29401ea))
|
||||
- api controller poc
|
||||
([802c72f](https://github.com/chenasraf/nextcloud-jukebox/commit/802c72f0f7dd9be5f9abc3829ff403b9abfda7f8))
|
||||
- settings page logic
|
||||
([20d66b3](https://github.com/chenasraf/nextcloud-jukebox/commit/20d66b3650f53701a9a9ec54ac9cf15961592ced))
|
||||
- update name
|
||||
([ab7c03b](https://github.com/chenasraf/nextcloud-jukebox/commit/ab7c03b42475f701a151f383f06170f999d51c75))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- app info
|
||||
([aebc8a5](https://github.com/chenasraf/nextcloud-jukebox/commit/aebc8a52cc49cb736ce5e78f23ddc0626006a4d1))
|
||||
- build cmd
|
||||
([4046b7b](https://github.com/chenasraf/nextcloud-jukebox/commit/4046b7b8df01bce39fa4f31947971166b8f4aa56))
|
||||
- cron
|
||||
([5aebc7a](https://github.com/chenasraf/nextcloud-jukebox/commit/5aebc7a2aa46fba0daf403f11baa731620e335ae))
|
||||
- tests + use IAppConfig
|
||||
([b6058ef](https://github.com/chenasraf/nextcloud-jukebox/commit/b6058eff576790620f8b8166550d903872731f1d))
|
||||
- typescript version
|
||||
([c4f625a](https://github.com/chenasraf/nextcloud-jukebox/commit/c4f625a19236df7834a68b6a7d75c8b27d5113e6))
|
||||
- updateSettings ep + types
|
||||
([aae1dbc](https://github.com/chenasraf/nextcloud-jukebox/commit/aae1dbc141ab9c6ee8d57682e283d8615e3c4c91))
|
||||
|
||||
## 0.1.0 (2024-12-03)
|
||||
|
||||
### Features
|
||||
|
||||
- add admin example
|
||||
([1b3804b](https://github.com/chenasraf/nextcloud-jukebox/commit/1b3804ba0d8f73687c4260fbb2f20aac4470b758))
|
||||
- admin page view
|
||||
([68ae6eb](https://github.com/chenasraf/nextcloud-jukebox/commit/68ae6eb09e35057e426072c9986c7965d29401ea))
|
||||
- api controller poc
|
||||
([802c72f](https://github.com/chenasraf/nextcloud-jukebox/commit/802c72f0f7dd9be5f9abc3829ff403b9abfda7f8))
|
||||
- poc ready
|
||||
([e54cb41](https://github.com/chenasraf/nextcloud-jukebox/commit/e54cb41c5b549294fc8b014ef2a507178f4e8597))
|
||||
- poc working
|
||||
([c32cdaf](https://github.com/chenasraf/nextcloud-jukebox/commit/c32cdaf38de64f45de1285463f4265da2e95b438))
|
||||
- settings page logic
|
||||
([20d66b3](https://github.com/chenasraf/nextcloud-jukebox/commit/20d66b3650f53701a9a9ec54ac9cf15961592ced))
|
||||
- update name
|
||||
([ab7c03b](https://github.com/chenasraf/nextcloud-jukebox/commit/ab7c03b42475f701a151f383f06170f999d51c75))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- app info
|
||||
([aebc8a5](https://github.com/chenasraf/nextcloud-jukebox/commit/aebc8a52cc49cb736ce5e78f23ddc0626006a4d1))
|
||||
- build cmd
|
||||
([4046b7b](https://github.com/chenasraf/nextcloud-jukebox/commit/4046b7b8df01bce39fa4f31947971166b8f4aa56))
|
||||
- cron
|
||||
([5aebc7a](https://github.com/chenasraf/nextcloud-jukebox/commit/5aebc7a2aa46fba0daf403f11baa731620e335ae))
|
||||
- tests + use IAppConfig
|
||||
([b6058ef](https://github.com/chenasraf/nextcloud-jukebox/commit/b6058eff576790620f8b8166550d903872731f1d))
|
||||
- typescript version
|
||||
([c4f625a](https://github.com/chenasraf/nextcloud-jukebox/commit/c4f625a19236df7834a68b6a7d75c8b27d5113e6))
|
||||
- updateSettings ep + types
|
||||
([aae1dbc](https://github.com/chenasraf/nextcloud-jukebox/commit/aae1dbc141ab9c6ee8d57682e283d8615e3c4c91))
|
||||
@@ -47,7 +47,10 @@ It supports music files, podcasts (with gPodder sync), audiobooks, YouTube video
|
||||
<job>OCA\Jukebox\Cron\FetchCurrenciesJob</job>
|
||||
</background-jobs> -->
|
||||
<settings>
|
||||
<admin>OCA\Jukebox\Settings\CurrencyAdmin</admin>
|
||||
<admin-section>OCA\Jukebox\Sections\CurrencyAdmin</admin-section>
|
||||
<personal>OCA\Jukebox\Settings\JukeboxUserSettings</personal>
|
||||
<personal-section>OCA\Jukebox\Sections\JukeboxUserSection</personal-section>
|
||||
</settings>
|
||||
<commands>
|
||||
<command>OCA\Jukebox\Command\ScanMusic</command>
|
||||
</commands>
|
||||
</info>
|
||||
|
||||
@@ -21,6 +21,7 @@ class Application extends App implements IBootstrap {
|
||||
}
|
||||
|
||||
public function register(IRegistrationContext $context): void {
|
||||
include_once __DIR__ . '/../../vendor/autoload.php';
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
|
||||
51
lib/Command/ScanMusic.php
Normal file
51
lib/Command/ScanMusic.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: Chen Asraf <casraf@pm.me>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Jukebox\Command;
|
||||
|
||||
use OCA\Jukebox\Service\MusicScanner;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ScanMusic extends Command {
|
||||
public function __construct(
|
||||
private MusicScanner $service,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void {
|
||||
$this
|
||||
->setName('jukebox:scan-music')
|
||||
->setDescription('Scan music files for a user')
|
||||
->addArgument('uid', InputArgument::OPTIONAL, 'User ID to scan. If not provided, uses the current session user.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$uid = $input->getArgument('uid');
|
||||
|
||||
try {
|
||||
if ($uid) {
|
||||
$output->writeln("<info>Scanning music files for user '$uid'...</info>");
|
||||
$this->service->scanUserByUID($uid);
|
||||
} else {
|
||||
$output->writeln('<info>Scanning music files for the current session user...</info>');
|
||||
$this->service->scanMusicFiles();
|
||||
}
|
||||
|
||||
$output->writeln('<info>Scan completed successfully.</info>');
|
||||
return Command::SUCCESS;
|
||||
} catch (\Throwable $e) {
|
||||
$output->writeln('<error>Scan failed: ' . $e->getMessage() . '</error>');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
lib/Controller/SettingsController.php
Normal file
82
lib/Controller/SettingsController.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Jukebox\Controller;
|
||||
|
||||
use OCA\Jukebox\AppInfo\Application;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Handles user-specific settings such as the music folder path.
|
||||
*/
|
||||
class SettingsController extends OCSController {
|
||||
private IAppConfig $config;
|
||||
private IUserSession $userSession;
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
IAppConfig $config,
|
||||
IUserSession $userSession,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->config = $config;
|
||||
$this->userSession = $userSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user-specific settings
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return DataResponse<Http::STATUS_OK, array{status: non-empty-string}, array{}>
|
||||
*
|
||||
* 200: Settings saved
|
||||
*/
|
||||
#[ApiRoute(verb: 'PUT', url: '/api/settings')]
|
||||
public function saveSettings(mixed $data): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse(['status' => 'unauthenticated'], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$uid = $user->getUID();
|
||||
|
||||
if (array_key_exists('music_folder_path', $data)) {
|
||||
$this->config->setValueString(Application::APP_ID, 'music_folder_path_' . $uid, $data['music_folder_path']);
|
||||
}
|
||||
|
||||
return new DataResponse(['status' => 'OK']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all user-specific settings
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array<string, string>, array{}>
|
||||
*
|
||||
* 200: Current settings
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/settings')]
|
||||
public function getSettings(): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$uid = $user->getUID();
|
||||
$result = [];
|
||||
|
||||
$musicPath = $this->config->getValueString(Application::APP_ID, 'music_folder_path_' . $uid, 'Music');
|
||||
if ($musicPath !== null) {
|
||||
$result['music_folder_path'] = $musicPath;
|
||||
}
|
||||
|
||||
return new DataResponse($result);
|
||||
}
|
||||
}
|
||||
36
lib/Sections/JukeboxUserSection.php
Normal file
36
lib/Sections/JukeboxUserSection.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Jukebox\Sections;
|
||||
|
||||
use OCA\Jukebox\AppInfo\Application;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\IIconSection;
|
||||
|
||||
class JukeboxUserSection implements IIconSection {
|
||||
private IL10N $l;
|
||||
private IURLGenerator $urlGenerator;
|
||||
|
||||
public function __construct(IL10N $l, IURLGenerator $urlGenerator) {
|
||||
$this->l = $l;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return $this->urlGenerator->imagePath('core', 'actions/settings-dark.svg');
|
||||
}
|
||||
|
||||
public function getID(): string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l->t('Jukebox');
|
||||
}
|
||||
|
||||
public function getPriority(): int {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@ declare(strict_types=1);
|
||||
namespace OCA\Jukebox\Service;
|
||||
|
||||
use getID3;
|
||||
use OCA\Jukebox\AppInfo\Application;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IUser;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IUserSession;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
@@ -22,37 +23,55 @@ class MusicScanner {
|
||||
private IRootFolder $rootFolder;
|
||||
private IUserSession $userSession;
|
||||
|
||||
/**
|
||||
* @param IRootFolder $rootFolder
|
||||
* @param IUserSession $userSession
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(
|
||||
IRootFolder $rootFolder,
|
||||
IUserSession $userSession,
|
||||
private LoggerInterface $logger,
|
||||
private IAppConfig $appConfig,
|
||||
) {
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->userSession = $userSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts scanning the authenticated user's folder for music files.
|
||||
* Starts scanning the user's configured music directory for audio files.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
public function scanMusicFiles(): void {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
// Handle unauthenticated user
|
||||
$this->logger->warning('Music scan aborted: no user session.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scanUserByUID($user->getUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the music directory for a specific user by UID.
|
||||
*
|
||||
* @param string $uid
|
||||
* @return void
|
||||
*/
|
||||
public function scanUserByUID(string $uid): void {
|
||||
try {
|
||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||
$this->traverseFolder($userFolder, $user);
|
||||
$userFolder = $this->rootFolder->getUserFolder($uid);
|
||||
|
||||
$relativePath = $this->appConfig->getValueString(Application::APP_ID, 'music_folder_path_' . $uid, 'Music');
|
||||
|
||||
/** @var Folder $musicFolder */
|
||||
$musicFolder = $userFolder->get($relativePath);
|
||||
if (!($musicFolder instanceof Folder)) {
|
||||
$this->logger->warning("Configured music path '$relativePath' for user $uid is not a folder.");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info("Starting music scan for user '$uid' in folder '$relativePath'");
|
||||
$this->traverseFolder($musicFolder, $uid);
|
||||
} catch (NotFoundException $e) {
|
||||
// Handle folder not found
|
||||
$this->logger->error("Could not find music folder for user $uid: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,18 +79,21 @@ class MusicScanner {
|
||||
* Recursively traverses a folder and processes audio files.
|
||||
*
|
||||
* @param Folder $folder
|
||||
* @param IUser $user
|
||||
* @param string $uid
|
||||
* @return void
|
||||
*/
|
||||
private function traverseFolder(Folder $folder, IUser $user): void {
|
||||
private function traverseFolder(Folder $folder, string $uid): void {
|
||||
$this->logger->info('Scanning folder: ' . $folder->getPath());
|
||||
|
||||
foreach ($folder->getDirectoryListing() as $node) {
|
||||
if ($node instanceof File) {
|
||||
$mimeType = $node->getMimeType();
|
||||
if (str_starts_with($mimeType, 'audio/')) {
|
||||
$this->processAudioFile($node, $user);
|
||||
$this->logger->info('Found audio file: ' . $node->getPath() . " (MIME: $mimeType)");
|
||||
$this->processAudioFile($node, $uid);
|
||||
}
|
||||
} elseif ($node instanceof Folder) {
|
||||
$this->traverseFolder($node, $user);
|
||||
$this->traverseFolder($node, $uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,24 +102,28 @@ class MusicScanner {
|
||||
* Downloads an audio file, reads metadata, and processes it.
|
||||
*
|
||||
* @param File $file
|
||||
* @param IUser $user
|
||||
* @param string $uid
|
||||
* @return void
|
||||
*/
|
||||
private function processAudioFile(File $file, IUser $user): void {
|
||||
private function processAudioFile(File $file, string $uid): void {
|
||||
$this->logger->info('Processing audio file: ' . $file->getPath());
|
||||
|
||||
$tempPath = tempnam(sys_get_temp_dir(), 'jukebox_');
|
||||
if ($tempPath === false) {
|
||||
$this->logger->error('Could not create temporary file for audio processing.');
|
||||
return; // Could not create temp file
|
||||
return;
|
||||
}
|
||||
|
||||
$handle = fopen($tempPath, 'w');
|
||||
if ($handle === false) {
|
||||
$this->logger->error('Failed to open temporary file handle.');
|
||||
return;
|
||||
}
|
||||
|
||||
$stream = $file->fopen('r');
|
||||
if ($stream === false) {
|
||||
fclose($handle);
|
||||
$this->logger->error('Failed to open file stream for ' . $file->getPath());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,17 +134,24 @@ class MusicScanner {
|
||||
$getID3 = new getID3();
|
||||
$info = $getID3->analyze($tempPath);
|
||||
|
||||
$artist = $info['tags']['id3v2']['artist'][0] ?? '';
|
||||
$songArtist = $info['tags']['id3v2']['artist'][0] ?? '';
|
||||
$albumArtist =
|
||||
$info['tags']['id3v2']['band'][0] ??
|
||||
$info['tags']['id3v2']['album_artist'][0] ??
|
||||
$info['tags']['quicktime']['album_artist'][0] ??
|
||||
$info['tags']['asf']['WM/AlbumArtist'][0] ??
|
||||
'';
|
||||
$album = $info['tags']['id3v2']['album'][0] ?? '';
|
||||
$title = $info['tags']['id3v2']['title'][0] ?? $file->getName();
|
||||
|
||||
// Clean up temp file
|
||||
$this->logger->info("Scanned metadata for '{$file->getPath()}': Song Artist='{$songArtist}', Album Artist='{$albumArtist}', Album='{$album}', Title='{$title}'");
|
||||
|
||||
unlink($tempPath);
|
||||
|
||||
// Stub: Save metadata to DB
|
||||
$this->saveMetadataToDatabase($user->getUID(), $file->getId(), $artist, $album, $title);
|
||||
$this->saveMetadataToDatabase($uid, $file->getId(), $songArtist, $album, $title);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Placeholder method to save music metadata.
|
||||
*
|
||||
|
||||
44
lib/Settings/JukeboxAdmin.php
Normal file
44
lib/Settings/JukeboxAdmin.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace OCA\Jukebox\Settings;
|
||||
|
||||
use OCA\Jukebox\AppInfo\Application;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCP\Util;
|
||||
|
||||
class JukeboxAdmin implements ISettings {
|
||||
private IL10N $l;
|
||||
private IAppConfig $config;
|
||||
|
||||
public function __construct(IAppConfig $config, IL10N $l) {
|
||||
$this->config = $config;
|
||||
$this->l = $l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function getForm(): TemplateResponse {
|
||||
Util::addScript(Application::APP_ID, Application::JS_DIR . '/Jukebox-main');
|
||||
Util::addStyle(Application::APP_ID, Application::CSS_DIR . '/Jukebox-style');
|
||||
return new TemplateResponse(Application::APP_ID, 'settings', [], '');
|
||||
}
|
||||
|
||||
public function getSection(): string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int whether the form should be rather on the top or bottom of
|
||||
* the admin section. The forms are arranged in ascending order of the
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
*
|
||||
* E.g.: 70
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
39
lib/Settings/JukeboxUserSettings.php
Normal file
39
lib/Settings/JukeboxUserSettings.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Jukebox\Settings;
|
||||
|
||||
use OCA\Jukebox\AppInfo\Application;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\Settings\ISettings;
|
||||
use OCP\Util;
|
||||
|
||||
/**
|
||||
* Settings form shown under user's personal settings.
|
||||
*/
|
||||
class JukeboxUserSettings implements ISettings {
|
||||
private IL10N $l;
|
||||
private IAppConfig $config;
|
||||
|
||||
public function __construct(IAppConfig $config, IL10N $l) {
|
||||
$this->config = $config;
|
||||
$this->l = $l;
|
||||
}
|
||||
|
||||
public function getForm(): TemplateResponse {
|
||||
Util::addScript(Application::APP_ID, Application::JS_DIR . '/Jukebox-settings');
|
||||
Util::addStyle(Application::APP_ID, Application::CSS_DIR . '/Jukebox-style');
|
||||
return new TemplateResponse(Application::APP_ID, 'settings', []);
|
||||
}
|
||||
|
||||
public function getSection(): string {
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
public function getPriority(): int {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@nextcloud/axios": "^2.5.1",
|
||||
"@nextcloud/dialogs": "7.0.0-rc.0",
|
||||
"@nextcloud/l10n": "^3.3.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/vite-config": "^2.3.5",
|
||||
|
||||
249
pnpm-lock.yaml
generated
249
pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ importers:
|
||||
'@nextcloud/axios':
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
'@nextcloud/dialogs':
|
||||
specifier: 7.0.0-rc.0
|
||||
version: 7.0.0-rc.0(typescript@5.8.3)
|
||||
'@nextcloud/l10n':
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
@@ -165,6 +168,9 @@ packages:
|
||||
'@bufbuild/protobuf@2.5.2':
|
||||
resolution: {integrity: sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==}
|
||||
|
||||
'@buttercup/fetch@0.2.1':
|
||||
resolution: {integrity: sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==}
|
||||
|
||||
'@ckpack/vue-color@1.6.0':
|
||||
resolution: {integrity: sha512-b9kFTKhYbNArfgP1lmnaVm0VNsWdZjqIbyHUYry7mZ+E7JeTQclbjq1+2xWn0SE3wzqRYlXmAVjECPOgteWmMQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -443,6 +449,9 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@mdi/js@7.4.47':
|
||||
resolution: {integrity: sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==}
|
||||
|
||||
'@microsoft/api-extractor-model@7.30.6':
|
||||
resolution: {integrity: sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==}
|
||||
|
||||
@@ -476,6 +485,10 @@ packages:
|
||||
resolution: {integrity: sha512-L1NQtOfHWzkfj0Ple1MEJt6HmOHWAi3y4qs+OnwSWexqJT0DtXTVPyRxi7ADyITwRxS5H9R/HMl6USAj4Nr1nQ==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
|
||||
'@nextcloud/dialogs@7.0.0-rc.0':
|
||||
resolution: {integrity: sha512-RqoXbSBRzxPUU4g4f5c3sRWwpL6tnuSifZSW+L/xpT5GRfLbzF+tGkMgN1ghC9rmiLb0B5ovPWqABZiwrid9Rw==}
|
||||
engines: {node: ^20 || ^22, npm: ^10.0.0}
|
||||
|
||||
'@nextcloud/eslint-config@8.4.2':
|
||||
resolution: {integrity: sha512-zsDcBxvp2Vr/BgasK/vNYJ84LOXjl4RseJPrcp93zcnaB2WnygV50Sd0nQ5JN0ngTyPjiIlGd92MMzrMTofjRA==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
@@ -505,6 +518,10 @@ packages:
|
||||
resolution: {integrity: sha512-1Qfs6i7Tz2qd1A33NpBQOt810ydHIRjhyXMFwSEkYX2yUI80lAk/sWO8HIB2Fqp+iffhyviPPcQYoytMDRyDNw==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
|
||||
'@nextcloud/files@3.10.2':
|
||||
resolution: {integrity: sha512-8k6zN3nvGW8nEV5Db5DyfqcyK99RWw1iOSPIafi2RttiRQGpFzHlnF2EoM4buH5vWzI39WEvJnfuLZpkPX0cFw==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
|
||||
'@nextcloud/initial-state@2.2.0':
|
||||
resolution: {integrity: sha512-cDW98L5KGGgpS8pzd+05304/p80cyu8U2xSDQGa+kGPTpUFmCbv2qnO5WrwwGTauyjYijCal2bmw82VddSH+Pg==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
@@ -517,6 +534,10 @@ packages:
|
||||
resolution: {integrity: sha512-wByt0R0/6QC44RBpaJr1MWghjjOxk/pRbACHo/ZWWKht1qYbJRHB4GtEi+35KEIHY07ZpqxiDk6dIRuN7sXYWQ==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
|
||||
'@nextcloud/paths@2.2.1':
|
||||
resolution: {integrity: sha512-M3ShLjrxR7B48eKThLMoqbxTqTKyQXcwf9TgeXQGbCIhiHoXU6as5j8l5qNv/uZlANokVdowpuWHBi3b2+YNNA==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
|
||||
'@nextcloud/router@3.0.1':
|
||||
resolution: {integrity: sha512-Ci/uD3x8OKHdxSqXL6gRJ+mGJOEXjeiHjj7hqsZqVTsT7kOrCjDf0/J8z5RyLlokKZ0IpSe+hGxgi3YB7Gpw3Q==}
|
||||
engines: {node: ^20.0.0, npm: ^10.0.0}
|
||||
@@ -851,6 +872,9 @@ packages:
|
||||
'@types/sizzle@2.3.9':
|
||||
resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==}
|
||||
|
||||
'@types/toastify-js@1.12.4':
|
||||
resolution: {integrity: sha512-zfZHU4tKffPCnZRe7pjv/eFKzTVHozKewFCKaCjZ4gFinKgJRz/t0bkZiMCXJxPhv/ZoeDGNOeRD09R0kQZ/nw==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
@@ -1235,6 +1259,9 @@ packages:
|
||||
balanced-match@2.0.0:
|
||||
resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
|
||||
|
||||
base-64@1.0.0:
|
||||
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
@@ -1317,6 +1344,9 @@ packages:
|
||||
builtins@5.1.0:
|
||||
resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==}
|
||||
|
||||
byte-length@1.0.2:
|
||||
resolution: {integrity: sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1333,6 +1363,9 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cancelable-promise@4.3.1:
|
||||
resolution: {integrity: sha512-A/8PwLk/T7IJDfUdQ68NR24QHa8rIlnN/stiJEBo6dmVUkD4K14LswG0w3VwdeK/o7qOwRUR1k2MhK5Rpy2m7A==}
|
||||
|
||||
caniuse-lite@1.0.30001721:
|
||||
resolution: {integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==}
|
||||
|
||||
@@ -1359,6 +1392,9 @@ packages:
|
||||
character-reference-invalid@2.0.1:
|
||||
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
|
||||
|
||||
charenc@0.0.2:
|
||||
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
@@ -1471,6 +1507,9 @@ packages:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
crypt@0.0.2:
|
||||
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
|
||||
|
||||
crypto-browserify@3.12.1:
|
||||
resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==}
|
||||
engines: {node: '>= 0.10'}
|
||||
@@ -1491,6 +1530,10 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1623,6 +1666,10 @@ packages:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
entities@6.0.0:
|
||||
resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1902,6 +1949,10 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -1958,6 +2009,10 @@ packages:
|
||||
resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
fs-extra@11.3.0:
|
||||
resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==}
|
||||
engines: {node: '>=14.14'}
|
||||
@@ -2106,6 +2161,9 @@ packages:
|
||||
hmac-drbg@1.0.1:
|
||||
resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
|
||||
|
||||
hot-patcher@2.0.1:
|
||||
resolution: {integrity: sha512-ECg1JFG0YzehicQaogenlcs2qg6WsXQsxtnbr1i696u5tLUjtJdQAh0u2g0Q5YV45f263Ta1GnUJsc8WIfJf4Q==}
|
||||
|
||||
html-tags@3.3.1:
|
||||
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2200,6 +2258,9 @@ packages:
|
||||
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-buffer@1.1.6:
|
||||
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
|
||||
|
||||
is-builtin-module@3.2.1:
|
||||
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2301,6 +2362,10 @@ packages:
|
||||
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-svg@5.1.0:
|
||||
resolution: {integrity: sha512-uVg5yifaTxHoefNf5Jcx+i9RZe2OBYd/UStp1umx+EERa4xGRa3LLGXjoEph43qUORC0qkafUgrXZ6zzK89yGA==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
is-symbol@1.1.1:
|
||||
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2399,6 +2464,9 @@ packages:
|
||||
kolorist@1.8.0:
|
||||
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||
|
||||
layerr@3.0.0:
|
||||
resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -2472,6 +2540,9 @@ packages:
|
||||
md5.js@1.3.5:
|
||||
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
|
||||
|
||||
md5@2.3.0:
|
||||
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
||||
|
||||
mdast-squeeze-paragraphs@6.0.0:
|
||||
resolution: {integrity: sha512-6NDbJPTg0M0Ye+TlYwX1KJ1LFbp515P2immRJyJQhc9Na9cetHzSoHNYIQcXpANEAP1sm9yd/CTZU2uHqR5A+w==}
|
||||
|
||||
@@ -2642,9 +2713,21 @@ packages:
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
nested-property@4.0.0:
|
||||
resolution: {integrity: sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
@@ -2754,6 +2837,9 @@ packages:
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
path-posix@1.0.0:
|
||||
resolution: {integrity: sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA==}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2884,6 +2970,9 @@ packages:
|
||||
resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==}
|
||||
engines: {node: '>=0.4.x'}
|
||||
|
||||
querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
@@ -2941,6 +3030,9 @@ packages:
|
||||
resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
|
||||
engines: {node: '>=0.10.5'}
|
||||
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -3426,6 +3518,9 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
toastify-js@1.12.0:
|
||||
resolution: {integrity: sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==}
|
||||
|
||||
tributejs@5.1.3:
|
||||
resolution: {integrity: sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==}
|
||||
|
||||
@@ -3487,6 +3582,9 @@ packages:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: '>=4.8.4 <5.9.0'
|
||||
|
||||
typescript-event-target@1.1.1:
|
||||
resolution: {integrity: sha512-dFSOFBKV6uwaloBCCUhxlD3Pr/P1a/tJdcmPrTXCHlEFD3faj0mztjcGn6VBAhQ0/Bdy8K3VWrrqwbt/ffsYsg==}
|
||||
|
||||
typescript@5.8.2:
|
||||
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -3544,6 +3642,13 @@ packages:
|
||||
uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
|
||||
url-join@5.0.0:
|
||||
resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
url-parse@1.5.10:
|
||||
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
||||
|
||||
url@0.11.4:
|
||||
resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3663,6 +3768,14 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
webdav@5.8.0:
|
||||
resolution: {integrity: sha512-iuFG7NamJ41Oshg4930iQgfIpRrUiatPWIekeznYgEf2EOraTRcDPTjy7gIOMtkdpKTaqPk1E68NO5PAGtJahA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -3843,6 +3956,10 @@ snapshots:
|
||||
|
||||
'@bufbuild/protobuf@2.5.2': {}
|
||||
|
||||
'@buttercup/fetch@0.2.1':
|
||||
optionalDependencies:
|
||||
node-fetch: 3.3.2
|
||||
|
||||
'@ckpack/vue-color@1.6.0(vue@3.5.16(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@ctrl/tinycolor': 3.6.1
|
||||
@@ -4038,6 +4155,8 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@mdi/js@7.4.47': {}
|
||||
|
||||
'@microsoft/api-extractor-model@7.30.6(@types/node@20.17.10)':
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.15.1
|
||||
@@ -4096,6 +4215,33 @@ snapshots:
|
||||
dependencies:
|
||||
'@nextcloud/initial-state': 2.2.0
|
||||
|
||||
'@nextcloud/dialogs@7.0.0-rc.0(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@mdi/js': 7.4.47
|
||||
'@nextcloud/auth': 2.5.1
|
||||
'@nextcloud/axios': 2.5.1
|
||||
'@nextcloud/event-bus': 3.3.2
|
||||
'@nextcloud/files': 3.10.2
|
||||
'@nextcloud/initial-state': 2.2.0
|
||||
'@nextcloud/l10n': 3.3.0
|
||||
'@nextcloud/paths': 2.2.1
|
||||
'@nextcloud/router': 3.0.1
|
||||
'@nextcloud/sharing': 0.2.4
|
||||
'@nextcloud/typings': 1.9.1
|
||||
'@nextcloud/vue': 9.0.0-rc.2(typescript@5.8.3)
|
||||
'@types/toastify-js': 1.12.4
|
||||
'@vueuse/core': 13.3.0(vue@3.5.16(typescript@5.8.3))
|
||||
cancelable-promise: 4.3.1
|
||||
p-queue: 8.1.0
|
||||
toastify-js: 1.12.0
|
||||
vue: 3.5.16(typescript@5.8.3)
|
||||
webdav: 5.8.0
|
||||
transitivePeerDependencies:
|
||||
- '@nuxt/kit'
|
||||
- debug
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@nextcloud/eslint-config@8.4.2(@babel/core@7.26.0)(@babel/eslint-parser@7.25.9(@babel/core@7.26.0)(eslint@9.28.0))(@nextcloud/eslint-plugin@2.2.1(eslint@9.28.0))(@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.32.0(eslint@9.28.0))(eslint@9.28.0)(typescript@5.8.3))(eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0)(eslint-plugin-n@16.6.2(eslint@9.28.0))(eslint-plugin-promise@6.6.0(eslint@9.28.0))(eslint@9.28.0))(eslint-import-resolver-exports@1.0.0-beta.5(eslint-plugin-import@2.31.0)(eslint@9.28.0))(eslint-import-resolver-typescript@3.6.3)(eslint-plugin-import@2.31.0)(eslint-plugin-jsdoc@46.10.1(eslint@9.28.0))(eslint-plugin-n@16.6.2(eslint@9.28.0))(eslint-plugin-promise@6.6.0(eslint@9.28.0))(eslint-plugin-vue@9.32.0(eslint@9.28.0))(eslint@9.28.0)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
@@ -4125,6 +4271,20 @@ snapshots:
|
||||
'@types/semver': 7.7.0
|
||||
semver: 7.7.2
|
||||
|
||||
'@nextcloud/files@3.10.2':
|
||||
dependencies:
|
||||
'@nextcloud/auth': 2.5.1
|
||||
'@nextcloud/capabilities': 1.2.0
|
||||
'@nextcloud/l10n': 3.3.0
|
||||
'@nextcloud/logger': 3.0.2
|
||||
'@nextcloud/paths': 2.2.1
|
||||
'@nextcloud/router': 3.0.1
|
||||
'@nextcloud/sharing': 0.2.4
|
||||
cancelable-promise: 4.3.1
|
||||
is-svg: 5.1.0
|
||||
typescript-event-target: 1.1.1
|
||||
webdav: 5.8.0
|
||||
|
||||
'@nextcloud/initial-state@2.2.0': {}
|
||||
|
||||
'@nextcloud/l10n@3.3.0':
|
||||
@@ -4139,6 +4299,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@nextcloud/auth': 2.5.1
|
||||
|
||||
'@nextcloud/paths@2.2.1': {}
|
||||
|
||||
'@nextcloud/router@3.0.1':
|
||||
dependencies:
|
||||
'@nextcloud/typings': 1.9.1
|
||||
@@ -4479,6 +4641,8 @@ snapshots:
|
||||
|
||||
'@types/sizzle@2.3.9': {}
|
||||
|
||||
'@types/toastify-js@1.12.4': {}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
optional: true
|
||||
|
||||
@@ -4968,6 +5132,8 @@ snapshots:
|
||||
|
||||
balanced-match@2.0.0: {}
|
||||
|
||||
base-64@1.0.0: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
blurhash@2.0.5: {}
|
||||
@@ -5071,6 +5237,8 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.2
|
||||
|
||||
byte-length@1.0.2: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -5090,6 +5258,8 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
cancelable-promise@4.3.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001721: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
@@ -5109,6 +5279,8 @@ snapshots:
|
||||
|
||||
character-reference-invalid@2.0.1: {}
|
||||
|
||||
charenc@0.0.2: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
@@ -5216,6 +5388,8 @@ snapshots:
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
crypt@0.0.2: {}
|
||||
|
||||
crypto-browserify@3.12.1:
|
||||
dependencies:
|
||||
browserify-cipher: 1.0.1
|
||||
@@ -5242,6 +5416,8 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -5381,6 +5557,8 @@ snapshots:
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
entities@6.0.0: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
||||
environment@1.1.0: {}
|
||||
@@ -5786,6 +5964,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
file-entry-cache@8.0.0:
|
||||
dependencies:
|
||||
flat-cache: 4.0.1
|
||||
@@ -5839,6 +6022,10 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
fs-extra@11.3.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -6018,6 +6205,8 @@ snapshots:
|
||||
minimalistic-assert: 1.0.1
|
||||
minimalistic-crypto-utils: 1.0.1
|
||||
|
||||
hot-patcher@2.0.1: {}
|
||||
|
||||
html-tags@3.3.1: {}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
@@ -6103,6 +6292,8 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-buffer@1.1.6: {}
|
||||
|
||||
is-builtin-module@3.2.1:
|
||||
dependencies:
|
||||
builtin-modules: 3.3.0
|
||||
@@ -6195,6 +6386,10 @@ snapshots:
|
||||
call-bound: 1.0.4
|
||||
has-tostringtag: 1.0.2
|
||||
|
||||
is-svg@5.1.0:
|
||||
dependencies:
|
||||
fast-xml-parser: 4.5.3
|
||||
|
||||
is-symbol@1.1.1:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -6272,6 +6467,8 @@ snapshots:
|
||||
|
||||
kolorist@1.8.0: {}
|
||||
|
||||
layerr@3.0.0: {}
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@@ -6363,6 +6560,12 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
md5@2.3.0:
|
||||
dependencies:
|
||||
charenc: 0.0.2
|
||||
crypt: 0.0.2
|
||||
is-buffer: 1.1.6
|
||||
|
||||
mdast-squeeze-paragraphs@6.0.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
@@ -6663,9 +6866,19 @@ snapshots:
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
nested-property@4.0.0: {}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
node-stdlib-browser@1.3.1:
|
||||
@@ -6820,6 +7033,8 @@ snapshots:
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
path-posix@1.0.0: {}
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
@@ -6936,6 +7151,8 @@ snapshots:
|
||||
|
||||
querystring-es3@0.2.1: {}
|
||||
|
||||
querystringify@2.2.0: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
randombytes@2.1.0:
|
||||
@@ -7043,6 +7260,8 @@ snapshots:
|
||||
|
||||
requireindex@1.2.0: {}
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
@@ -7605,6 +7824,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
toastify-js@1.12.0: {}
|
||||
|
||||
tributejs@5.1.3: {}
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
@@ -7679,6 +7900,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
typescript-event-target@1.1.1: {}
|
||||
|
||||
typescript@5.8.2: {}
|
||||
|
||||
typescript@5.8.3: {}
|
||||
@@ -7749,6 +7972,13 @@ snapshots:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
url-join@5.0.0: {}
|
||||
|
||||
url-parse@1.5.10:
|
||||
dependencies:
|
||||
querystringify: 2.2.0
|
||||
requires-port: 1.0.0
|
||||
|
||||
url@0.11.4:
|
||||
dependencies:
|
||||
punycode: 1.4.1
|
||||
@@ -7868,6 +8098,25 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
webdav@5.8.0:
|
||||
dependencies:
|
||||
'@buttercup/fetch': 0.2.1
|
||||
base-64: 1.0.0
|
||||
byte-length: 1.0.2
|
||||
entities: 6.0.0
|
||||
fast-xml-parser: 4.5.3
|
||||
hot-patcher: 2.0.1
|
||||
layerr: 3.0.0
|
||||
md5: 2.3.0
|
||||
minimatch: 9.0.5
|
||||
nested-property: 4.0.0
|
||||
node-fetch: 3.3.2
|
||||
path-posix: 1.0.0
|
||||
url-join: 5.0.0
|
||||
url-parse: 1.5.10
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
dependencies:
|
||||
is-bigint: 1.1.0
|
||||
|
||||
79
scaffold.config.cjs
Normal file
79
scaffold.config.cjs
Normal file
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const { format } = require('date-fns')
|
||||
// eslint-disable-next-line no-undef
|
||||
const fs = require('node:fs')
|
||||
|
||||
function getLatestMigration() {
|
||||
const migrationDir = 'lib/migration'
|
||||
const files = fs.readdirSync(migrationDir)
|
||||
const migrationFiles = files.sort((a, b) => a.localeCompare(b))
|
||||
const latestMigration = migrationFiles[migrationFiles.length - 1]
|
||||
const matches = /Version(\d+)/.exec(latestMigration)
|
||||
const version = matches ? Number(matches[1]) + 1 : 0
|
||||
return version
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
module.exports = () => {
|
||||
return {
|
||||
component: {
|
||||
templates: ['gen/component'],
|
||||
output: 'src/components',
|
||||
subDir: false,
|
||||
},
|
||||
page: {
|
||||
templates: ['gen/page'],
|
||||
output: 'src/pages',
|
||||
subDir: false,
|
||||
},
|
||||
command: {
|
||||
templates: ['gen/command'],
|
||||
output: 'lib/Command',
|
||||
subDir: false,
|
||||
},
|
||||
model: {
|
||||
templates: ['gen/model'],
|
||||
output: 'lib/Db',
|
||||
subDir: false,
|
||||
},
|
||||
'task-queued': {
|
||||
templates: ['gen/task-queued'],
|
||||
output: 'lib/Cron',
|
||||
subDir: false,
|
||||
},
|
||||
'task-timed': {
|
||||
templates: ['gen/task-timed'],
|
||||
output: 'lib/Cron',
|
||||
subDir: false,
|
||||
},
|
||||
service: {
|
||||
templates: ['gen/service'],
|
||||
output: 'lib/Service',
|
||||
subDir: false,
|
||||
},
|
||||
util: {
|
||||
templates: ['gen/util'],
|
||||
output: 'lib/Util',
|
||||
subDir: false,
|
||||
},
|
||||
api: {
|
||||
templates: ['gen/api'],
|
||||
output: 'lib/Controller',
|
||||
subDir: false,
|
||||
},
|
||||
migration: () => {
|
||||
const latestMigrationVersion = getLatestMigration()
|
||||
return {
|
||||
templates: ['gen/migration'],
|
||||
output: 'lib/Migration',
|
||||
name: '-',
|
||||
data: {
|
||||
version: latestMigrationVersion,
|
||||
dt: format(new Date(), 'yyyyMMddHHmmss'),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
407
src/Settings.vue
407
src/Settings.vue
@@ -1,220 +1,71 @@
|
||||
<template>
|
||||
|
||||
<div id="jukebox-content" class="section">
|
||||
<div id="jukebox-user-settings" class="section">
|
||||
|
||||
<h2>Jukebox</h2>
|
||||
<NcAppSettingsSection name="Information"
|
||||
>
|
||||
<p> {{ strings.info }} </p>
|
||||
<h2>{{ strings.header }}</h2>
|
||||
|
||||
<p v-html="strings.requirements"></p>
|
||||
|
||||
<ol class="ol">
|
||||
|
||||
<li v-for="li in strings.requirementsList" v-html="li"></li>
|
||||
|
||||
</ol>
|
||||
<NcNoteCard type="info"
|
||||
<form @submit.prevent="save">
|
||||
<NcAppSettingsSection :name="strings.musicLibrarySettings"
|
||||
>
|
||||
<p v-html="strings.infoNote"></p>
|
||||
</NcNoteCard
|
||||
>
|
||||
<p>{{ strings.exampleHeader }}</p>
|
||||
<div class="folder-select-wrapper">
|
||||
|
||||
<ul>
|
||||
|
||||
<li>✅ <code>$</code></li>
|
||||
|
||||
<li>✅ <code>USD</code></li>
|
||||
|
||||
<li>✅ <code>$ USD</code></li>
|
||||
|
||||
<li>❌ <code>US Dollar</code></li>
|
||||
|
||||
<li>❌ <code>United States Dollar</code></li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="currency-list">
|
||||
|
||||
<p>{{ strings.supportedCurrencies }}</p>
|
||||
|
||||
<div style="max-width: 300px">
|
||||
<NcTextField
|
||||
v-model="currencySearch"
|
||||
:label="strings.currencySearchLabel"
|
||||
trailing-button-icon="close"
|
||||
:placeholder="strings.currencySearchPlaceholder"
|
||||
:show-trailing-button="currencySearch !== ''"
|
||||
@trailing-button-click="clearCurrencySearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
|
||||
<th>{{ strings.tableSymbol }}</th>
|
||||
|
||||
<th>{{ strings.tableCode }}</th>
|
||||
|
||||
<th>{{ strings.tableName }}</th>
|
||||
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
<tr v-for="currency in currencies" :key="currency.code">
|
||||
|
||||
<td>{{ currency.symbol }}</td>
|
||||
|
||||
<td>{{ currency.code }}</td>
|
||||
|
||||
<td>{{ currency.name }}</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</NcAppSettingsSection
|
||||
> <NcAppSettingsSection :name="strings.cronSettingsHeader"
|
||||
>
|
||||
<section>
|
||||
|
||||
<form @submit.prevent @submit="save">
|
||||
|
||||
<div class="cron-flex">
|
||||
<NcSelect
|
||||
v-model="interval"
|
||||
:options="intervals"
|
||||
:input-label="strings.intervalLabel"
|
||||
required
|
||||
<div class="input-with-button">
|
||||
<NcTextField
|
||||
v-model="musicFolder"
|
||||
:label="strings.musicFolderLabel"
|
||||
:placeholder="strings.musicFolderPlaceholder"
|
||||
:disabled="true"
|
||||
/> <NcButton
|
||||
@click="openFolderPicker"
|
||||
icon="icon-folder"
|
||||
:aria-label="strings.pickFolder"
|
||||
:title="strings.pickFolder"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<div class="cron-last-update-container">
|
||||
<NcButton @click="doCron" :disabled="loading">{{ strings.fetchNow }}</NcButton
|
||||
>
|
||||
<div>
|
||||
{{ strings.lastFetched }} <span v-if="loading">{{ strings.loading }}</span
|
||||
> <span v-if="!loading && !lastUpdate">{{ strings.never }}</span
|
||||
> <NcDateTime v-if="!loading && lastUpdate" :timestamp="lastUpdate.valueOf()" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="submit-buttons">
|
||||
<NcButton native-type="submit">{{ strings.save }}</NcButton
|
||||
class="folder-button"
|
||||
>{{ strings.pickFolder }}</NcButton
|
||||
>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</NcAppSettingsSection
|
||||
>
|
||||
<div class="submit-buttons">
|
||||
<NcButton type="submit" :disabled="loading">{{ strings.save }}</NcButton
|
||||
>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</section>
|
||||
</NcAppSettingsSection
|
||||
>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
|
||||
import NcSelect from '@nextcloud/vue/components/NcSelect'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
|
||||
import NcDateTime from '@nextcloud/vue/components/NcDateTime'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import { t, n } from '@nextcloud/l10n'
|
||||
import { parseISO as parseDate } from 'date-fns/parseISO'
|
||||
import { format as formatDate } from 'date-fns/format'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import { settingsAxios } from './axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
import '@nextcloud/dialogs/style.css'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
name: 'JukeboxUserSettings',
|
||||
components: {
|
||||
NcAppSettingsSection,
|
||||
NcButton,
|
||||
NcDateTime,
|
||||
NcNoteCard,
|
||||
NcSelect,
|
||||
NcTextField,
|
||||
NcButton,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
interval: null,
|
||||
lastUpdate: null,
|
||||
intervalOptions: [
|
||||
{ label: t('jukebox', 'Every hour'), value: 1 },
|
||||
{ label: n('jukebox', 'Every %n hour', 'Every %n hours', 3), value: 3 },
|
||||
{ label: n('jukebox', 'Every %n hour', 'Every %n hours', 6), value: 6 },
|
||||
{ label: n('jukebox', 'Every %n hour', 'Every %n hours', 9), value: 9 },
|
||||
{ label: n('jukebox', 'Every %n hour', 'Every %n hours', 12), value: 12 },
|
||||
{
|
||||
label: n('jukebox', 'Every %n hour (default)', 'Every %n hours (default)', 24),
|
||||
value: 24,
|
||||
},
|
||||
],
|
||||
supportedCurrencies: [],
|
||||
currencySearch: '',
|
||||
loading: false,
|
||||
musicFolder: '',
|
||||
strings: {
|
||||
info: t(
|
||||
'jukebox',
|
||||
'To make sure your currencies are found for the rates to be updated, please ensure your ' +
|
||||
'currencies are named appropriately.',
|
||||
),
|
||||
requirements: t(
|
||||
'jukebox',
|
||||
'Currency names must contain {bStart}at least one of{bEnd}:',
|
||||
{ bStart: '<b>', bEnd: '</b>' },
|
||||
undefined,
|
||||
{ escape: false },
|
||||
),
|
||||
requirementsList: [
|
||||
t(
|
||||
'jukebox',
|
||||
'The currency symbol - e.g. {cStart}${cEnd}, {cStart}€{cEnd}, {cStart}£{cEnd}',
|
||||
{ cStart: '<code>', cEnd: '</code>' },
|
||||
undefined,
|
||||
{ escape: false },
|
||||
),
|
||||
t(
|
||||
'jukebox',
|
||||
'The currency code - e.g. {cStart}USD{cEnd}, {cStart}EUR{cEnd}, {cStart}GBP{cEnd} (case-insensitive)',
|
||||
{ cStart: '<code>', cEnd: '</code>' },
|
||||
undefined,
|
||||
{ escape: false },
|
||||
),
|
||||
],
|
||||
infoNote: t(
|
||||
'jukebox',
|
||||
'The naming rules apply for both main & additional currencies.',
|
||||
undefined,
|
||||
undefined,
|
||||
{ escape: false },
|
||||
),
|
||||
cronSettingsHeader: t('jukebox', 'Cron Settings'),
|
||||
exampleHeader: t('jukebox', 'Example names:'),
|
||||
supportedCurrencies: t('jukebox', 'Supported currencies:'),
|
||||
currencySearchLabel: t('jukebox', 'Search'),
|
||||
currencySearchPlaceholder: t('jukebox', 'e.g. $, USD, US Dollar'),
|
||||
intervalLabel: t('jukebox', 'Currency conversion rate update interval'),
|
||||
tableSymbol: t('jukebox', 'Symbol'),
|
||||
tableCode: t('jukebox', 'Code'),
|
||||
tableName: t('jukebox', 'Name'),
|
||||
fetchNow: t('jukebox', 'Fetch Rates Now'),
|
||||
lastFetched: t('jukebox', 'Rates last fetched:'),
|
||||
loading: t('jukebox', 'Loading…'),
|
||||
never: t('jukebox', 'Never'),
|
||||
header: t('jukebox', 'Jukebox'),
|
||||
musicLibrarySettings: t('jukebox', 'Music Library'),
|
||||
musicFolderLabel: t('jukebox', 'Music Folder Path'),
|
||||
musicFolderPlaceholder: t('jukebox', 'e.g. Music'),
|
||||
pickFolder: t('jukebox', 'Pick a folder'),
|
||||
save: t('jukebox', 'Save'),
|
||||
},
|
||||
}
|
||||
@@ -224,93 +75,67 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
async fetchSettings() {
|
||||
this.loading = true
|
||||
try {
|
||||
this.loading = true
|
||||
const resp = await axios.get('/cron')
|
||||
const data = resp.data.ocs.data
|
||||
const response = await settingsAxios.get('/settings')
|
||||
const data = response.data.ocs.data
|
||||
this.musicFolder = data.music_folder_path || ''
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch settings:', e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
console.debug('[DEBUG] Jukebox settings fetched', data)
|
||||
|
||||
const interval = this.getIntervalByValue(data.interval)
|
||||
if (interval) {
|
||||
console.debug('[DEBUG] Interval found', interval)
|
||||
this.interval = interval.label
|
||||
} else {
|
||||
console.warn('Invalid interval value', data.interval)
|
||||
}
|
||||
|
||||
if (data.last_update) {
|
||||
const lastUpdate = parseDate(data.last_update, new Date())
|
||||
this.lastUpdate = lastUpdate
|
||||
}
|
||||
|
||||
this.supportedCurrencies = data.supported_currencies.sort((a, b) =>
|
||||
a.code.localeCompare(b.code),
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch Jukebox settings', e)
|
||||
}
|
||||
},
|
||||
getIntervalByValue(value) {
|
||||
return this.intervalOptions.find((x) => x.value === value)
|
||||
},
|
||||
getIntervalByLabel(label) {
|
||||
return this.intervalOptions.find((x) => x.label === label)
|
||||
},
|
||||
async doCron() {
|
||||
try {
|
||||
const resp = await axios.post('/cron/run')
|
||||
const data = resp.data.ocs.data
|
||||
console.debug('[DEBUG] Cron executed', data)
|
||||
this.fetchSettings()
|
||||
} catch (e) {
|
||||
console.error('Failed to run cron', e)
|
||||
}
|
||||
},
|
||||
clearCurrencySearch() {
|
||||
this.currencySearch = ''
|
||||
},
|
||||
async save() {
|
||||
this.loading = true
|
||||
try {
|
||||
this.loading = true
|
||||
const interval = this.getIntervalByLabel(this.interval)?.value ?? 24
|
||||
const resp = await axios.put('/cron', { data: { interval } })
|
||||
const data = resp.data.ocs.data
|
||||
this.loading = false
|
||||
console.debug('[DEBUG] Jukebox settings saved', data)
|
||||
this.fetchSettings()
|
||||
const data = {
|
||||
music_folder_path: this.musicFolder,
|
||||
}
|
||||
console.log('Saving settings :', data)
|
||||
await settingsAxios.put('/settings', { data })
|
||||
} catch (e) {
|
||||
console.error('Failed to update Jukebox settings', e)
|
||||
console.error('Failed to save settings:', e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
intervals() {
|
||||
return this.intervalOptions.map((x) => x.label)
|
||||
},
|
||||
currencies() {
|
||||
if (!this.supportedCurrencies) {
|
||||
return []
|
||||
}
|
||||
if (!this.currencySearch) {
|
||||
return this.supportedCurrencies
|
||||
}
|
||||
async openFolderPicker() {
|
||||
try {
|
||||
const picker = getFilePickerBuilder(this.strings.musicFolderLabel)
|
||||
.allowDirectories(true)
|
||||
.addButton({
|
||||
label: t('jukebox', 'Select'),
|
||||
callback: (nodes) => {
|
||||
console.log('Selected nodes:', nodes)
|
||||
const node = nodes?.[0]
|
||||
if (!node || !node._data?.root || !node._data?.attributes?.filename) return
|
||||
const root = node._data.root
|
||||
const fullPath = node._data.attributes.filename
|
||||
this.musicFolder = fullPath.startsWith(root)
|
||||
? fullPath.slice(root.length) || '/'
|
||||
: fullPath
|
||||
if (this.musicFolder.startsWith('/')) {
|
||||
this.musicFolder = this.musicFolder.slice(1)
|
||||
}
|
||||
console.log('Selected folder path:', this.musicFolder)
|
||||
},
|
||||
})
|
||||
.build()
|
||||
|
||||
return this.supportedCurrencies.filter((currency) => {
|
||||
return [
|
||||
currency.symbol.toLowerCase().includes(this.currencySearch.toLowerCase()),
|
||||
currency.code.toLowerCase().includes(this.currencySearch.toLowerCase()),
|
||||
currency.name.toLowerCase().includes(this.currencySearch.toLowerCase()),
|
||||
].some(Boolean)
|
||||
})
|
||||
await picker.pick()
|
||||
} catch (e) {
|
||||
if (e.message.includes('No nodes selected')) return
|
||||
console.error('Failed to open folder picker:', e)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#jukebox-content {
|
||||
h2:first-child {
|
||||
<style scoped>
|
||||
#jukebox-user-settings {
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -318,64 +143,14 @@ export default {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.cron-flex {
|
||||
.input-with-button {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.cron-last-update-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 2.5em;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--color-border);
|
||||
|
||||
tr:not(:last-child),
|
||||
thead tr {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
tbody {
|
||||
display: block;
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody tr {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.currency-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 2em;
|
||||
.folder-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
7
src/axios.ts
Normal file
7
src/axios.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
const baseURL = generateOcsUrl('/apps/jukebox/api')
|
||||
export const settingsAxios = axios.create({
|
||||
baseURL,
|
||||
})
|
||||
@@ -1,12 +1,8 @@
|
||||
import { settingsAxios } from './axios'
|
||||
import Settings from './Settings.vue'
|
||||
import './style.scss'
|
||||
import { createApp } from 'vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
|
||||
const baseURL = generateOcsUrl('/apps/jukebox/api')
|
||||
axios.defaults.baseURL = baseURL
|
||||
|
||||
console.log('[DEBUG] Mounting jukebox Settings')
|
||||
console.log('[DEBUG] Base URL:', baseURL)
|
||||
console.log('[DEBUG] Base URL:', settingsAxios.defaults.baseURL)
|
||||
createApp(Settings).mount('#jukebox-settings')
|
||||
|
||||
1
version.txt
Normal file
1
version.txt
Normal file
@@ -0,0 +1 @@
|
||||
0.1.0
|
||||
@@ -15,8 +15,15 @@ export default createAppConfig(
|
||||
cssCodeSplit: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['vue', 'vue-router'],
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('@nextcloud/dialogs')) return 'nextcloud-dialogs'
|
||||
if (id.includes('@nextcloud/vue')) return 'nextcloud-vue'
|
||||
if (id.includes('vue')) return 'vue'
|
||||
if (id.includes('vue-router')) return 'vue-router'
|
||||
if (id.includes('axios')) return 'axios'
|
||||
return 'vendor' // fallback for other deps
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user