Skip to content

Commit

Permalink
Manage setting configs files with dynamic routes
Browse files Browse the repository at this point in the history
- Implement dynamic routing for settings files, enabling URLs structured as /settings/{type}/{category}/{filename}.
- Support various setting types (e.g. userconfigs, sharedconfigs) and categories (e.g. autotext, wordbook) so that multiple files can be stored for each category.
- Ensure proper URL parsing and directory handling for uploading and retrieving files via the WOPI interface.

Signed-off-by: codewithvk <vivek.javiya@collabora.com>
  • Loading branch information
codewithvk committed Jan 11, 2025
1 parent e1f8021 commit cc91ef8
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 16 deletions.
10 changes: 10 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@
['name' => 'settings#getUserFileList', 'url' => 'settings/user-files.json', 'verb' => 'GET'],
['name' => 'settings#downloadUserFile', 'url' => 'settings/user-files/{fileName}', 'verb' => 'GET'],
['name' => 'settings#deleteSystemFile', 'url' => 'settings/system-files/{fileName}', 'verb' => 'DELETE'],
[
'name' => 'settings#getSettingsFile',
'url' => 'settings/{type}/{category}/{name}',
'verb' => 'GET',
'requirements' => [
'type' => '[a-zA-Z0-9_\-]+',
'category' => '[a-zA-Z0-9_\-]+',
'name' => '.+',
],
],

// Direct Editing: Webview
['name' => 'directView#show', 'url' => '/direct/{token}', 'verb' => 'GET'],
Expand Down
1 change: 1 addition & 0 deletions composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
'OCA\\Richdocuments\\TokenManager' => $baseDir . '/../lib/TokenManager.php',
'OCA\\Richdocuments\\UploadException' => $baseDir . '/../lib/UploadException.php',
'OCA\\Richdocuments\\WOPI\\Parser' => $baseDir . '/../lib/WOPI/Parser.php',
'OCA\\Richdocuments\\WOPI\\SettingsUrl' => $baseDir . '/../lib/WOPI/SettingsUrl.php',
'mikehaertl\\pdftk\\Command' => $vendorDir . '/mikehaertl/php-pdftk/src/Command.php',
'mikehaertl\\pdftk\\DataFields' => $vendorDir . '/mikehaertl/php-pdftk/src/DataFields.php',
'mikehaertl\\pdftk\\FdfFile' => $vendorDir . '/mikehaertl/php-pdftk/src/FdfFile.php',
Expand Down
1 change: 1 addition & 0 deletions composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ComposerStaticInitRichdocuments
'OCA\\Richdocuments\\TokenManager' => __DIR__ . '/..' . '/../lib/TokenManager.php',
'OCA\\Richdocuments\\UploadException' => __DIR__ . '/..' . '/../lib/UploadException.php',
'OCA\\Richdocuments\\WOPI\\Parser' => __DIR__ . '/..' . '/../lib/WOPI/Parser.php',
'OCA\\Richdocuments\\WOPI\\SettingsUrl' => __DIR__ . '/..' . '/../lib/WOPI/SettingsUrl.php',
'mikehaertl\\pdftk\\Command' => __DIR__ . '/..' . '/mikehaertl/php-pdftk/src/Command.php',
'mikehaertl\\pdftk\\DataFields' => __DIR__ . '/..' . '/mikehaertl/php-pdftk/src/DataFields.php',
'mikehaertl\\pdftk\\FdfFile' => __DIR__ . '/..' . '/mikehaertl/php-pdftk/src/FdfFile.php',
Expand Down
30 changes: 30 additions & 0 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use OCP\IURLGenerator;
use OCP\PreConditionNotMetException;
use OCP\Util;
use OCA\Richdocuments\WOPI\SettingsUrl;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Output\NullOutput;

Expand Down Expand Up @@ -623,6 +624,35 @@ public function downloadUserFile(string $fileName) {
}
}

/**
* @param string $type
* @param string $category
* @param string $name
*
* @return DataDisplayResponse
*
* @NoAdminRequired
* @PublicPage
* @NoCSRFRequired
**/
public function getSettingsFile(string $type, string $category, string $name) {
try {
$systemFile = $this->settingsService->getSettingsFile($type, $category, $name);
return new DataDisplayResponse(
$systemFile->getContent(),
200,
[
'Content-Type' => $systemFile->getMimeType() ?: 'application/octet-stream'
]
);
} catch (NotFoundException $e) {
return new DataDisplayResponse('File not found.', 404);
} catch (\Exception $e) {
return new DataDisplayResponse('Something went wrong', 500);
}
}


/**
* @param string $key
* @return array
Expand Down
28 changes: 12 additions & 16 deletions lib/Controller/WopiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use OCA\Richdocuments\Service\SettingsService;
use \OCA\Richdocuments\WOPI\SettingsUrl;

#[RestrictToWopiServer]
class WopiController extends Controller {
Expand Down Expand Up @@ -374,30 +375,24 @@ public function getFile(string $fileId, string $access_token): JSONResponse|Stre
#[PublicPage]
#[FrontpageRoute(verb: 'GET', url: 'wopi/settings')]
public function getSettings(string $type, string $access_token): JSONResponse {
if ($type !== 'UserSettingsUri') {
if (empty($type)) {
return new JSONResponse(['error' => 'Invalid type parameter'], Http::STATUS_BAD_REQUEST);
}

try {
$wopi = $this->wopiMapper->getWopiForToken($access_token);

if ($wopi->getTokenType() !== Wopi::TOKEN_TYPE_SETTING_AUTH) {
return new JSONResponse(['error' => 'Invalid token type'], Http::STATUS_FORBIDDEN);
}

// user admin check
$user = $this->userManager->get($wopi->getEditorUid());
if (!$user || !$this->groupManager->isAdmin($user->getUID())) {
return new JSONResponse(['error' => 'Access denied'], Http::STATUS_FORBIDDEN);
}

$systemFiles = $this->settingsService->getSystemFiles();
$formattedList = $this->settingsService->getSystemFileList($systemFiles);

$response = new JSONResponse($formattedList);

return $response;
} catch (UnknownTokenException|ExpiredTokenException $e) {
$userConfig = $this->settingsService->generateSettingsConfig($type);
return new JSONResponse($userConfig, Http::STATUS_OK);
} catch (UnknownTokenException | ExpiredTokenException $e) {
$this->logger->debug($e->getMessage(), ['exception' => $e]);
return new JSONResponse(['error' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED);
} catch (\Exception $e) {
Expand All @@ -422,16 +417,17 @@ public function handleSettingsFile(string $fileId, string $access_token): JSONRe
if (!$content) {
throw new \Exception("Failed to read input stream.");
}

$fileContent = stream_get_contents($content);
fclose($content);

// TODO: JSON based upload
$result = $this->settingsService->uploadSystemFile($fileId, $fileContent);


// Use the fileId as a file path URL (e.g., "/settings/systemconfig/wordbook/en_US%20(1).dic")
$settingsUrl = new SettingsUrl($fileId);
$result = $this->settingsService->uploadFile($settingsUrl, $fileContent);

return new JSONResponse([
'status' => 'success',
'filename' => $newFileName,
'filename' => $settingsUrl->getFileName(),
'details' => $result,
], Http::STATUS_OK);

Expand Down
165 changes: 165 additions & 0 deletions lib/Service/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
use OCA\Richdocuments\WOPI\SettingsUrl;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IURLGenerator;
Expand All @@ -36,6 +37,170 @@ public function __construct(
$this->cache = $cacheFactory->createDistributed(Application::APPNAME);
}

// TODO: Implement file caching...

/**
* Ensure the settings directory exists, if it doesn't exist then create it.
*
* @param SettingsUrl $settingsUrl
* @return ISimpleFolder
*/

public function ensureDirectory(SettingsUrl $settingsUrl): ISimpleFolder {
$type = $settingsUrl->getType();
$category = $settingsUrl->getCategory();

try {
$baseFolder = $this->appData->getFolder($type);
} catch (NotFoundException $e) {
$baseFolder = $this->appData->newFolder($type);
}

try {
$categoryFolder = $baseFolder->getFolder($category);
} catch (NotFoundException $e) {
$categoryFolder = $baseFolder->newFolder($category);
}

return $categoryFolder;
}

/**
* Upload a file to the settings directory.
* ex. $type/$category/$filename
*
* @param SettingsUrl $settingsUrl
* @param resource $fileData
* @return array ['stamp' => string, 'uri' => string]
*/

public function uploadFile(SettingsUrl $settingsUrl, $fileData): array {
$categoryFolder = $this->ensureDirectory($settingsUrl);
$fileName = $settingsUrl->getFileName();
$newFile = $categoryFolder->newFile($fileName, $fileData);
$fileUri = $this->generateFileUri($settingsUrl->getType(), $settingsUrl->getCategory(), $fileName);

return [
'stamp' => $newFile->getETag(),
'uri' => $fileUri,
];
}

/**
* Get list of files in a setting category.
*
* @param string $type
* @param string $category
* @return array Each item has 'stamp' and 'uri'.
*/
public function getCategoryFileList(string $type, string $category): array {
try {
$categoryFolder = $this->appData->getFolder($type . '/' . $category);
} catch (NotFoundException $e) {
return [];
}

$files = $categoryFolder->getDirectoryListing();

return array_map(function(ISimpleFile $file) use ($type, $category) {
return [
'stamp' => $file->getETag(),
'uri' => $this->generateFileUri($type, $category, $file->getName()),
];
}, $files);
}

/**
* generate setting config
*
* @param string $type
* @return array
*/
public function generateSettingsConfig(string $type): array {
$kind = $type === 'userconfig' ? 'user' : 'shared';

$config = [
'kind' => $kind,
];

$categories = $this->getAllCategories($type);

foreach ($categories as $category) {
$files = $this->getCategoryFileList($type, $category);
$config[$category] = $files;
}

return $config;
}

/**
* Get all setting categories for a setting type.
*
* @param string $type
* @return string[]
*/
private function getAllCategories(string $type): array {
try {
$categories = [];
$directories = $this->appData->getFolder($type)->getFullDirectoryListing();
foreach ($directories as $dir) {
if ($dir instanceof ISimpleFolder) {
$categories[] = $dir->getName();
}
}
return $categories;
} catch (NotFoundException $e) {
return [];
}
}

/**
* Generate file URL.
*
* @param string $type
* @param string $category
* @param string $fileName
* @return string
*/
private function generateFileUri(string $type, string $category, string $fileName): string {
return $this->urlGenerator->linkToRouteAbsolute(
'richdocuments.settings.getSettingsFile',
[
'type' => $type,
'category' => $category,
'name' => $fileName,
]
);
}

/**
* Get a specific settings file.
*
* @param string $type
* @param string $category
* @param string $name
* @return ISimpleFile
*/
public function getSettingsFile(string $type, string $category, string $name): ISimpleFile {
try {
$baseFolder = $this->appData->getFolder($type);
} catch (NotFoundException $e) {
throw new NotFoundException("Type folder '{$type}' not found.");
}

try {
$categoryFolder = $baseFolder->getFolder($category);
} catch (NotFoundException $e) {
throw new NotFoundException("Category folder '{$category}' not found in type '{$type}'.");
}

try {
return $categoryFolder->getFile($name);
} catch (NotFoundException $e) {
throw new NotFoundException("File '{$name}' not found in category '{$category}' for type '{$type}'.");
}
}

/**
* Get or create the system-wide folder in app data.
*
Expand Down
Loading

0 comments on commit cc91ef8

Please sign in to comment.