2727namespace OCA \Theming \Jobs ;
2828
2929use OCA \Theming \AppInfo \Application ;
30- use OCP \App \IAppManager ;
3130use OCP \AppFramework \Utility \ITimeFactory ;
3231use OCP \BackgroundJob \IJobList ;
3332use OCP \BackgroundJob \QueuedJob ;
3433use OCP \Files \AppData \IAppDataFactory ;
34+ use OCP \Files \IAppData ;
3535use OCP \Files \NotFoundException ;
3636use OCP \Files \NotPermittedException ;
3737use OCP \Files \SimpleFS \ISimpleFolder ;
38- use OCP \IConfig ;
38+ use OCP \IDBConnection ;
39+ use Psr \Log \LoggerInterface ;
3940
4041class MigrateBackgroundImages extends QueuedJob {
4142 public const TIME_SENSITIVE = 0 ;
4243
43- private IConfig $ config ;
44- private IAppManager $ appManager ;
44+ public const STAGE_PREPARE = 'prepare ' ;
45+ public const STAGE_EXECUTE = 'execute ' ;
46+ // will be saved in appdata/theming/global/
47+ protected const STATE_FILE_NAME = '25_dashboard_to_theming_migration_users.json ' ;
48+
4549 private IAppDataFactory $ appDataFactory ;
4650 private IJobList $ jobList ;
47-
48- public function __construct (ITimeFactory $ time , IAppDataFactory $ appDataFactory , IConfig $ config , IAppManager $ appManager , IJobList $ jobList ) {
51+ private IDBConnection $ dbc ;
52+ private IAppData $ appData ;
53+ private LoggerInterface $ logger ;
54+
55+ public function __construct (
56+ ITimeFactory $ time ,
57+ IAppDataFactory $ appDataFactory ,
58+ IJobList $ jobList ,
59+ IDBConnection $ dbc ,
60+ IAppData $ appData ,
61+ LoggerInterface $ logger
62+ ) {
4963 parent ::__construct ($ time );
50- $ this ->config = $ config ;
51- $ this ->appManager = $ appManager ;
5264 $ this ->appDataFactory = $ appDataFactory ;
5365 $ this ->jobList = $ jobList ;
66+ $ this ->dbc = $ dbc ;
67+ $ this ->appData = $ appData ;
68+ $ this ->logger = $ logger ;
5469 }
5570
5671 protected function run ($ argument ): void {
57- if (!$ this ->appManager ->isEnabledForUser ('dashboard ' )) {
58- return ;
72+ if (!isset ($ argument ['stage ' ])) {
73+ // not executed in 25.0.0?!
74+ $ argument ['stage ' ] = self ::STAGE_PREPARE ;
5975 }
6076
61- $ dashboardData = $ this ->appDataFactory ->get ('dashboard ' );
77+ switch ($ argument ['stage ' ]) {
78+ case self ::STAGE_PREPARE :
79+ $ this ->runPreparation ();
80+ break ;
81+ case self ::STAGE_EXECUTE :
82+ $ this ->runMigration ();
83+ break ;
84+ default :
85+ break ;
86+ }
87+ }
6288
63- $ userIds = $ this ->config ->getUsersForUserValue ('theming ' , 'background ' , 'custom ' );
89+ protected function runPreparation (): void {
90+ try {
91+ $ selector = $ this ->dbc ->getQueryBuilder ();
92+ $ result = $ selector ->select ('userid ' )
93+ ->from ('preferences ' )
94+ ->where ($ selector ->expr ()->eq ('appid ' , $ selector ->createNamedParameter ('theming ' )))
95+ ->andWhere ($ selector ->expr ()->eq ('configkey ' , $ selector ->createNamedParameter ('background ' )))
96+ ->andWhere ($ selector ->expr ()->eq ('configvalue ' , $ selector ->createNamedParameter ('custom ' )))
97+ ->executeQuery ();
98+
99+ $ userIds = $ result ->fetchAll (\PDO ::FETCH_COLUMN );
100+ $ this ->storeUserIdsToProcess ($ userIds );
101+ } catch (\Throwable $ t ) {
102+ $ this ->jobList ->add (self ::class, self ::STAGE_PREPARE );
103+ throw $ t ;
104+ }
105+ $ this ->jobList ->add (self ::class, self ::STAGE_EXECUTE );
106+ }
64107
65- $ notSoFastMode = \count ($ userIds ) > 5000 ;
66- $ reTrigger = false ;
67- $ processed = 0 ;
108+ /**
109+ * @throws NotPermittedException
110+ * @throws NotFoundException
111+ */
112+ protected function runMigration (): void {
113+ $ allUserIds = $ this ->readUserIdsToProcess ();
114+ $ notSoFastMode = count ($ allUserIds ) > 5000 ;
115+ $ dashboardData = $ this ->appDataFactory ->get ('dashboard ' );
68116
117+ $ userIds = $ notSoFastMode ? array_slice ($ allUserIds , 0 , 5000 ) : $ allUserIds ;
69118 foreach ($ userIds as $ userId ) {
70119 try {
71- // precondition
72- if ($ notSoFastMode ) {
73- if ($ this ->config ->getUserValue ($ userId , 'theming ' , 'background-migrated ' , '0 ' ) === '1 ' ) {
74- // already migrated
75- continue ;
76- }
77- $ reTrigger = true ;
78- }
79-
80120 // migration
81121 $ file = $ dashboardData ->getFolder ($ userId )->getFile ('background.jpg ' );
82122 $ targetDir = $ this ->getUserFolder ($ userId );
@@ -87,18 +127,82 @@ protected function run($argument): void {
87127 $ file ->delete ();
88128 } catch (NotFoundException |NotPermittedException $ e ) {
89129 }
90- // capture state
91- if ($ notSoFastMode ) {
92- $ this ->config ->setUserValue ($ userId , 'theming ' , 'background-migrated ' , '1 ' );
93- $ processed ++;
130+ }
131+
132+ if ($ notSoFastMode ) {
133+ $ remainingUserIds = array_slice ($ allUserIds , 5000 );
134+ $ this ->storeUserIdsToProcess ($ remainingUserIds );
135+ $ this ->jobList ->add (self ::class, ['stage ' => self ::STAGE_EXECUTE ]);
136+ } else {
137+ $ this ->deleteStateFile ();
138+ }
139+ }
140+
141+ /**
142+ * @throws NotPermittedException
143+ * @throws NotFoundException
144+ */
145+ protected function readUserIdsToProcess (): array {
146+ $ globalFolder = $ this ->appData ->getFolder ('global ' );
147+ if ($ globalFolder ->fileExists (self ::STATE_FILE_NAME )) {
148+ $ file = $ globalFolder ->getFile (self ::STATE_FILE_NAME );
149+ try {
150+ $ userIds = \json_decode ($ file ->getContent (), true );
151+ } catch (NotFoundException $ e ) {
152+ $ userIds = [];
94153 }
95- if ($ processed > 4999 ) {
96- break ;
154+ if ($ userIds === null ) {
155+ $ userIds = [] ;
97156 }
157+ } else {
158+ $ userIds = [];
98159 }
160+ return $ userIds ;
161+ }
99162
100- if ($ reTrigger ) {
101- $ this ->jobList ->add (self ::class);
163+ /**
164+ * @throws NotFoundException
165+ */
166+ protected function storeUserIdsToProcess (array $ userIds ): void {
167+ $ storableUserIds = \json_encode ($ userIds );
168+ $ globalFolder = $ this ->appData ->getFolder ('global ' );
169+ try {
170+ if ($ globalFolder ->fileExists (self ::STATE_FILE_NAME )) {
171+ $ file = $ globalFolder ->getFile (self ::STATE_FILE_NAME );
172+ } else {
173+ $ file = $ globalFolder ->newFile (self ::STATE_FILE_NAME );
174+ }
175+ $ file ->putContent ($ storableUserIds );
176+ } catch (NotFoundException $ e ) {
177+ } catch (NotPermittedException $ e ) {
178+ $ this ->logger ->warning ('Lacking permissions to create {file} ' ,
179+ [
180+ 'app ' => 'theming ' ,
181+ 'file ' => self ::STATE_FILE_NAME ,
182+ 'exception ' => $ e ,
183+ ]
184+ );
185+ }
186+ }
187+
188+ /**
189+ * @throws NotFoundException
190+ */
191+ protected function deleteStateFile (): void {
192+ $ globalFolder = $ this ->appData ->getFolder ('global ' );
193+ if ($ globalFolder ->fileExists (self ::STATE_FILE_NAME )) {
194+ $ file = $ globalFolder ->getFile (self ::STATE_FILE_NAME );
195+ try {
196+ $ file ->delete ();
197+ } catch (NotPermittedException $ e ) {
198+ $ this ->logger ->info ('Could not delete {file} due to permissions. It is safe to delete manually inside data -> appdata -> theming -> global. ' ,
199+ [
200+ 'app ' => 'theming ' ,
201+ 'file ' => $ file ->getName (),
202+ 'exception ' => $ e ,
203+ ]
204+ );
205+ }
102206 }
103207 }
104208
0 commit comments