Skip to content

Commit bb4c55c

Browse files
committed
refactor: Move copy skeleton step to a file listener
Instead of having all user providers call OC_Util::copySkeleton Signed-off-by: Carl Schwan <carlschwan@kde.org>
1 parent aa904b2 commit bb4c55c

10 files changed

Lines changed: 147 additions & 74 deletions

File tree

apps/files/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => $baseDir . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
8080
'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php',
8181
'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php',
82+
'OCA\\Files\\Listener\\UserFirstTimeLoggedInListener' => $baseDir . '/../lib/Listener/UserFirstTimeLoggedInListener.php',
8283
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
8384
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
8485
'OCA\\Files\\Migration\\Version2003Date20241021095629' => $baseDir . '/../lib/Migration/Version2003Date20241021095629.php',

apps/files/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class ComposerStaticInitFiles
9494
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => __DIR__ . '/..' . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
9595
'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php',
9696
'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php',
97+
'OCA\\Files\\Listener\\UserFirstTimeLoggedInListener' => __DIR__ . '/..' . '/../lib/Listener/UserFirstTimeLoggedInListener.php',
9798
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
9899
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
99100
'OCA\\Files\\Migration\\Version2003Date20241021095629' => __DIR__ . '/..' . '/../lib/Migration/Version2003Date20241021095629.php',

apps/files/lib/AppInfo/Application.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use OCA\Files\Listener\NodeRemovedFromFavoriteListener;
2626
use OCA\Files\Listener\RenderReferenceEventListener;
2727
use OCA\Files\Listener\SyncLivePhotosListener;
28+
use OCA\Files\Listener\UserFirstTimeLoggedInListener;
2829
use OCA\Files\Notification\Notifier;
2930
use OCA\Files\Search\FilesSearchProvider;
3031
use OCA\Files\Service\TagService;
@@ -53,6 +54,7 @@
5354
use OCP\ITagManager;
5455
use OCP\IUserSession;
5556
use OCP\Share\IManager as IShareManager;
57+
use OCP\User\Events\UserFirstTimeLoggedInEvent;
5658
use OCP\Util;
5759
use Psr\Container\ContainerInterface;
5860
use Psr\Log\LoggerInterface;
@@ -121,6 +123,8 @@ public function register(IRegistrationContext $context): void {
121123
$context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
122124
$context->registerEventListener(NodeAddedToFavorite::class, NodeAddedToFavoriteListener::class);
123125
$context->registerEventListener(NodeRemovedFromFavorite::class, NodeRemovedFromFavoriteListener::class);
126+
$context->registerEventListener(UserFirstTimeLoggedInEvent::class, UserFirstTimeLoggedInListener::class);
127+
124128
$context->registerSearchProvider(FilesSearchProvider::class);
125129

126130
$context->registerNotifierService(Notifier::class);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Files\Listener;
11+
12+
use OC\Files\Template\TemplateManager;
13+
use OCP\EventDispatcher\Event;
14+
use OCP\EventDispatcher\IEventListener;
15+
use OCP\Files\ISetupManager;
16+
use OCP\Files\NotPermittedException;
17+
use OCP\User\Events\UserFirstTimeLoggedInEvent;
18+
19+
/**
20+
* @template-implements IEventListener<UserFirstTimeLoggedInEvent>
21+
*/
22+
class UserFirstTimeLoggedInListener implements IEventListener {
23+
public function __construct(
24+
private readonly TemplateManager $templateManager,
25+
private readonly ISetupManager $setupManager,
26+
) {
27+
}
28+
29+
public function handle(Event $event): void {
30+
if (!$event instanceof UserFirstTimeLoggedInEvent) {
31+
return;
32+
}
33+
34+
$user = $event->getUser();
35+
$this->setupManager->setupForUser($user);
36+
37+
try {
38+
// copy skeleton
39+
$this->templateManager->copySkeleton($user->getUID());
40+
} catch (NotPermittedException) {
41+
// read only uses
42+
}
43+
}
44+
}

lib/base.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
class OC {
4747
/**
4848
* The installation path for Nextcloud on the server (e.g. /srv/http/nextcloud)
49+
* @internal Use auto-loaded $serverRoot with DI instead.
4950
*/
5051
public static string $SERVERROOT = '';
5152
/**

lib/private/Files/Template/TemplateManager.php

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ public function __construct(
6464
private readonly IFactory $l10nFactory,
6565
private readonly LoggerInterface $logger,
6666
private readonly IFilenameValidator $filenameValidator,
67+
private readonly string $serverRoot,
6768
) {
6869
$this->l10n = $l10nFactory->get('lib');
6970
$this->userId = $userSession->getUser()?->getUID();
71+
7072
}
7173

7274
#[Override]
@@ -320,8 +322,8 @@ public function initializeTemplateDirectory(?string $path = null, ?string $userI
320322
$this->userId = $userId;
321323
}
322324

323-
$defaultSkeletonDirectory = \OC::$SERVERROOT . '/core/skeleton';
324-
$defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates';
325+
$defaultSkeletonDirectory = $this->serverRoot . '/core/skeleton';
326+
$defaultTemplateDirectory = $this->serverRoot . '/core/skeleton/Templates';
325327
$skeletonPath = $this->config->getSystemValueString('skeletondirectory', $defaultSkeletonDirectory);
326328
$skeletonTemplatePath = $this->config->getSystemValueString('templatedirectory', $defaultTemplateDirectory);
327329
$isDefaultSkeleton = $skeletonPath === $defaultSkeletonDirectory;
@@ -371,7 +373,7 @@ public function initializeTemplateDirectory(?string $path = null, ?string $userI
371373
if (!$isDefaultTemplates && $folderIsEmpty) {
372374
$localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang);
373375
if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) {
374-
\OC_Util::copyr($localizedSkeletonTemplatePath, $folder);
376+
$this->copyr($localizedSkeletonTemplatePath, $folder);
375377
$userFolder->getStorage()->getScanner()->scan($folder->getInternalPath(), Scanner::SCAN_RECURSIVE);
376378
$this->setTemplatePath($userTemplatePath);
377379
return $userTemplatePath;
@@ -381,7 +383,7 @@ public function initializeTemplateDirectory(?string $path = null, ?string $userI
381383
if ($path !== null && $isDefaultSkeleton && $isDefaultTemplates && $folderIsEmpty) {
382384
$localizedSkeletonPath = $this->getLocalizedTemplatePath($skeletonPath . '/Templates', $userLang);
383385
if (!empty($localizedSkeletonPath) && file_exists($localizedSkeletonPath)) {
384-
\OC_Util::copyr($localizedSkeletonPath, $folder);
386+
$this->copyr($localizedSkeletonPath, $folder);
385387
$userFolder->getStorage()->getScanner()->scan($folder->getInternalPath(), Scanner::SCAN_RECURSIVE);
386388
$this->setTemplatePath($userTemplatePath);
387389
return $userTemplatePath;
@@ -412,4 +414,80 @@ private function getLocalizedTemplatePath(string $skeletonTemplatePath, string $
412414

413415
return $localizedSkeletonTemplatePath;
414416
}
417+
418+
/**
419+
* Copies a local directory recursively by using streams
420+
*/
421+
private function copyr(string $source, Folder $target): void {
422+
// Verify if folder exists
423+
$dir = opendir($source);
424+
if ($dir === false) {
425+
$this->logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
426+
return;
427+
}
428+
429+
// Copy the files
430+
while (false !== ($file = readdir($dir))) {
431+
if (!Filesystem::isIgnoredDir($file)) {
432+
if (is_dir($source . '/' . $file)) {
433+
$child = $target->newFolder($file);
434+
$this->copyr($source . '/' . $file, $child);
435+
} else {
436+
$sourceStream = fopen($source . '/' . $file, 'r');
437+
if ($sourceStream === false) {
438+
$this->logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
439+
closedir($dir);
440+
return;
441+
}
442+
$target->newFile($file, $sourceStream);
443+
}
444+
}
445+
}
446+
closedir($dir);
447+
}
448+
449+
public function copySkeleton(string $userId): void {
450+
$user = $this->userManager->get($userId);
451+
if ($user === null) {
452+
throw new \LogicException('Trying to initialize home dir for a non-existent user');
453+
}
454+
455+
$userDirectory = $this->rootFolder->getUserFolder($userId);
456+
457+
$plainSkeletonDirectory = $this->config->getSystemValueString('skeletondirectory', $this->serverRoot . '/core/skeleton');
458+
$userLang = $this->l10nFactory->findLanguage();
459+
$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
460+
461+
if (!file_exists($skeletonDirectory)) {
462+
$dialectStart = strpos($userLang, '_');
463+
if ($dialectStart !== false) {
464+
$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
465+
}
466+
if ($dialectStart === false || !file_exists($skeletonDirectory)) {
467+
$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
468+
}
469+
if (!file_exists($skeletonDirectory)) {
470+
$skeletonDirectory = '';
471+
}
472+
}
473+
474+
$instanceId = $this->config->getSystemValue('instanceid', '');
475+
476+
if ($instanceId === null) {
477+
throw new \RuntimeException('no instance id!');
478+
}
479+
$appdata = 'appdata_' . $instanceId;
480+
if ($userId === $appdata) {
481+
throw new \RuntimeException('username is reserved name: ' . $appdata);
482+
}
483+
484+
if (!empty($skeletonDirectory)) {
485+
$this->logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
486+
$this->copyr($skeletonDirectory, $userDirectory);
487+
// update the file cache
488+
$userDirectory->getStorage()->getScanner()->scan('', Scanner::SCAN_RECURSIVE);
489+
490+
$this->initializeTemplateDirectory(null, $userId);
491+
}
492+
}
415493
}

lib/private/User/Session.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@
2020
use OC\Http\CookieHelper;
2121
use OC\Security\CSRF\CsrfTokenManager;
2222
use OC_User;
23-
use OC_Util;
2423
use OCA\DAV\Connector\Sabre\Auth;
2524
use OCP\AppFramework\Db\TTransactional;
2625
use OCP\AppFramework\Utility\ITimeFactory;
2726
use OCP\Authentication\Exceptions\ExpiredTokenException;
2827
use OCP\Authentication\Exceptions\InvalidTokenException;
2928
use OCP\EventDispatcher\GenericEvent;
3029
use OCP\EventDispatcher\IEventDispatcher;
31-
use OCP\Files\NotPermittedException;
3230
use OCP\IConfig;
3331
use OCP\IDBConnection;
3432
use OCP\IRequest;
@@ -525,21 +523,6 @@ protected function prepareUserLogin($firstTimeLogin, $refreshCsrfToken = true) {
525523
}
526524

527525
if ($firstTimeLogin) {
528-
//we need to pass the user name, which may differ from login name
529-
$user = $this->getUser()->getUID();
530-
OC_Util::setupFS($user);
531-
532-
// TODO: lock necessary?
533-
//trigger creation of user home and /files folder
534-
$userFolder = \OC::$server->getUserFolder($user);
535-
536-
try {
537-
// copy skeleton
538-
\OC_Util::copySkeleton($user, $userFolder);
539-
} catch (NotPermittedException $ex) {
540-
// read only uses
541-
}
542-
543526
// trigger any other initialization
544527
Server::get(IEventDispatcher::class)->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
545528
Server::get(IEventDispatcher::class)->dispatchTyped(new UserFirstTimeLoggedInEvent($this->getUser()));

lib/private/legacy/OC_Util.php

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@
77
*/
88
use bantu\IniGetWrapper\IniGetWrapper;
99
use OC\Authentication\TwoFactorAuth\Manager as TwoFactorAuthManager;
10-
use OC\Files\Cache\Scanner;
1110
use OC\Files\Filesystem;
1211
use OC\Files\SetupManager;
12+
use OC\Files\Template\TemplateManager;
1313
use OC\Setup;
1414
use OC\SystemConfig;
1515
use OCP\App\IAppManager;
1616
use OCP\Files\FileInfo;
1717
use OCP\Files\Folder;
1818
use OCP\Files\NotFoundException;
1919
use OCP\Files\NotPermittedException;
20-
use OCP\Files\Template\ITemplateManager;
2120
use OCP\HintException;
2221
use OCP\IConfig;
2322
use OCP\IGroupManager;
@@ -27,7 +26,6 @@
2726
use OCP\IUser;
2827
use OCP\IUserManager;
2928
use OCP\IUserSession;
30-
use OCP\L10N\IFactory;
3129
use OCP\Security\ISecureRandom;
3230
use OCP\Server;
3331
use OCP\Share\IManager;
@@ -116,49 +114,10 @@ public static function isDefaultExpireDateEnforced() {
116114
* @param Folder $userDirectory
117115
* @throws NotFoundException
118116
* @throws NotPermittedException
119-
* @suppress PhanDeprecatedFunction
117+
* @deprecated 34.0.0 Not needed anymore, triggered automatically when UserFirstTimeLoggedInEvent is triggered
120118
*/
121119
public static function copySkeleton($userId, Folder $userDirectory) {
122-
/** @var LoggerInterface $logger */
123-
$logger = Server::get(LoggerInterface::class);
124-
125-
$plainSkeletonDirectory = Server::get(IConfig::class)->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
126-
$userLang = Server::get(IFactory::class)->findLanguage();
127-
$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
128-
129-
if (!file_exists($skeletonDirectory)) {
130-
$dialectStart = strpos($userLang, '_');
131-
if ($dialectStart !== false) {
132-
$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
133-
}
134-
if ($dialectStart === false || !file_exists($skeletonDirectory)) {
135-
$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
136-
}
137-
if (!file_exists($skeletonDirectory)) {
138-
$skeletonDirectory = '';
139-
}
140-
}
141-
142-
$instanceId = Server::get(IConfig::class)->getSystemValue('instanceid', '');
143-
144-
if ($instanceId === null) {
145-
throw new \RuntimeException('no instance id!');
146-
}
147-
$appdata = 'appdata_' . $instanceId;
148-
if ($userId === $appdata) {
149-
throw new \RuntimeException('username is reserved name: ' . $appdata);
150-
}
151-
152-
if (!empty($skeletonDirectory)) {
153-
$logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
154-
self::copyr($skeletonDirectory, $userDirectory);
155-
// update the file cache
156-
$userDirectory->getStorage()->getScanner()->scan('', Scanner::SCAN_RECURSIVE);
157-
158-
/** @var ITemplateManager $templateManager */
159-
$templateManager = Server::get(ITemplateManager::class);
160-
$templateManager->initializeTemplateDirectory(null, $userId);
161-
}
120+
Server::get(TemplateManager::class)->copySkeleton($userId);
162121
}
163122

164123
/**
@@ -167,6 +126,7 @@ public static function copySkeleton($userId, Folder $userDirectory) {
167126
* @param string $source
168127
* @param Folder $target
169128
* @return void
129+
* @deprecated 34.0.0 Unused, if you really need this functionality, open an issue on GitHub
170130
*/
171131
public static function copyr($source, Folder $target) {
172132
$logger = Server::get(LoggerInterface::class);

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
<file name="status.php"/>
6464
<file name="version.php"/>
6565
<file name="tests/lib/TestCase.php"/>
66+
<file name="tests/lib/Files/Template/*.php"/>
6667
<ignoreFiles>
6768
<directory name="apps/**/tests"/>
6869
<directory name="apps/**/composer"/>

0 commit comments

Comments
 (0)