Skip to content

Commit 0b2df4b

Browse files
Merge pull request #15298 from nextcloud/backport/14757/stable-3.32
Backport - BugFix - CalendarImportWork
2 parents f2ad2d2 + c5c1535 commit 0b2df4b

3 files changed

Lines changed: 120 additions & 89 deletions

File tree

1.34 KB
Loading
Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Nextcloud - Android Client
33
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
45
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
56
* SPDX-FileCopyrightText: 2017 Tobias Kaminsky
67
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH
@@ -13,6 +14,7 @@ import android.content.Context
1314
import androidx.work.Worker
1415
import androidx.work.WorkerParameters
1516
import com.nextcloud.client.logger.Logger
17+
import com.owncloud.android.lib.common.utils.Log_OC
1618
import net.fortuna.ical4j.data.CalendarBuilder
1719
import third_parties.sufficientlysecure.AndroidCalendar
1820
import third_parties.sufficientlysecure.CalendarSource
@@ -28,37 +30,56 @@ class CalendarImportWork(
2830

2931
companion object {
3032
const val TAG = "CalendarImportWork"
31-
const val SELECTED_CALENDARS = "selected_contacts_indices"
3233
}
3334

35+
@Suppress("TooGenericExceptionCaught")
3436
override fun doWork(): Result {
35-
val calendarPaths = inputData.getStringArray(SELECTED_CALENDARS) ?: arrayOf<String>()
36-
val calendars = inputData.keyValueMap as Map<String, AndroidCalendar>
37+
val calendars = inputData.keyValueMap as? Map<*, *>
38+
if (calendars == null) {
39+
logger.d(TAG, "CalendarImportWork cancelled due to null empty input data")
40+
return Result.failure()
41+
}
3742

3843
val calendarBuilder = CalendarBuilder()
3944

40-
for ((path, selectedCalendar) in calendars) {
41-
logger.d(TAG, "Import calendar from $path")
45+
for ((path, selectedCalendarIndex) in calendars) {
46+
try {
47+
if (path !is String || selectedCalendarIndex !is Int) {
48+
logger.d(TAG, "Skipping wrong input data types: $path - $selectedCalendarIndex")
49+
continue
50+
}
51+
52+
logger.d(TAG, "Import calendar from $path")
53+
54+
val file = File(path)
55+
val calendarSource = CalendarSource(
56+
file.toURI().toURL().toString(),
57+
null,
58+
null,
59+
null,
60+
appContext
61+
)
4262

43-
val file = File(path)
44-
val calendarSource = CalendarSource(
45-
file.toURI().toURL().toString(),
46-
null,
47-
null,
48-
null,
49-
appContext
50-
)
63+
val calendarList = AndroidCalendar.loadAll(contentResolver)
64+
if (selectedCalendarIndex >= calendarList.size) {
65+
logger.d(TAG, "Skipping selectedCalendarIndex out of bound")
66+
continue
67+
}
5168

52-
val calendars = AndroidCalendar.loadAll(contentResolver)[0]
69+
val selectedCalendar = calendarList[selectedCalendarIndex]
5370

54-
ProcessVEvent(
55-
appContext,
56-
calendarBuilder.build(calendarSource.stream),
57-
selectedCalendar,
58-
true
59-
).run()
71+
ProcessVEvent(
72+
appContext,
73+
calendarBuilder.build(calendarSource.stream),
74+
selectedCalendar,
75+
true
76+
).run()
77+
} catch (e: Exception) {
78+
Log_OC.e(TAG, "skipping calendarIndex: $selectedCalendarIndex due to: $e")
79+
}
6080
}
6181

82+
logger.d(TAG, "CalendarImportWork successfully completed")
6283
return Result.success()
6384
}
6485
}

app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt

Lines changed: 79 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
package com.owncloud.android.ui.fragment.contactsbackup
1010

1111
import android.Manifest
12-
import android.annotation.SuppressLint
1312
import android.app.DatePickerDialog
1413
import android.app.DatePickerDialog.OnDateSetListener
1514
import android.content.Context
1615
import android.content.Intent
17-
import android.os.AsyncTask
1816
import android.os.Bundle
1917
import android.view.LayoutInflater
2018
import android.view.MenuItem
@@ -24,10 +22,12 @@ import android.widget.CompoundButton
2422
import android.widget.DatePicker
2523
import android.widget.Toast
2624
import androidx.core.app.ActivityCompat
25+
import androidx.lifecycle.lifecycleScope
2726
import com.nextcloud.client.account.User
2827
import com.nextcloud.client.di.Injectable
2928
import com.nextcloud.client.jobs.BackgroundJobManager
3029
import com.nextcloud.utils.extensions.getSerializableArgument
30+
import com.nextcloud.utils.extensions.setVisibleIf
3131
import com.owncloud.android.R
3232
import com.owncloud.android.databinding.BackupFragmentBinding
3333
import com.owncloud.android.datamodel.ArbitraryDataProvider
@@ -44,6 +44,9 @@ import com.owncloud.android.utils.PermissionUtil
4444
import com.owncloud.android.utils.PermissionUtil.checkSelfPermission
4545
import com.owncloud.android.utils.theme.ThemeUtils
4646
import com.owncloud.android.utils.theme.ViewThemeUtils
47+
import kotlinx.coroutines.Dispatchers
48+
import kotlinx.coroutines.launch
49+
import kotlinx.coroutines.withContext
4750
import third_parties.daveKoeller.AlphanumComparator
4851
import java.util.Calendar
4952
import java.util.GregorianCalendar
@@ -77,7 +80,7 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
7780
private var showCalendarBackup = true
7881
private var isCalendarBackupEnabled: Boolean
7982
get() = arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CALENDAR_BACKUP_ENABLED)
80-
private set(enabled) {
83+
set(enabled) {
8184
arbitraryDataProvider.storeOrUpdateKeyValue(
8285
user.accountName,
8386
PREFERENCE_CALENDAR_BACKUP_ENABLED,
@@ -87,21 +90,28 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
8790

8891
private var isContactsBackupEnabled: Boolean
8992
get() = arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_BACKUP_ENABLED)
90-
private set(enabled) {
93+
set(enabled) {
9194
arbitraryDataProvider.storeOrUpdateKeyValue(
9295
user.accountName,
9396
PREFERENCE_CONTACTS_BACKUP_ENABLED,
9497
enabled
9598
)
9699
}
97100

101+
private lateinit var contactsBackupFolderPath: String
102+
private lateinit var calendarBackupFolderPath: String
103+
98104
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
99105
// use grey as fallback for elements where custom theming is not available
100106
if (themeUtils.themingEnabled(context)) {
101107
requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true)
102108
}
103109

104110
binding = BackupFragmentBinding.inflate(inflater, container, false)
111+
112+
contactsBackupFolderPath = getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
113+
calendarBackupFolderPath = getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
114+
105115
val view: View = binding.root
106116

107117
setHasOptionsMenu(true)
@@ -175,8 +185,9 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
175185
}
176186

177187
private fun setBackupNowButtonVisibility() {
178-
binding.backupNow.visibility =
179-
if (binding.contacts.isChecked || binding.calendar.isChecked) View.VISIBLE else View.INVISIBLE
188+
binding.run {
189+
backupNow.isEnabled = (contacts.isChecked || calendar.isChecked)
190+
}
180191
}
181192

182193
private fun setOnClickListeners() {
@@ -239,61 +250,73 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
239250
openDate(selectedDate)
240251
}
241252

242-
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
243-
if (contactsPreferenceActivity != null) {
244-
val backupFolderPath = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
245-
refreshBackupFolder(
246-
backupFolderPath,
247-
contactsPreferenceActivity.applicationContext,
248-
contactsPreferenceActivity.storageManager
249-
)
253+
(activity as? ContactsPreferenceActivity)?.let {
254+
refreshBackupFolder(it.storageManager)
250255
}
251256
}
252257

253-
private fun refreshBackupFolder(
254-
backupFolderPath: String,
255-
context: Context,
256-
storageManager: FileDataStorageManager
257-
) {
258-
val task: AsyncTask<String, Int, Boolean> =
259-
@SuppressLint("StaticFieldLeak")
260-
object : AsyncTask<String, Int, Boolean>() {
261-
@Deprecated("Deprecated in Java")
262-
override fun doInBackground(vararg path: String): Boolean {
263-
val folder = storageManager.getFileByPath(path[0])
264-
return if (folder != null) {
265-
val operation = RefreshFolderOperation(
266-
folder,
267-
System.currentTimeMillis(),
268-
false,
269-
false,
270-
storageManager,
271-
user,
272-
context
273-
)
274-
val result = operation.execute(user, context)
275-
result.isSuccess
276-
} else {
277-
false
278-
}
258+
private fun refreshBackupFolder(storageManager: FileDataStorageManager) {
259+
lifecycleScope.launch {
260+
val backupFiles = listOf(calendarBackupFolderPath, contactsBackupFolderPath)
261+
.mapNotNull { path -> storageManager.getFileByDecryptedRemotePath(path) }
262+
.flatMap { folder -> fetchBackupFiles(folder, storageManager) }
263+
.toMutableList()
264+
.also {
265+
it.sortWith(AlphanumComparator())
279266
}
280267

281-
@Deprecated("Deprecated in Java")
282-
override fun onPostExecute(result: Boolean) {
283-
if (result) {
284-
val backupFolder = storageManager.getFileByPath(backupFolderPath)
285-
val backupFiles = storageManager.getFolderContent(backupFolder, false)
286-
backupFiles.sortWith(AlphanumComparator())
287-
if (backupFiles.isEmpty()) {
288-
binding.contactsDatepicker.visibility = View.INVISIBLE
289-
} else {
290-
binding.contactsDatepicker.visibility = View.VISIBLE
291-
}
292-
}
293-
}
268+
withContext(Dispatchers.Main) {
269+
binding.contactsDatepicker.setVisibleIf(backupFiles.isNotEmpty())
294270
}
271+
}
272+
}
273+
274+
/**
275+
* Returns backup files from database
276+
*/
277+
private fun getBackupFiles(): List<OCFile> {
278+
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
279+
val storageManager = contactsPreferenceActivity?.storageManager ?: return listOf()
280+
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderPath)
281+
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderPath)
295282

296-
task.execute(backupFolderPath)
283+
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
284+
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
285+
return backupFiles
286+
}
287+
288+
/**
289+
* Refreshes the folder and returns updated backup files
290+
*/
291+
@Suppress("TooGenericExceptionCaught")
292+
private suspend fun fetchBackupFiles(folder: OCFile, storageManager: FileDataStorageManager): List<OCFile> {
293+
return withContext(Dispatchers.IO) {
294+
try {
295+
val operation = RefreshFolderOperation(
296+
folder,
297+
System.currentTimeMillis(),
298+
false,
299+
false,
300+
storageManager,
301+
user,
302+
context
303+
)
304+
305+
@Suppress("DEPRECATION")
306+
val result = operation.execute(user, context)
307+
308+
if (result.isSuccess) {
309+
Log_OC.d(TAG, "Backup files fetched")
310+
storageManager.getFolderContent(folder, false)
311+
} else {
312+
Log_OC.d(TAG, "Backup files cannot be fetched")
313+
listOf()
314+
}
315+
} catch (e: Exception) {
316+
Log_OC.d(TAG, "Exception fetchBackupFiles: $e")
317+
listOf()
318+
}
319+
}
297320
}
298321

299322
@Deprecated("Deprecated in Java")
@@ -500,14 +523,7 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
500523
return
501524
}
502525

503-
val contactsBackupFolderString = getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
504-
val calendarBackupFolderString = getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
505-
val storageManager = contactsPreferenceActivity.storageManager
506-
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
507-
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
508-
509-
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
510-
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
526+
val backupFiles = getBackupFiles().toMutableList()
511527
backupFiles.sortBy { it.modificationTimestamp }
512528

513529
if (backupFiles.isNotEmpty() && backupFiles.last() != null) {
@@ -570,13 +586,7 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
570586
}
571587

572588
selectedDate = GregorianCalendar(year, month, dayOfMonth)
573-
val contactsBackupFolderString = getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
574-
val calendarBackupFolderString = getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
575-
val storageManager = contactsPreferenceActivity.storageManager
576-
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
577-
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
578-
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
579-
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
589+
val backupFiles = getBackupFiles()
580590

581591
// find file with modification with date and time between 00:00 and 23:59
582592
// if more than one file exists, take oldest

0 commit comments

Comments
 (0)