66 */
77namespace OCA \Files_Trashbin \BackgroundJob ;
88
9+ use OC \Files \SetupManager ;
910use OC \Files \View ;
11+ use OCA \Files_Trashbin \AppInfo \Application ;
1012use OCA \Files_Trashbin \Expiration ;
1113use OCA \Files_Trashbin \Helper ;
1214use OCA \Files_Trashbin \Trashbin ;
1315use OCP \AppFramework \Utility \ITimeFactory ;
1416use OCP \BackgroundJob \TimedJob ;
1517use OCP \IAppConfig ;
18+ use OCP \IUser ;
1619use OCP \IUserManager ;
20+ use OCP \Lock \ILockingProvider ;
1721use Psr \Log \LoggerInterface ;
1822
1923class ExpireTrash extends TimedJob {
24+ public const TOGGLE_CONFIG_KEY_NAME = 'background_job_expire_trash ' ;
25+ public const OFFSET_CONFIG_KEY_NAME = 'background_job_expire_trash_offset ' ;
26+ private const THIRTY_MINUTES = 30 * 60 ;
27+ private const USER_BATCH_SIZE = 10 ;
28+
2029 public function __construct (
2130 private IAppConfig $ appConfig ,
2231 private IUserManager $ userManager ,
2332 private Expiration $ expiration ,
2433 private LoggerInterface $ logger ,
34+ private SetupManager $ setupManager ,
35+ private ILockingProvider $ lockingProvider ,
2536 ITimeFactory $ time ,
2637 ) {
2738 parent ::__construct ($ time );
28- // Run once per 30 minutes
29- $ this ->setInterval (60 * 30 );
39+ $ this ->setInterval (self ::THIRTY_MINUTES );
3040 }
3141
3242 protected function run ($ argument ) {
33- $ backgroundJob = $ this ->appConfig ->getValueString ( ' files_trashbin ' , ' background_job_expire_trash ' , ' yes ' );
34- if ($ backgroundJob === ' no ' ) {
43+ $ backgroundJob = $ this ->appConfig ->getValueBool (Application:: APP_ID , self :: TOGGLE_CONFIG_KEY_NAME , true );
44+ if (! $ backgroundJob ) {
3545 return ;
3646 }
3747
@@ -40,48 +50,89 @@ protected function run($argument) {
4050 return ;
4151 }
4252
43- $ stopTime = time () + 60 * 30 ; // Stops after 30 minutes.
44- $ offset = $ this ->appConfig ->getValueInt ('files_trashbin ' , 'background_job_expire_trash_offset ' , 0 );
45- $ users = $ this ->userManager ->getSeenUsers ($ offset );
53+ $ startTime = time ();
4654
47- foreach ($ users as $ user ) {
48- try {
55+ // Process users in batches of 10, but don't run for more than 30 minutes
56+ while (time () < $ startTime + self ::THIRTY_MINUTES ) {
57+ $ offset = $ this ->getNextOffset ();
58+ $ users = $ this ->userManager ->getSeenUsers ($ offset , self ::USER_BATCH_SIZE );
59+ $ count = 0 ;
60+
61+ foreach ($ users as $ user ) {
4962 $ uid = $ user ->getUID ();
50- if (!$ this ->setupFS ($ uid )) {
51- continue ;
63+ $ count ++;
64+
65+ try {
66+ if ($ this ->setupFS ($ user )) {
67+ $ dirContent = Helper::getTrashFiles ('/ ' , $ uid , 'mtime ' );
68+ Trashbin::deleteExpiredFiles ($ dirContent , $ uid );
69+ }
70+ } catch (\Throwable $ e ) {
71+ $ this ->logger ->error ('Error while expiring trashbin for user ' . $ uid , ['exception ' => $ e ]);
72+ } finally {
73+ $ this ->setupManager ->tearDown ();
5274 }
53- $ dirContent = Helper::getTrashFiles ('/ ' , $ uid , 'mtime ' );
54- Trashbin::deleteExpiredFiles ($ dirContent , $ uid );
55- } catch (\Throwable $ e ) {
56- $ this ->logger ->error ('Error while expiring trashbin for user ' . $ user ->getUID (), ['exception ' => $ e ]);
5775 }
5876
59- $ offset ++;
60-
61- if ($ stopTime < time ()) {
62- $ this ->appConfig ->setValueInt ('files_trashbin ' , 'background_job_expire_trash_offset ' , $ offset );
63- \OC_Util::tearDownFS ();
64- return ;
77+ // If the last batch was not full it means that we reached the end of the user list.
78+ if ($ count < self ::USER_BATCH_SIZE ) {
79+ $ this ->resetOffset ();
6580 }
6681 }
67-
68- $ this ->appConfig ->setValueInt ('files_trashbin ' , 'background_job_expire_trash_offset ' , 0 );
69- \OC_Util::tearDownFS ();
7082 }
7183
7284 /**
7385 * Act on behalf on trash item owner
7486 */
75- protected function setupFS (string $ user ): bool {
76- \OC_Util::tearDownFS ();
77- \OC_Util::setupFS ($ user );
87+ protected function setupFS (IUser $ user ): bool {
88+ $ this ->setupManager ->setupForUser ($ user );
7889
7990 // Check if this user has a trashbin directory
80- $ view = new View ('/ ' . $ user );
91+ $ view = new View ('/ ' . $ user-> getUID () );
8192 if (!$ view ->is_dir ('/files_trashbin/files ' )) {
8293 return false ;
8394 }
8495
8596 return true ;
8697 }
98+
99+ private function getNextOffset (): int {
100+ return $ this ->runMutexOperation (function () {
101+ $ this ->appConfig ->clearCache ();
102+
103+ $ offset = $ this ->appConfig ->getValueInt (Application::APP_ID , self ::OFFSET_CONFIG_KEY_NAME , 0 );
104+ $ this ->appConfig ->setValueInt (Application::APP_ID , self ::OFFSET_CONFIG_KEY_NAME , $ offset + self ::USER_BATCH_SIZE );
105+
106+ return $ offset ;
107+ });
108+
109+ }
110+
111+ private function resetOffset () {
112+ $ this ->runMutexOperation (function () {
113+ $ this ->appConfig ->setValueInt (Application::APP_ID , self ::OFFSET_CONFIG_KEY_NAME , 0 );
114+ });
115+ }
116+
117+ private function runMutexOperation ($ operation ): mixed {
118+ $ acquired = false ;
119+
120+ while ($ acquired === false ) {
121+ try {
122+ $ this ->lockingProvider ->acquireLock (self ::OFFSET_CONFIG_KEY_NAME , ILockingProvider::LOCK_EXCLUSIVE , 'Expire trashbin background job offset ' );
123+ $ acquired = true ;
124+ } catch (\OCP \Lock \LockedException $ e ) {
125+ // wait a bit and try again
126+ usleep (100000 );
127+ }
128+ }
129+
130+ try {
131+ $ result = $ operation ();
132+ } finally {
133+ $ this ->lockingProvider ->releaseLock (self ::OFFSET_CONFIG_KEY_NAME , ILockingProvider::LOCK_EXCLUSIVE );
134+ }
135+
136+ return $ result ;
137+ }
87138}
0 commit comments