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- 		$ stopTimetime () + 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+ 		$ startTimetime ();
4654
47- 		foreach  ($ usersas  $ user
48- 			try  {
55+ 		// Process users in batches of 10, but don't run for more than 30 minutes 
56+ 		while  (time () < $ startTimeself ::THIRTY_MINUTES ) {
57+ 			$ offset$ this getNextOffset ();
58+ 			$ users$ this userManager ->getSeenUsers ($ offsetself ::USER_BATCH_SIZE );
59+ 			$ count0 ;
60+ 
61+ 			foreach  ($ usersas  $ user
4962				$ uid$ usergetUID ();
50- 				if  (!$ this setupFS ($ uid
51- 					continue ;
63+ 				$ count
64+ 
65+ 				try  {
66+ 					if  ($ this setupFS ($ user
67+ 						$ dirContentgetTrashFiles ('/ ' , $ 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- 				$ dirContentgetTrashFiles ('/ ' , $ uid'mtime ' );
54- 				Trashbin::deleteExpiredFiles ($ dirContent$ uid
55- 			} catch  (\Throwable   $ e
56- 				$ this logger ->error ('Error while expiring trashbin for user  '  . $ usergetUID (), ['exception '  => $ e
5775			}
5876
59- 			$ offset
60- 
61- 			if  ($ stopTimetime ()) {
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  ($ countself ::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  $ userbool  {
76- 		\OC_Util::tearDownFS ();
77- 		\OC_Util::setupFS ($ user
87+ 	protected  function  setupFS (IUser $ userbool  {
88+ 		$ this setupManager ->setupForUser ($ user
7889
7990		// Check if this user has a trashbin directory 
80- 		$ viewnew  View ('/ '  . $ user
91+ 		$ viewnew  View ('/ '  . $ user-> getUID () );
8192		if  (!$ viewis_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 , $ offsetself ::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 ($ operationmixed  {
118+ 		$ acquiredfalse ;
119+ 
120+ 		while  ($ acquiredfalse ) {
121+ 			try  {
122+ 				$ this lockingProvider ->acquireLock (self ::OFFSET_CONFIG_KEY_NAME , ILockingProvider::LOCK_EXCLUSIVE , 'Expire trashbin background job offset ' );
123+ 				$ acquiredtrue ;
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