Skip to content

Commit

Permalink
Add support of multiple mimes in File Actions (#144)
Browse files Browse the repository at this point in the history
Resolves: #95

- [x] Add support of multiple mimes in filesplugin
- [x] Rename UI tables for naming consistency

---------

Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com>
  • Loading branch information
andrey18106 authored Dec 5, 2023
1 parent 86ae743 commit 3c6a3fe
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 95 deletions.
42 changes: 11 additions & 31 deletions lib/Db/UI/FilesActionsMenuMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class FilesActionsMenuMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'ex_files_actions_menu');
parent::__construct($db, 'ex_ui_files_actions');
}

/**
Expand All @@ -25,17 +25,17 @@ public function __construct(IDBConnection $db) {
public function findAllEnabled(): array {
$qb = $this->db->getQueryBuilder();
$result = $qb->select(
'ex_files_actions_menu.appid',
'ex_files_actions_menu.name',
'ex_files_actions_menu.display_name',
'ex_files_actions_menu.mime',
'ex_files_actions_menu.permissions',
'ex_files_actions_menu.order',
'ex_files_actions_menu.icon',
'ex_files_actions_menu.action_handler',
'ex_ui_files_actions.appid',
'ex_ui_files_actions.name',
'ex_ui_files_actions.display_name',
'ex_ui_files_actions.mime',
'ex_ui_files_actions.permissions',
'ex_ui_files_actions.order',
'ex_ui_files_actions.icon',
'ex_ui_files_actions.action_handler',
)
->from($this->tableName, 'ex_files_actions_menu')
->innerJoin('ex_files_actions_menu', 'ex_apps', 'exa', 'exa.appid = ex_files_actions_menu.appid')
->from($this->tableName, 'ex_ui_files_actions')
->innerJoin('ex_ui_files_actions', 'ex_apps', 'exa', 'exa.appid = ex_ui_files_actions.appid')
->where(
$qb->expr()->eq('exa.enabled', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT))
)
Expand Down Expand Up @@ -64,26 +64,6 @@ public function findByAppidName(string $appId, string $name): FilesActionsMenu {
return $this->findEntity($qb);
}

/**
* @param string $name
*
*
* @return FilesActionsMenu
* @throws DoesNotExistException|Exception if not found
* @throws Exception
*
* @throws MultipleObjectsReturnedException if more than one result
*/
public function findByName(string $name): FilesActionsMenu {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->tableName)
->where(
$qb->expr()->eq('name', $qb->createNamedParameter($name, IQueryBuilder::PARAM_STR))
);
return $this->findEntity($qb);
}

/**
* @throws Exception
*/
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/UI/InitialStateMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class InitialStateMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'ex_apps_ui_states');
parent::__construct($db, 'ex_ui_states');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/UI/ScriptMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class ScriptMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'ex_apps_ui_scripts');
parent::__construct($db, 'ex_ui_scripts');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/UI/StyleMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class StyleMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'ex_apps_ui_styles');
parent::__construct($db, 'ex_ui_styles');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/Db/UI/TopMenuMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
class TopMenuMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'ex_apps_ui_top_menu');
parent::__construct($db, 'ex_ui_top_menu');
}

/**
Expand Down
66 changes: 56 additions & 10 deletions lib/Migration/Version1003Date202311061844.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

/**
* Breaking changes migration refactoring UI tables (renames)
*/
class Version1003Date202311061844 extends SimpleMigrationStep {
/**
* @param IOutput $output
Expand All @@ -22,8 +25,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

if (!$schema->hasTable('ex_apps_ui_top_menu')) {
$table = $schema->createTable('ex_apps_ui_top_menu');
if (!$schema->hasTable('ex_ui_top_menu')) {
$table = $schema->createTable('ex_ui_top_menu');

$table->addColumn('id', Types::BIGINT, [
'notnull' => true,
Expand Down Expand Up @@ -55,8 +58,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
$table->addUniqueIndex(['appid', 'name'], 'ui_top_menu__idx');
}

if (!$schema->hasTable('ex_apps_ui_states')) {
$table = $schema->createTable('ex_apps_ui_states');
if (!$schema->hasTable('ex_ui_states')) {
$table = $schema->createTable('ex_ui_states');

$table->addColumn('id', Types::BIGINT, [
'notnull' => true,
Expand Down Expand Up @@ -86,8 +89,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
$table->addUniqueIndex(['appid', 'type', 'name', 'key'], 'ui_state__idx');
}

if (!$schema->hasTable('ex_apps_ui_scripts')) {
$table = $schema->createTable('ex_apps_ui_scripts');
if (!$schema->hasTable('ex_ui_scripts')) {
$table = $schema->createTable('ex_ui_scripts');

$table->addColumn('id', Types::BIGINT, [
'notnull' => true,
Expand Down Expand Up @@ -118,8 +121,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
$table->addUniqueIndex(['appid', 'type', 'name', 'path'], 'ui_script__idx');
}

if (!$schema->hasTable('ex_apps_ui_styles')) {
$table = $schema->createTable('ex_apps_ui_styles');
if (!$schema->hasTable('ex_ui_styles')) {
$table = $schema->createTable('ex_ui_styles');

$table->addColumn('id', Types::BIGINT, [
'notnull' => true,
Expand Down Expand Up @@ -147,8 +150,51 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
}

if ($schema->hasTable('ex_files_actions_menu')) {
$table = $schema->getTable('ex_files_actions_menu');
$table->dropColumn('icon_class');
$schema->dropTable('ex_files_actions_menu');
}

if (!$schema->hasTable('ex_ui_files_actions')) {
$table = $schema->createTable('ex_ui_files_actions');

$table->addColumn('id', Types::BIGINT, [
'notnull' => true,
'autoincrement' => true,
]);
$table->addColumn('appid', Types::STRING, [
'notnull' => true,
'length' => 32,
]);
$table->addColumn('name', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('display_name', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('mime', Types::TEXT, [
'notnull' => true,
'default' => 'file',
]);
$table->addColumn('permissions', Types::STRING, [
'notnull' => true,
]);
$table->addColumn('order', Types::BIGINT, [
'notnull' => true,
'default' => 0,
]);
$table->addColumn('icon', Types::STRING, [
'notnull' => false,
'default' => '',
]);
// Action handler key name, that will be sent to exApp for handling
$table->addColumn('action_handler', Types::STRING, [
'notnull' => true,
'length' => 64,
]);

$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['appid', 'name'], 'ex_ui_files_actions__idx');
}

return $schema;
Expand Down
14 changes: 7 additions & 7 deletions lib/Service/UI/FilesActionsMenuService.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function __construct(
private readonly FilesActionsMenuMapper $mapper,
private readonly LoggerInterface $logger,
) {
$this->cache = $cacheFactory->createDistributed(Application::APP_ID . '/ex_files_actions_menu');
$this->cache = $cacheFactory->createDistributed(Application::APP_ID . '/ex_ui_files_actions');
}

/**
Expand Down Expand Up @@ -61,7 +61,7 @@ public function registerFileActionMenu(string $appId, string $name, string $disp
$newFileActionMenu->setId($fileActionMenu->getId());
}
$fileActionMenu = $this->mapper->insertOrUpdate($newFileActionMenu);
$this->cache->set('/ex_files_actions_menu_' . $appId . '_' . $name, $fileActionMenu);
$this->cache->set('/ex_ui_files_actions_' . $appId . '_' . $name, $fileActionMenu);
$this->resetCacheEnabled();
} catch (Exception $e) {
$this->logger->error(
Expand All @@ -79,7 +79,7 @@ public function unregisterFileActionMenu(string $appId, string $name): ?FilesAct
return null;
}
$this->mapper->delete($fileActionMenu);
$this->cache->remove('/ex_files_actions_menu_' . $appId . '_' . $name);
$this->cache->remove('/ex_ui_files_actions_' . $appId . '_' . $name);
$this->resetCacheEnabled();
return $fileActionMenu;
} catch (Exception $e) {
Expand All @@ -95,7 +95,7 @@ public function unregisterFileActionMenu(string $appId, string $name): ?FilesAct
*/
public function getRegisteredFileActions(): ?array {
try {
$cacheKey = '/ex_files_actions_menus';
$cacheKey = '/ex_ui_files_actions';
$cached = $this->cache->get($cacheKey);
if ($cached !== null) {
return array_map(function ($cacheEntry) {
Expand All @@ -112,7 +112,7 @@ public function getRegisteredFileActions(): ?array {
}

public function getExAppFileAction(string $appId, string $fileActionName): ?FilesActionsMenu {
$cacheKey = '/ex_files_actions_menu_' . $appId . '_' . $fileActionName;
$cacheKey = '/ex_ui_files_actions_' . $appId . '_' . $fileActionName;
$cache = $this->cache->get($cacheKey);
if ($cache !== null) {
return $cache instanceof FilesActionsMenu ? $cache : new FilesActionsMenu($cache);
Expand All @@ -133,12 +133,12 @@ public function unregisterExAppFileActions(string $appId): int {
} catch (Exception) {
$result = -1;
}
$this->cache->clear('/ex_files_actions_menu_' . $appId);
$this->cache->clear('/ex_ui_files_actions_' . $appId);
$this->resetCacheEnabled();
return $result;
}

public function resetCacheEnabled(): void {
$this->cache->remove('/ex_files_actions_menus');
$this->cache->remove('/ex_ui_files_actions');
}
}
97 changes: 54 additions & 43 deletions src/filesplugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,49 +31,52 @@ function generateAppAPIProxyUrl(appId, route) {

if (OCA.Files && OCA.Files.fileActions) {
state.fileActions.forEach(fileAction => {
const action = {
name: fileAction.name,
displayName: t(fileAction.appid, fileAction.display_name),
mime: fileAction.mime,
permissions: Number(fileAction.permissions),
order: Number(fileAction.order),
icon: fileAction.icon !== '' ? generateAppAPIProxyUrl(fileAction.appid, fileAction.icon) : null,
iconClass: fileAction.icon === '' ? 'icon-app-api' : '',
actionHandler: (fileName, context) => {
const file = context.$file[0]
const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler)
axios.post(exAppFileActionHandler, {
fileId: Number(file.dataset.id),
name: fileName,
directory: file.dataset.path,
etag: file.dataset.etag,
mime: file.dataset.mime,
favorite: file.dataset.favorite || 'false',
permissions: Number(file.dataset.permissions),
fileType: file.dataset.type,
size: Number(file.dataset.size),
mtime: Number(file.dataset.mtime) / 1000, // convert ms to s
shareTypes: file.dataset?.shareTypes || null,
shareAttributes: file.dataset?.shareAttributes || null,
sharePermissions: file.dataset?.sharePermissions || null,
shareOwner: file.dataset?.shareOwner || null,
shareOwnerId: file.dataset?.shareOwnerId || null,
userId: getCurrentUser().uid,
instanceId: state.instanceId,
}).then((response) => {
if (response.status === 200) {
OC.dialogs.info(t('app_api', 'Action request sent to ExApp'), t(fileAction.appid, fileAction.display_name))
} else {
console.debug(response)
const mimes = fileAction.mime.split(',').map(mime => mime.trim()) // multiple mimes are separated by comma
mimes.forEach((mimeType) => {
const action = {
name: fileAction.name,
displayName: t(fileAction.appid, fileAction.display_name),
mime: mimeType,
permissions: Number(fileAction.permissions),
order: Number(fileAction.order),
icon: fileAction.icon !== '' ? generateAppAPIProxyUrl(fileAction.appid, fileAction.icon) : null,
iconClass: fileAction.icon === '' ? 'icon-app-api' : '',
actionHandler: (fileName, context) => {
const file = context.$file[0]
const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler)
axios.post(exAppFileActionHandler, {
fileId: Number(file.dataset.id),
name: fileName,
directory: file.dataset.path,
etag: file.dataset.etag,
mime: file.dataset.mime,
favorite: file.dataset.favorite || 'false',
permissions: Number(file.dataset.permissions),
fileType: file.dataset.type,
size: Number(file.dataset.size),
mtime: Number(file.dataset.mtime) / 1000, // convert ms to s
shareTypes: file.dataset?.shareTypes || null,
shareAttributes: file.dataset?.shareAttributes || null,
sharePermissions: file.dataset?.sharePermissions || null,
shareOwner: file.dataset?.shareOwner || null,
shareOwnerId: file.dataset?.shareOwnerId || null,
userId: getCurrentUser().uid,
instanceId: state.instanceId,
}).then((response) => {
if (response.status === 200) {
OC.dialogs.info(t('app_api', 'Action request sent to ExApp'), t(fileAction.appid, fileAction.display_name))
} else {
console.debug(response)
OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name))
}
}).catch((error) => {
console.error('error', error)
OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name))
}
}).catch((error) => {
console.error('error', error)
OC.dialogs.info(t('app_api', 'Error while sending File action request to ExApp'), t(fileAction.appid, fileAction.display_name))
})
},
}
OCA.Files.fileActions.registerAction(action)
})
},
}
OCA.Files.fileActions.registerAction(action)
})
})
} else {
state.fileActions.forEach(fileAction => {
Expand All @@ -99,7 +102,15 @@ if (OCA.Files && OCA.Files.fileActions) {
return false
}

return (files[0].mime.indexOf(fileAction.mime) !== -1)
// Check for multiple mimes separated by comma
let isMimeMatch = false
fileAction.mime.split(',').forEach((mime) => {
if (files[0].mime.indexOf(mime.trim()) !== -1) {
isMimeMatch = true
}
})

return isMimeMatch
},
async exec(node) {
const exAppFileActionHandler = generateAppAPIProxyUrl(fileAction.appid, fileAction.action_handler)
Expand Down

0 comments on commit 3c6a3fe

Please sign in to comment.