Skip to content

Commit 9f96a47

Browse files
committed
Add OCC command to reset rendered texts
The command is meant to be used when the fonts used to render texts ("core/fonts/NotoSans-Regular.ttf" and "core/fonts/NotoSans-Bold.ttf") are changed (for example, to add support for other scripts). The avatar and text file previews will be removed, so they will be generated again with the updated font when needed. Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
1 parent ee06780 commit 9f96a47

File tree

5 files changed

+210
-0
lines changed

5 files changed

+210
-0
lines changed

build/psalm-baseline.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,6 +2641,11 @@
26412641
<code>section</code>
26422642
</UndefinedInterfaceMethod>
26432643
</file>
2644+
<file src="core/Command/Preview/ResetRenderedTexts.php">
2645+
<InvalidReturnStatement occurrences="1">
2646+
<code>[]</code>
2647+
</InvalidReturnStatement>
2648+
</file>
26442649
<file src="core/Command/Upgrade.php">
26452650
<InvalidScalarArgument occurrences="11">
26462651
<code>0</code>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2021, Daniel Calviño Sánchez <danxuliu@gmail.com>
7+
*
8+
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OC\Core\Command\Preview;
28+
29+
use OC\Preview\Storage\Root;
30+
use OCP\DB\QueryBuilder\IQueryBuilder;
31+
use OCP\Files\IMimeTypeLoader;
32+
use OCP\Files\NotFoundException;
33+
use OCP\Files\NotPermittedException;
34+
use OCP\IAvatarManager;
35+
use OCP\IDBConnection;
36+
use OCP\IUserManager;
37+
use Symfony\Component\Console\Command\Command;
38+
use Symfony\Component\Console\Input\InputInterface;
39+
use Symfony\Component\Console\Input\InputOption;
40+
use Symfony\Component\Console\Output\OutputInterface;
41+
42+
class ResetRenderedTexts extends Command {
43+
44+
/** @var IDBConnection */
45+
protected $connection;
46+
47+
/** @var IUserManager */
48+
protected $userManager;
49+
50+
/** @var IAvatarManager */
51+
protected $avatarManager;
52+
53+
/** @var Root */
54+
private $previewFolder;
55+
56+
/** @var IMimeTypeLoader */
57+
private $mimeTypeLoader;
58+
59+
public function __construct(IDBConnection $connection,
60+
IUserManager $userManager,
61+
IAvatarManager $avatarManager,
62+
Root $previewFolder,
63+
IMimeTypeLoader $mimeTypeLoader) {
64+
parent::__construct();
65+
66+
$this->connection = $connection;
67+
$this->userManager = $userManager;
68+
$this->avatarManager = $avatarManager;
69+
$this->previewFolder = $previewFolder;
70+
$this->mimeTypeLoader = $mimeTypeLoader;
71+
}
72+
73+
protected function configure() {
74+
$this
75+
->setName('preview:reset-rendered-texts')
76+
->setDescription('Deletes all generated avatars and previews of text and md files')
77+
->addOption('dry', 'd', InputOption::VALUE_NONE, 'Dry mode - will not delete any files - in combination with the verbose mode one could check the operations.');
78+
}
79+
80+
protected function execute(InputInterface $input, OutputInterface $output): int {
81+
$dryMode = $input->getOption('dry');
82+
83+
if ($dryMode) {
84+
$output->writeln('INFO: The command is run in dry mode and will not modify anything.');
85+
$output->writeln('');
86+
}
87+
88+
$this->deleteAvatars($output, $dryMode);
89+
$this->deletePreviews($output, $dryMode);
90+
91+
return 0;
92+
}
93+
94+
private function deleteAvatars(OutputInterface $output, bool $dryMode): void {
95+
$avatarsToDeleteCount = 0;
96+
97+
foreach ($this->getAvatarsToDelete() as [$userId, $avatar]) {
98+
$output->writeln('Deleting avatar for ' . $userId, OutputInterface::VERBOSITY_VERBOSE);
99+
100+
$avatarsToDeleteCount++;
101+
102+
if ($dryMode) {
103+
continue;
104+
}
105+
106+
try {
107+
$avatar->remove();
108+
} catch (NotFoundException $e) {
109+
// continue
110+
} catch (NotPermittedException $e) {
111+
// continue
112+
}
113+
}
114+
115+
$output->writeln('Deleted ' . $avatarsToDeleteCount . ' avatars');
116+
$output->writeln('');
117+
}
118+
119+
private function getAvatarsToDelete(): \Iterator {
120+
foreach ($this->userManager->search('') as $user) {
121+
$avatar = $this->avatarManager->getAvatar($user->getUID());
122+
123+
if (!$avatar->isCustomAvatar()) {
124+
yield [$user->getUID(), $avatar];
125+
}
126+
}
127+
}
128+
129+
private function deletePreviews(OutputInterface $output, bool $dryMode): void {
130+
$previewsToDeleteCount = 0;
131+
132+
foreach ($this->getPreviewsToDelete() as ['name' => $previewFileId, 'path' => $filePath]) {
133+
$output->writeln('Deleting previews for ' . $filePath, OutputInterface::VERBOSITY_VERBOSE);
134+
135+
$previewsToDeleteCount++;
136+
137+
if ($dryMode) {
138+
continue;
139+
}
140+
141+
try {
142+
$preview = $this->previewFolder->getFolder((string)$previewFileId);
143+
$preview->delete();
144+
} catch (NotFoundException $e) {
145+
// continue
146+
} catch (NotPermittedException $e) {
147+
// continue
148+
}
149+
}
150+
151+
$output->writeln('Deleted ' . $previewsToDeleteCount . ' previews');
152+
}
153+
154+
// Copy pasted and adjusted from
155+
// "lib/private/Preview/BackgroundCleanupJob.php".
156+
private function getPreviewsToDelete(): \Iterator {
157+
$qb = $this->connection->getQueryBuilder();
158+
$qb->select('path', 'mimetype')
159+
->from('filecache')
160+
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($this->previewFolder->getId())));
161+
$cursor = $qb->execute();
162+
$data = $cursor->fetch();
163+
$cursor->closeCursor();
164+
165+
if ($data === null) {
166+
return [];
167+
}
168+
169+
/*
170+
* This lovely like is the result of the way the new previews are stored
171+
* We take the md5 of the name (fileid) and split the first 7 chars. That way
172+
* there are not a gazillion files in the root of the preview appdata.
173+
*/
174+
$like = $this->connection->escapeLikeParameter($data['path']) . '/_/_/_/_/_/_/_/%';
175+
176+
$qb = $this->connection->getQueryBuilder();
177+
$qb->select('a.name', 'b.path')
178+
->from('filecache', 'a')
179+
->leftJoin('a', 'filecache', 'b', $qb->expr()->eq(
180+
$qb->expr()->castColumn('a.name', IQueryBuilder::PARAM_INT), 'b.fileid'
181+
))
182+
->where(
183+
$qb->expr()->andX(
184+
$qb->expr()->like('a.path', $qb->createNamedParameter($like)),
185+
$qb->expr()->eq('a.mimetype', $qb->createNamedParameter($this->mimeTypeLoader->getId('httpd/unix-directory'))),
186+
$qb->expr()->orX(
187+
$qb->expr()->eq('b.mimetype', $qb->createNamedParameter($this->mimeTypeLoader->getId('text/plain'))),
188+
$qb->expr()->eq('b.mimetype', $qb->createNamedParameter($this->mimeTypeLoader->getId('text/markdown'))),
189+
$qb->expr()->eq('b.mimetype', $qb->createNamedParameter($this->mimeTypeLoader->getId('text/x-markdown')))
190+
)
191+
)
192+
);
193+
194+
$cursor = $qb->execute();
195+
196+
while ($row = $cursor->fetch()) {
197+
yield $row;
198+
}
199+
200+
$cursor->closeCursor();
201+
}
202+
}

core/register_command.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
));
169169

170170
$application->add(\OC::$server->query(\OC\Core\Command\Preview\Repair::class));
171+
$application->add(\OC::$server->query(\OC\Core\Command\Preview\ResetRenderedTexts::class));
171172

172173
$application->add(new OC\Core\Command\User\Add(\OC::$server->getUserManager(), \OC::$server->getGroupManager()));
173174
$application->add(new OC\Core\Command\User\Delete(\OC::$server->getUserManager()));

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@
854854
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php',
855855
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php',
856856
'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php',
857+
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => $baseDir . '/core/Command/Preview/ResetRenderedTexts.php',
857858
'OC\\Core\\Command\\Security\\ImportCertificate' => $baseDir . '/core/Command/Security/ImportCertificate.php',
858859
'OC\\Core\\Command\\Security\\ListCertificates' => $baseDir . '/core/Command/Security/ListCertificates.php',
859860
'OC\\Core\\Command\\Security\\RemoveCertificate' => $baseDir . '/core/Command/Security/RemoveCertificate.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
883883
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php',
884884
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php',
885885
'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php',
886+
'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => __DIR__ . '/../../..' . '/core/Command/Preview/ResetRenderedTexts.php',
886887
'OC\\Core\\Command\\Security\\ImportCertificate' => __DIR__ . '/../../..' . '/core/Command/Security/ImportCertificate.php',
887888
'OC\\Core\\Command\\Security\\ListCertificates' => __DIR__ . '/../../..' . '/core/Command/Security/ListCertificates.php',
888889
'OC\\Core\\Command\\Security\\RemoveCertificate' => __DIR__ . '/../../..' . '/core/Command/Security/RemoveCertificate.php',

0 commit comments

Comments
 (0)