77 */
88namespace OCA \Files_Trashbin \BackgroundJob ;
99
10+ use OC \Files \SetupManager ;
11+ use OC \Files \View ;
12+ use OCA \Files_Trashbin \AppInfo \Application ;
1013use OCA \Files_Trashbin \Expiration ;
1114use OCA \Files_Trashbin \Helper ;
1215use OCA \Files_Trashbin \Trashbin ;
1316use OCP \AppFramework \Utility \ITimeFactory ;
1417use OCP \BackgroundJob \TimedJob ;
1518use OCP \IAppConfig ;
19+ use OCP \IUser ;
1620use OCP \IUserManager ;
21+ use OCP \Lock \ILockingProvider ;
1722use Psr \Log \LoggerInterface ;
1823
1924class ExpireTrash extends TimedJob {
25+ public const TOGGLE_CONFIG_KEY_NAME = 'background_job_expire_trash ' ;
26+ public const OFFSET_CONFIG_KEY_NAME = 'background_job_expire_trash_offset ' ;
27+ private const THIRTY_MINUTES = 30 * 60 ;
28+ private const USER_BATCH_SIZE = 10 ;
2029
2130 public function __construct (
2231 private IAppConfig $ appConfig ,
2332 private IUserManager $ userManager ,
2433 private Expiration $ expiration ,
2534 private LoggerInterface $ logger ,
26- ITimeFactory $ time
35+ private SetupManager $ setupManager ,
36+ private ILockingProvider $ lockingProvider ,
37+ ITimeFactory $ time ,
2738 ) {
2839 parent ::__construct ($ time );
29- // Run once per 30 minutes
30- $ this ->setInterval (60 * 30 );
40+ $ this ->setInterval (self ::THIRTY_MINUTES );
3141 }
3242
3343 protected function run ($ argument ) {
34- $ backgroundJob = $ this ->appConfig ->getValueString ( ' files_trashbin ' , ' background_job_expire_trash ' , ' yes ' );
35- if ($ backgroundJob === ' no ' ) {
44+ $ backgroundJob = $ this ->appConfig ->getValueBool (Application:: APP_ID , self :: TOGGLE_CONFIG_KEY_NAME , true );
45+ if (! $ backgroundJob ) {
3646 return ;
3747 }
3848
@@ -41,48 +51,89 @@ protected function run($argument) {
4151 return ;
4252 }
4353
44- $ stopTime = time () + 60 * 30 ; // Stops after 30 minutes.
45- $ offset = $ this ->appConfig ->getValueInt ('files_trashbin ' , 'background_job_expire_trash_offset ' , 0 );
46- $ users = $ this ->userManager ->getSeenUsers ($ offset );
54+ $ startTime = time ();
4755
48- foreach ($ users as $ user ) {
49- try {
56+ // Process users in batches of 10, but don't run for more than 30 minutes
57+ while (time () < $ startTime + self ::THIRTY_MINUTES ) {
58+ $ offset = $ this ->getNextOffset ();
59+ $ users = $ this ->userManager ->getSeenUsers ($ offset , self ::USER_BATCH_SIZE );
60+ $ count = 0 ;
61+
62+ foreach ($ users as $ user ) {
5063 $ uid = $ user ->getUID ();
51- if (!$ this ->setupFS ($ uid )) {
52- continue ;
64+ $ count ++;
65+
66+ try {
67+ if ($ this ->setupFS ($ user )) {
68+ $ dirContent = Helper::getTrashFiles ('/ ' , $ uid , 'mtime ' );
69+ Trashbin::deleteExpiredFiles ($ dirContent , $ uid );
70+ }
71+ } catch (\Throwable $ e ) {
72+ $ this ->logger ->error ('Error while expiring trashbin for user ' . $ uid , ['exception ' => $ e ]);
73+ } finally {
74+ $ this ->setupManager ->tearDown ();
5375 }
54- $ dirContent = Helper::getTrashFiles ('/ ' , $ uid , 'mtime ' );
55- Trashbin::deleteExpiredFiles ($ dirContent , $ uid );
56- } catch (\Throwable $ e ) {
57- $ this ->logger ->error ('Error while expiring trashbin for user ' . $ user ->getUID (), ['exception ' => $ e ]);
5876 }
5977
60- $ offset ++;
61-
62- if ($ stopTime < time ()) {
63- $ this ->appConfig ->setValueInt ('files_trashbin ' , 'background_job_expire_trash_offset ' , $ offset );
64- \OC_Util::tearDownFS ();
65- return ;
78+ // If the last batch was not full it means that we reached the end of the user list.
79+ if ($ count < self ::USER_BATCH_SIZE ) {
80+ $ this ->resetOffset ();
6681 }
6782 }
68-
69- $ this ->appConfig ->setValueInt ('files_trashbin ' , 'background_job_expire_trash_offset ' , 0 );
70- \OC_Util::tearDownFS ();
7183 }
7284
7385 /**
7486 * Act on behalf on trash item owner
7587 */
76- protected function setupFS (string $ user ): bool {
77- \OC_Util::tearDownFS ();
78- \OC_Util::setupFS ($ user );
88+ protected function setupFS (IUser $ user ): bool {
89+ $ this ->setupManager ->setupForUser ($ user );
7990
8091 // Check if this user has a trashbin directory
81- $ view = new \ OC \ Files \ View ('/ ' . $ user );
92+ $ view = new View ('/ ' . $ user-> getUID () );
8293 if (!$ view ->is_dir ('/files_trashbin/files ' )) {
8394 return false ;
8495 }
8596
8697 return true ;
8798 }
99+
100+ private function getNextOffset (): int {
101+ return $ this ->runMutexOperation (function () {
102+ $ this ->appConfig ->clearCache ();
103+
104+ $ offset = $ this ->appConfig ->getValueInt (Application::APP_ID , self ::OFFSET_CONFIG_KEY_NAME , 0 );
105+ $ this ->appConfig ->setValueInt (Application::APP_ID , self ::OFFSET_CONFIG_KEY_NAME , $ offset + self ::USER_BATCH_SIZE );
106+
107+ return $ offset ;
108+ });
109+
110+ }
111+
112+ private function resetOffset () {
113+ $ this ->runMutexOperation (function () {
114+ $ this ->appConfig ->setValueInt (Application::APP_ID , self ::OFFSET_CONFIG_KEY_NAME , 0 );
115+ });
116+ }
117+
118+ private function runMutexOperation ($ operation ): mixed {
119+ $ acquired = false ;
120+
121+ while ($ acquired === false ) {
122+ try {
123+ $ this ->lockingProvider ->acquireLock (self ::OFFSET_CONFIG_KEY_NAME , ILockingProvider::LOCK_EXCLUSIVE , 'Expire trashbin background job offset ' );
124+ $ acquired = true ;
125+ } catch (\OCP \Lock \LockedException $ e ) {
126+ // wait a bit and try again
127+ usleep (100000 );
128+ }
129+ }
130+
131+ try {
132+ $ result = $ operation ();
133+ } finally {
134+ $ this ->lockingProvider ->releaseLock (self ::OFFSET_CONFIG_KEY_NAME , ILockingProvider::LOCK_EXCLUSIVE );
135+ }
136+
137+ return $ result ;
138+ }
88139}
0 commit comments