Skip to content

feat: allow the data folder name to be set server-wide - #632

Merged
julien-nc merged 5 commits into
nextcloud:mainfrom
bakiburakogun:feat/configurable-data-folder
Aug 28, 2026
Merged

feat: allow the data folder name to be set server-wide#632
julien-nc merged 5 commits into
nextcloud:mainfrom
bakiburakogun:feat/configurable-data-folder

Conversation

@bakiburakogun

Copy link
Copy Markdown
Contributor

Fixes #631

Summary

Application::ASSISTANT_DATA_FOLDER_NAME is a class constant, so a server whose users do not work in English has no supported way to give new accounts a localized name for the folder the assistant creates in their files.

The per-user data_folder setting does not fill the gap. When the configured folder does not exist yet, getAssistantDataFolder() falls through to createAssistantDataFolder(), which reads the constant rather than the configured name, and the caller then writes the created name back over the preference:

$dataFolder = $this->createAssistantDataFolder($userId);
$dataFolderName = $dataFolder->getName();
$this->config->setUserValue($userId, Application::APP_ID, 'data_folder', $dataFolderName);

So occ user:setting <uid> assistant data_folder Something on a user who has not used the assistant yet creates Assistant and silently reverts the setting.

What this changes

Resolve the default from an app config value, keeping the constant as the last resort, and pass the resolved name down so the creation path uses it:

$defaultFolderName = $this->config->getAppValue(Application::APP_ID, 'default_data_folder', Application::ASSISTANT_DATA_FOLDER_NAME) ?: Application::ASSISTANT_DATA_FOLDER_NAME;
$dataFolderName = $this->config->getUserValue($userId, Application::APP_ID, 'data_folder', $defaultFolderName) ?: $defaultFolderName;

createAssistantDataFolder() now takes the base name instead of reading the constant, keeping its existing " 1", " 2" collision suffixes.

An administrator can then run:

occ config:app:set assistant default_data_folder --value="Bilge8"

This is the same shape Talk uses for its attachment folder (default_attachment_folder in spreed/lib/Config.php): an app-level default behind the per-user value.

Behaviour

  • A server that sets no app config behaves exactly as before.
  • Users who already have a folder keep it, because the per-user value still wins and is written on first use.
  • The per-user setting now works for a folder that does not exist yet, which it did not before.

Testing

Running on Nextcloud 34 with the equivalent change against the bundled version, on a server with several thousand accounts. New accounts get the configured folder name, existing accounts keep theirs, and unsetting the app config restores the previous behaviour.

php-cs-fixer and psalm are clean for the changed file.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 46 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ebfa7088-5a3f-4a19-b5c5-1158790e8b11

📥 Commits

Reviewing files that changed from the base of the PR and between d336728 and d3dcaa7.

📒 Files selected for processing (1)
  • lib/Service/AssistantService.php
📝 Walkthrough

Walkthrough

AssistantService reads the application-wide default_data_folder setting and uses the built-in folder name when no value exists. User-specific data_folder settings continue to override the application default. New folders use the resolved name as their base, while recursive collision handling retains up to three suffix attempts. Admin and personal settings expose these values and persist changes.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: adding a server-wide setting for the Assistant data folder name.
Description check ✅ Passed The description explains the server-wide default, per-user override behavior, folder creation fix, fallback behavior, and testing. It is directly related to the changeset.
Linked Issues check ✅ Passed The changes satisfy the coding objectives in issue [#631]. They add the app-level default, preserve the constant fallback and existing folders, make per-user settings work for new folders, preserve co…
Out of Scope Changes check ✅ Passed The changes are within the linked issue scope. The settings UI additions and delayed-save correction directly support the new server-wide and per-user configuration options. No unrelated code changes …
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files. (2 skipped: 2 u…
Full details: Linked Issues check

Explanation

The changes satisfy the coding objectives in issue [#631]. They add the app-level default, preserve the constant fallback and existing folders, make per-user settings work for new folders, preserve collision suffixes, and expose the settings in the admin and personal interfaces.

Full details: Out of Scope Changes check

Explanation

The changes are within the linked issue scope. The settings UI additions and delayed-save correction directly support the new server-wide and per-user configuration options. No unrelated code changes are identified.

Full details: Docstring Coverage

Explanation

Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files. (2 skipped: 2 unsupported.)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 61a7cbc4-d691-4b68-9788-5791e77df722

📥 Commits

Reviewing files that changed from the base of the PR and between ad30559 and 05605a8.

📒 Files selected for processing (1)
  • lib/Service/AssistantService.php

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread lib/Service/AssistantService.php Outdated

@julien-nc julien-nc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. This would need to be documented at some point or even better, allow to change those values in the admin settings (the default) and user settings (the user value).

Comment thread lib/Service/AssistantService.php Outdated
}
// it does not exist or is not a folder or does not have write permissions: we create one
$dataFolder = $this->createAssistantDataFolder($userId);
$dataFolder = $this->createAssistantDataFolder($userId, $defaultFolderName);

@julien-nc julien-nc Aug 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$dataFolder = $this->createAssistantDataFolder($userId, $defaultFolderName);
$dataFolder = $this->createAssistantDataFolder($userId, $dataFolderName);

The default one is used only if the user didn't define a value for data_folder. With the current line, only the default value is used and the user value is never used.

@bakiburakogun

Copy link
Copy Markdown
Contributor Author

Thanks both — pushed two commits.

@coderabbitai was right, and it was the same class of bug the PR set out to fix, just one line further down. createAssistantDataFolder() was passed $defaultFolderName instead of the resolved $dataFolderName, so a user whose configured folder had been deleted would get one named after the administrator's default, and their own setting was then overwritten with it. Fixed in c8c9b06.

@julien-nc — added both settings rather than only documenting it, since that was the option you preferred:

  • Admin settings get a "Data folder" section with the server-wide default (default_data_folder).
  • Personal settings get the per-user override (data_folder), with the administrator's default shown as the placeholder. Leaving it empty falls back to the default, so the precedence is visible in the interface rather than buried in the code.

Both use the existing config endpoints, so no new routes. I moved the app-config read to IAppConfig::getValueString(..., lazy: true) to match how setAdminConfig() writes and how the rest of the app reads its lazy values.

Two things I did not do, happy to add either if you want them:

  • The personal field accepts any string, the same as the existing occ user:setting path did. I did not add validation for path separators or reserved names, since createAssistantDataFolder() already falls back safely when the name cannot be created, but say the word if you would rather it be restricted.
  • No admin documentation page, as I could not find an existing one in this repo to extend.

php-cs-fixer, psalm and eslint are clean, and the frontend builds.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/Service/AssistantService.php (1)

527-536: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate folder settings before using them as paths.

Folder::getFullPath() normalizes . to the current user folder, so data_folder='.' can make getAssistantDataFolder() return $userFolder. A value such as foo/bar can select a nested node or fail when its parent does not exist. .. is rejected only during newFolder(), after nodeExists() has swallowed the path error. Validate both settings as single folder names before these calls, including values written through occ, and add coverage for these inputs.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 40c439ec-206b-4b19-8142-87a263613de4

📥 Commits

Reviewing files that changed from the base of the PR and between 05605a8 and d61eb6a.

📒 Files selected for processing (5)
  • lib/Service/AssistantService.php
  • lib/Settings/Admin.php
  • lib/Settings/Personal.php
  • src/components/AdminSettings.vue
  • src/components/PersonalSettings.vue

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread lib/Service/AssistantService.php Outdated
Comment thread src/components/AdminSettings.vue
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline lib/Service/AssistantService.php --items all --match 'AssistantService|createAssistantDataFolder' --view expanded
printf '\n--- relevant references ---\n'
rg -n -C 12 '\bcreateAssistantDataFolder\s*\(|\$dataFolderName\b|\$defaultFolderName\b' lib/Service/AssistantService.php

Length of output: 4915


@bakiburakogun Verified. getAssistantDataFolder() now passes the resolved $dataFolderName to createAssistantDataFolder() on Line 536. It then saves the created folder name, including any collision suffix, on Line 538.

This fixes the reported user-setting overwrite case. Thanks.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@bakiburakogun

Copy link
Copy Markdown
Contributor Author

Both fixed in 055bb42.

The "0" case — right, and it was in three places rather than one: the service and both settings pages all used ?:, which PHP treats "0" as false for. A folder actually named 0 would have been replaced by the built-in name. Now checked with === '' at all three sites.

The delayed save — also right, and worth spelling out because it is not specific to this PR: delay() in src/utils.js keeps one module-level timer and clears it on every call, so editing a second field within the two seconds cancelled the first callback before it had ever written into optionsToSave. That edit was simply lost. The value is now queued when it changes rather than when the timer fires, and saveOptions gets a snapshot so a later edit cannot mutate the set mid-request.

That one is pre-existing behaviour in the admin settings rather than something this PR introduced — I fixed it here because the new field is subject to it and the fix is four lines. @julien-nc, if you would rather have it as a separate PR, say so and I will split it out.

Checks are clean: php-cs-fixer, psalm, eslint, and the frontend builds.

The folder the assistant creates in a user's files is named from a class
constant, so a server whose users do not work in English has no supported way to
hand new accounts a localized name. The per-user setting that already exists does
not fill the gap either: when the configured folder does not exist yet,
createAssistantDataFolder() falls back to the constant and the caller then writes
that name back over the preference, so setting it for a user who has not used the
assistant silently reverts.

Resolve the default from an app config value, keeping the constant as the last
resort, and pass the resolved name down to the creation path so it is used for
the folder that gets created. This mirrors how Talk resolves its attachment
folder.

Nothing changes for a server that sets no app config, and a user who already has
a folder keeps it.

Signed-off-by: Baki Burak Öğün <burak@burakogun.com>
The creation path was passed the server default rather than the name resolved
for the user, so a user whose configured folder had been deleted got a folder
named after the administrator's default, and their own setting was then
overwritten with it.

Reported by CodeRabbit on the pull request.

Signed-off-by: Baki Burak Öğün <burak@burakogun.com>
Add the server-wide default to the admin settings and the per-user override to
the personal settings, so neither has to be set with occ.

The personal field shows the administrator's default as its placeholder and an
empty value falls back to it, which keeps the existing precedence visible in the
interface.

Signed-off-by: Baki Burak Öğün <burak@burakogun.com>
Two issues CodeRabbit raised on the pull request.

PHP reads "0" as false, so `?: Application::ASSISTANT_DATA_FOLDER_NAME` silently
discarded a folder actually named "0" in the service and in both settings pages.
Check for an empty string explicitly instead.

`delay()` keeps a single module-level timer, so editing a second field within the
delay cancelled the first callback before it had queued its value, losing that
edit. Queue the value when it changes rather than when the timer fires, and hand
the pending set to saveOptions as a snapshot. This is pre-existing behaviour in
the admin settings; it is fixed here because the new field is subject to it.

Signed-off-by: Baki Burak Öğün <burak@burakogun.com>
@bakiburakogun
bakiburakogun force-pushed the feat/configurable-data-folder branch from 055bb42 to d336728 Compare August 26, 2026 18:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 452ac705-0939-4bfb-ad62-2722144bd9c9

📥 Commits

Reviewing files that changed from the base of the PR and between d61eb6a and d336728.

📒 Files selected for processing (5)
  • lib/Service/AssistantService.php
  • lib/Settings/Admin.php
  • lib/Settings/Personal.php
  • src/components/AdminSettings.vue
  • src/components/PersonalSettings.vue

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread lib/Service/AssistantService.php
A user who had used the assistant before an administrator set a default may
have a folder under the built-in name without a stored data_folder preference.
Applying the server default to them started a second folder and left their
existing output behind it.

Fall back to the built-in name when that folder already exists, so the server
default reaches accounts that have nothing to strand. This is also what the
admin settings text promises.

Signed-off-by: Baki Burak Öğün <burak@burakogun.com>
@bakiburakogun

Copy link
Copy Markdown
Contributor Author

Fixed in d3dcaa7 — and this one was worth catching, because the code disagreed with what the admin settings text promises.

A user who had used the assistant before an administrator set a default can have a folder under the built-in name with no stored data_folder value. The server default was being applied to them, which started a second folder and left their existing output behind it, while the settings text says changing the default does not touch folders that already exist.

Now an empty per-user value falls back to the built-in name when that folder already exists, and to the server default only when there is nothing to strand:

$dataFolderName = $this->config->getUserValue($userId, Application::APP_ID, 'data_folder', '');
if ($dataFolderName === '') {
	$dataFolderName = $userFolder->nodeExists(Application::ASSISTANT_DATA_FOLDER_NAME)
		? Application::ASSISTANT_DATA_FOLDER_NAME
		: $defaultFolderName;
}

I did not add a migration that renames existing folders. Moving a user's files because an administrator changed a setting seems like more than this PR should do on its own, and the existing behaviour of leaving them in place is at least predictable. Happy to add one if you would rather the rename be offered explicitly.

DCO was also failing on my earlier commits — sorry, that was mine, the sign-off was missing on three of them. Rebased with --signoff; the diff is unchanged. Both checks are green now.

php-cs-fixer, psalm and eslint are clean and the frontend builds.

@julien-nc julien-nc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot.

if you would rather have it as a separate PR, say so and I will split it out.

It's fine here, the change is kind of related.
Tested, works great.

@julien-nc
julien-nc merged commit a5d4304 into nextcloud:main Aug 28, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow the assistant data folder name to be set server-wide

2 participants