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