1717use OCP \AppFramework \Utility \ITimeFactory ;
1818use OCP \BackgroundJob \TimedJob ;
1919use OCP \DB \Exception ;
20+ use OCP \DB \IResult ;
2021use OCP \Files \AppData \IAppDataFactory ;
2122use OCP \Files \IAppData ;
2223use OCP \Files \IMimeTypeDetector ;
2324use OCP \Files \IMimeTypeLoader ;
2425use OCP \Files \IRootFolder ;
25- use OCP \Files \NotFoundException ;
26- use OCP \Files \SimpleFS \ISimpleFolder ;
2726use OCP \IAppConfig ;
27+ use OCP \IConfig ;
2828use OCP \IDBConnection ;
2929use Override ;
3030use Psr \Log \LoggerInterface ;
3131
3232class MovePreviewJob extends TimedJob {
3333 private IAppData $ appData ;
34+ private string $ previewRootPath ;
3435
3536 public function __construct (
3637 ITimeFactory $ time ,
3738 private readonly IAppConfig $ appConfig ,
39+ private readonly IConfig $ config ,
3840 private readonly PreviewMapper $ previewMapper ,
3941 private readonly StorageFactory $ storageFactory ,
4042 private readonly IDBConnection $ connection ,
@@ -49,6 +51,7 @@ public function __construct(
4951 $ this ->appData = $ appDataFactory ->get ('preview ' );
5052 $ this ->setTimeSensitivity (self ::TIME_INSENSITIVE );
5153 $ this ->setInterval (24 * 60 * 60 );
54+ $ this ->previewRootPath = 'appdata_ ' . $ this ->config ->getSystemValueString ('instanceid ' ) . '/preview/ ' ;
5255 }
5356
5457 #[Override]
@@ -57,49 +60,22 @@ protected function run(mixed $argument): void {
5760 return ;
5861 }
5962
60- $ emptyHierarchicalPreviewFolders = false ;
61-
6263 $ startTime = time ();
6364 while (true ) {
64- // Check new hierarchical preview folders first
65- if (!$ emptyHierarchicalPreviewFolders ) {
66- $ qb = $ this ->connection ->getQueryBuilder ();
67- $ qb ->select ('* ' )
68- ->from ('filecache ' )
69- ->where ($ qb ->expr ()->like ('path ' , $ qb ->createNamedParameter ('appdata_%/preview/%/%/%/%/%/%/%/%/% ' )))
70- ->hintShardKey ('storage ' , $ this ->rootFolder ->getMountPoint ()->getNumericStorageId ())
71- ->setMaxResults (100 );
72-
73- $ result = $ qb ->executeQuery ();
74- while ($ row = $ result ->fetch ()) {
75- $ pathSplit = explode ('/ ' , $ row ['path ' ]);
76- assert (count ($ pathSplit ) >= 2 );
77- $ fileId = $ pathSplit [count ($ pathSplit ) - 2 ];
78- $ this ->processPreviews ($ fileId , false );
79- }
80- }
81-
82- // And then the flat preview folder (legacy)
83- $ emptyHierarchicalPreviewFolders = true ;
8465 $ qb = $ this ->connection ->getQueryBuilder ();
85- $ qb ->select ('* ' )
66+ $ qb ->select ('path ' )
8667 ->from ('filecache ' )
87- ->where ($ qb ->expr ()->like ('path ' , $ qb ->createNamedParameter ('appdata_%/preview/%/%.% ' )))
68+ // Hierarchical preview folder structure
69+ ->where ($ qb ->expr ()->like ('path ' , $ qb ->createNamedParameter ($ this ->previewRootPath . '%/%/%/%/%/%/%/%/% ' )))
70+ // Legacy flat preview folder structure
71+ ->orWhere ($ qb ->expr ()->like ('path ' , $ qb ->createNamedParameter ($ this ->previewRootPath . '%/%.% ' )))
8872 ->hintShardKey ('storage ' , $ this ->rootFolder ->getMountPoint ()->getNumericStorageId ())
8973 ->setMaxResults (100 );
9074
9175 $ result = $ qb ->executeQuery ();
92- $ foundOldPreview = false ;
93- while ($ row = $ result ->fetch ()) {
94- $ pathSplit = explode ('/ ' , $ row ['path ' ]);
95- assert (count ($ pathSplit ) >= 2 );
96- $ fileId = $ pathSplit [count ($ pathSplit ) - 2 ];
97- array_pop ($ pathSplit );
98- $ this ->processPreviews ($ fileId , true );
99- $ foundOldPreview = true ;
100- }
76+ $ foundPreviews = $ this ->processQueryResult ($ result );
10177
102- if (!$ foundOldPreview ) {
78+ if (!$ foundPreviews ) {
10379 break ;
10480 }
10581
@@ -109,20 +85,46 @@ protected function run(mixed $argument): void {
10985 }
11086 }
11187
112- try {
113- // Delete any leftover preview directory
114- $ this ->appData ->getFolder ('. ' )->delete ();
115- } catch (NotFoundException ) {
116- // ignore
117- }
11888 $ this ->appConfig ->setValueBool ('core ' , 'previewMovedDone ' , true );
11989 }
12090
91+ private function processQueryResult (IResult $ result ): bool {
92+ $ foundPreview = false ;
93+ $ fileIds = [];
94+ $ flatFileIds = [];
95+ while ($ row = $ result ->fetch ()) {
96+ $ pathSplit = explode ('/ ' , $ row ['path ' ]);
97+ assert (count ($ pathSplit ) >= 2 );
98+ $ fileId = (int )$ pathSplit [count ($ pathSplit ) - 2 ];
99+ if (count ($ pathSplit ) === 11 ) {
100+ // Hierarchical structure
101+ if (!in_array ($ fileId , $ fileIds )) {
102+ $ fileIds [] = $ fileId ;
103+ }
104+ } else {
105+ // Flat structure
106+ if (!in_array ($ fileId , $ flatFileIds )) {
107+ $ flatFileIds [] = $ fileId ;
108+ }
109+ }
110+ $ foundPreview = true ;
111+ }
112+
113+ foreach ($ fileIds as $ fileId ) {
114+ $ this ->processPreviews ($ fileId , flatPath: false );
115+ }
116+
117+ foreach ($ flatFileIds as $ fileId ) {
118+ $ this ->processPreviews ($ fileId , flatPath: true );
119+ }
120+ return $ foundPreview ;
121+ }
122+
121123 /**
122124 * @param array<string|int, string[]> $previewFolders
123125 */
124- private function processPreviews (int | string $ fileId , bool $ simplePaths ): void {
125- $ internalPath = $ this ->getInternalFolder ((string )$ fileId , $ simplePaths );
126+ private function processPreviews (int $ fileId , bool $ flatPath ): void {
127+ $ internalPath = $ this ->getInternalFolder ((string )$ fileId , $ flatPath );
126128 $ folder = $ this ->appData ->getFolder ($ internalPath );
127129
128130 /**
@@ -133,7 +135,7 @@ private function processPreviews(int|string $fileId, bool $simplePaths): void {
133135 foreach ($ folder ->getDirectoryListing () as $ previewFile ) {
134136 $ path = $ fileId . '/ ' . $ previewFile ->getName ();
135137 /** @var SimpleFile $previewFile */
136- $ preview = Preview::fromPath ($ path , $ this ->mimeTypeDetector , $ this -> mimeTypeLoader );
138+ $ preview = Preview::fromPath ($ path , $ this ->mimeTypeDetector );
137139 if (!$ preview ) {
138140 $ this ->logger ->error ('Unable to import old preview at path. ' );
139141 continue ;
@@ -160,59 +162,82 @@ private function processPreviews(int|string $fileId, bool $simplePaths): void {
160162
161163 if (count ($ result ) > 0 ) {
162164 foreach ($ previewFiles as $ previewFile ) {
165+ /** @var Preview $preview */
163166 $ preview = $ previewFile ['preview ' ];
164167 /** @var SimpleFile $file */
165168 $ file = $ previewFile ['file ' ];
166169 $ preview ->setStorageId ($ result [0 ]['storage ' ]);
167170 $ preview ->setEtag ($ result [0 ]['etag ' ]);
168- $ preview ->setSourceMimetype ( $ result [0 ]['mimetype ' ]);
171+ $ preview ->setSourceMimeType ( $ this -> mimeTypeLoader -> getMimetypeById ( $ result [0 ]['mimetype ' ]) );
169172 try {
170173 $ preview = $ this ->previewMapper ->insert ($ preview );
171- } catch (Exception $ e ) {
174+ } catch (Exception ) {
172175 // We already have this preview in the preview table, skip
176+ $ qb ->delete ('filecache ' )
177+ ->where ($ qb ->expr ()->eq ('fileid ' , $ qb ->createNamedParameter ($ file ->getId ())))
178+ ->hintShardKey ('storage ' , $ this ->rootFolder ->getMountPoint ()->getNumericStorageId ())
179+ ->executeStatement ();
173180 continue ;
174181 }
175182
176183 try {
177184 $ this ->storageFactory ->migratePreview ($ preview , $ file );
185+ $ qb = $ this ->connection ->getQueryBuilder ();
178186 $ qb ->delete ('filecache ' )
179187 ->where ($ qb ->expr ()->eq ('fileid ' , $ qb ->createNamedParameter ($ file ->getId ())))
188+ ->hintShardKey ('storage ' , $ this ->rootFolder ->getMountPoint ()->getNumericStorageId ())
180189 ->executeStatement ();
181190 // Do not call $file->delete() as this will also delete the file from the file system
182191 } catch (\Exception $ e ) {
183192 $ this ->previewMapper ->delete ($ preview );
184193 throw $ e ;
185194 }
186195 }
196+ } else {
197+ // No matching fileId, delete preview
198+ try {
199+ $ this ->connection ->beginTransaction ();
200+ foreach ($ previewFiles as $ previewFile ) {
201+ /** @var SimpleFile $file */
202+ $ file = $ previewFile ['file ' ];
203+ $ file ->delete ();
204+ }
205+ $ this ->connection ->commit ();
206+ } catch (Exception ) {
207+ $ this ->connection ->rollback ();
208+ }
187209 }
188210
189- $ this ->deleteFolder ($ internalPath, $ folder );
211+ $ this ->deleteFolder ($ internalPath );
190212 }
191213
192- public static function getInternalFolder (string $ name , bool $ simplePaths ): string {
193- if ($ simplePaths ) {
194- return ' / ' . $ name ;
214+ public static function getInternalFolder (string $ name , bool $ flatPath ): string {
215+ if ($ flatPath ) {
216+ return $ name ;
195217 }
196218 return implode ('/ ' , str_split (substr (md5 ($ name ), 0 , 7 ))) . '/ ' . $ name ;
197219 }
198220
199- private function deleteFolder (string $ path , ISimpleFolder $ folder ): void {
200- $ folder ->delete ();
201-
221+ private function deleteFolder (string $ path ): void {
202222 $ current = $ path ;
203223
204224 while (true ) {
225+ $ appDataPath = $ this ->previewRootPath . $ current ;
226+ $ qb = $ this ->connection ->getQueryBuilder ();
227+ $ qb ->delete ('filecache ' )
228+ ->where ($ qb ->expr ()->eq ('path_hash ' , $ qb ->createNamedParameter (md5 ($ appDataPath ))))
229+ ->hintShardKey ('storage ' , $ this ->rootFolder ->getMountPoint ()->getNumericStorageId ())
230+ ->executeStatement ();
231+
205232 $ current = dirname ($ current );
206233 if ($ current === '/ ' || $ current === '. ' || $ current === '' ) {
207234 break ;
208235 }
209236
210-
211237 $ folder = $ this ->appData ->getFolder ($ current );
212238 if (count ($ folder ->getDirectoryListing ()) !== 0 ) {
213239 break ;
214240 }
215- $ folder ->delete ();
216241 }
217242 }
218243}
0 commit comments