1313use OC \Files \Search \SearchQuery ;
1414use OC \Files \Storage \Wrapper \Jail ;
1515use OC \Files \View ;
16+ use OCA \Files \AppInfo \Application ;
17+ use OCA \Files \ConfigLexicon ;
1618use OCA \DAV \Connector \Sabre \CachingTree ;
1719use OCA \DAV \Connector \Sabre \Directory ;
1820use OCA \DAV \Connector \Sabre \File ;
1921use OCA \DAV \Connector \Sabre \FilesPlugin ;
22+ use OCA \DAV \Connector \Sabre \GroupableFile ;
2023use OCA \DAV \Connector \Sabre \Server ;
2124use OCA \DAV \Connector \Sabre \TagsPlugin ;
2225use OCP \Files \Cache \ICacheEntry ;
3134use OCP \FilesMetadata \IFilesMetadataManager ;
3235use OCP \FilesMetadata \IMetadataQuery ;
3336use OCP \FilesMetadata \Model \IMetadataValueWrapper ;
37+ use OCP \IAppConfig ;
3438use OCP \IUser ;
3539use OCP \Share \IManager ;
3640use Sabre \DAV \Exception \NotFound ;
@@ -54,6 +58,7 @@ public function __construct(
5458 private IManager $ shareManager ,
5559 private View $ view ,
5660 private IFilesMetadataManager $ filesMetadataManager ,
61+ private IAppConfig $ appConfig ,
5762 ) {
5863 }
5964
@@ -216,10 +221,14 @@ public function search(Query $search): array {
216221 $ results = $ userFolder ->search ($ query );
217222 }
218223
224+ $ groupRecentFilesEnabled = $ this ->appConfig ->getValueBool (Application::APP_ID , ConfigLexicon::GROUP_RECENT_FILES , false );
225+
219226 /** @var SearchResult[] $nodes */
220- $ nodes = array_map (function (Node $ node ) {
227+ $ nodes = array_map (function (Node $ node ) use ( $ groupRecentFilesEnabled ) {
221228 if ($ node instanceof Folder) {
222229 $ davNode = new Directory ($ this ->view , $ node , $ this ->tree , $ this ->shareManager );
230+ } elseif ($ groupRecentFilesEnabled ) {
231+ $ davNode = new GroupableFile ($ this ->view , $ node , $ this ->shareManager );
223232 } else {
224233 $ davNode = new File ($ this ->view , $ node , $ this ->shareManager );
225234 }
@@ -228,6 +237,10 @@ public function search(Query $search): array {
228237 return new SearchResult ($ davNode , $ path );
229238 }, $ results );
230239
240+ if ($ groupRecentFilesEnabled ) {
241+ $ nodes = $ this ->setGroupOnNodes ($ nodes );
242+ }
243+
231244 if (!$ query ->limitToHome ()) {
232245 // Sort again, since the result from multiple storages is appended and not sorted
233246 usort ($ nodes , function (SearchResult $ a , SearchResult $ b ) use ($ search ) {
@@ -572,4 +585,103 @@ private function extractWhereValue(Operator &$operator, string $propertyName, st
572585 return null ;
573586 }
574587 }
588+
589+ /**
590+ * @param SearchResult[] $searchResults
591+ * @return SearchResult[] $searchResults
592+ */
593+ private function setGroupOnNodes (array $ searchResults ): array {
594+ $ mimeTypes = $ this ->appConfig ->getValueArray (Application::APP_ID , ConfigLexicon::RECENT_FILES_GROUP_MIME_TYPES , []);
595+ if (count ($ mimeTypes ) === 0 ) {
596+ return $ searchResults ;
597+ }
598+ $ timespanMinutes = $ this ->appConfig ->getValueInt (Application::APP_ID , ConfigLexicon::RECENT_FILES_GROUP_TIMESPAN_MINUTES , 2 );
599+ $ timespan = $ timespanMinutes * 60 ;
600+
601+ // sort by oldest action to the most recent
602+ usort ($ searchResults , fn ($ a , $ b ) => $ this ->getNodeTime ($ a ) <=> $ this ->getNodeTime ($ b ));
603+
604+ $ count = count ($ searchResults );
605+ $ result = [];
606+ $ groupNumber = 1 ;
607+ $ i = 0 ;
608+
609+ while ($ i < $ count ) {
610+ $ current = $ searchResults [$ i ];
611+
612+ if (!$ this ->isNodeGroupable ($ current , $ mimeTypes )) {
613+ $ result [] = $ current ;
614+ $ i ++;
615+ continue ;
616+ }
617+
618+ $ groupStartTime = $ this ->getNodeTime ($ current );
619+ $ isContaminated = false ;
620+
621+ // look ahead to check if the time window is contaminated by a non-groupable node
622+ for ($ j = $ i + 1 ; $ j < $ count ; $ j ++) {
623+ $ nextTime = $ this ->getNodeTime ($ searchResults [$ j ]);
624+ if (abs ($ nextTime - $ groupStartTime ) > $ timespan ) {
625+ break ;
626+ }
627+ if (!$ this ->isNodeGroupable ($ searchResults [$ j ], $ mimeTypes )) {
628+ $ isContaminated = true ;
629+ break ;
630+ }
631+ }
632+
633+ if ($ isContaminated ) {
634+ $ result [] = $ current ;
635+ $ i ++;
636+ continue ;
637+ }
638+
639+ $ groupIndexes = [$ i ];
640+ $ i ++;
641+
642+ // add nodes to group until time window limit is reached
643+ while ($ i < $ count ) {
644+ $ next = $ searchResults [$ i ];
645+ $ nextTime = $ this ->getNodeTime ($ next );
646+
647+ if (abs ($ nextTime - $ groupStartTime ) > $ timespan ) {
648+ break ;
649+ }
650+
651+ $ groupIndexes [] = $ i ;
652+ $ i ++;
653+ }
654+
655+ if (count ($ groupIndexes ) === 1 ) {
656+ $ result [] = $ searchResults [$ groupIndexes [0 ]];
657+ continue ;
658+ }
659+
660+ foreach ($ groupIndexes as $ idx ) {
661+ /** @var GroupableFile $node */
662+ $ node = $ searchResults [$ idx ]->node ;
663+ $ node ->setGroup ($ groupNumber );
664+ $ result [] = $ searchResults [$ idx ];
665+ }
666+ $ groupNumber ++;
667+ }
668+
669+ return $ result ;
670+ }
671+
672+ private function getNodeTime (SearchResult $ result ): int {
673+ $ node = $ result ->node ;
674+ if (!$ node instanceof GroupableFile) {
675+ return 0 ;
676+ }
677+ $ uploadTime = $ node ->getNode ()->getUploadTime ();
678+ $ creationTime = $ node ->getNode ()->getCreationTime ();
679+ $ lastModified = $ node ->getLastModified ();
680+ return max ($ uploadTime , $ creationTime , $ lastModified );
681+ }
682+
683+ private function isNodeGroupable (SearchResult $ result , array $ mimeTypes ): bool {
684+ $ node = $ result ->node ;
685+ return $ node instanceof GroupableFile && in_array ($ node ->getNode ()->getMimetype (), $ mimeTypes , true );
686+ }
575687}
0 commit comments