mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2025-12-23 15:07:51 -05:00
Merge branch 'main-ose' into 1737-enable-push-by-default
This commit is contained in:
7
.github/workflows/codeql.yml
vendored
7
.github/workflows/codeql.yml
vendored
@@ -3,11 +3,12 @@ name: "CodeQL"
|
||||
on:
|
||||
push:
|
||||
branches: [ main-ose ]
|
||||
pull_request:
|
||||
# pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main-ose ]
|
||||
# branches: [ main-ose ]
|
||||
schedule:
|
||||
- cron: '22 10 * * 1'
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -50,7 +51,7 @@ jobs:
|
||||
# uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Build
|
||||
run: ./gradlew --build-cache --configuration-cache --no-daemon app:assembleOseDebug
|
||||
run: ./gradlew --no-daemon app:compileOseDebugSource
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
24
.github/workflows/dependency-submission.yml
vendored
Normal file
24
.github/workflows/dependency-submission.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Dependency Submission
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'main-ose' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
dependency-submission:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Generate and submit dependency graph
|
||||
uses: gradle/actions/dependency-submission@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
|
||||
dependency-graph-exclude-configurations: '.*[Tt]est.* .*[cC]heck.*'
|
||||
91
.github/workflows/test-dev.yml
vendored
91
.github/workflows/test-dev.yml
vendored
@@ -9,10 +9,17 @@ concurrency:
|
||||
group: test-dev-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# We provide a remote gradle build cache. Take the settings from the secrets and enable
|
||||
# configuration and build cache for all gradle jobs.
|
||||
env:
|
||||
GRADLE_BUILDCACHE_URL: ${{ secrets.gradle_buildcache_url }}
|
||||
GRADLE_BUILDCACHE_USERNAME: ${{ secrets.gradle_buildcache_username }}
|
||||
GRADLE_BUILDCACHE_PASSWORD: ${{ secrets.gradle_buildcache_password }}
|
||||
GRADLE_OPTS: -Dorg.gradle.caching=true -Dorg.gradle.configuration-cache=true
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Compile for build cache
|
||||
if: github.ref == 'refs/heads/main-ose'
|
||||
name: Compile
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -22,16 +29,30 @@ jobs:
|
||||
java-version: 21
|
||||
|
||||
# See https://community.gradle.org/github-actions/docs/setup-gradle/ for more information
|
||||
- uses: gradle/actions/setup-gradle@v5 # creates build cache
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
|
||||
dependency-graph: generate-and-submit # submit Github Dependency Graph info
|
||||
cache-read-only: false # allow branches to update their configuration cache
|
||||
gradle-home-cache-excludes: caches/build-cache-1 # don't cache local build cache because we use a remote cache
|
||||
|
||||
- run: ./gradlew --build-cache --configuration-cache app:compileOseDebugSource
|
||||
- name: Cache Android environment
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.config/.android # needs to be cached so that configuration cache can work
|
||||
key: android-${{ hashFiles('app/build.gradle.kts') }}
|
||||
|
||||
test:
|
||||
- name: Compile
|
||||
run: ./gradlew app:compileOseDebugSource
|
||||
|
||||
# Cache configurations for the other jobs
|
||||
- name: Populate configuration cache
|
||||
run: |
|
||||
./gradlew --dry-run app:lintOseDebug
|
||||
./gradlew --dry-run app:testOseDebugUnitTest
|
||||
./gradlew --dry-run app:virtualOseDebugAndroidTest
|
||||
|
||||
unit_tests:
|
||||
needs: compile
|
||||
if: ${{ !cancelled() }} # even if compile didn't run (because not on main branch)
|
||||
name: Lint and unit tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -45,14 +66,20 @@ jobs:
|
||||
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
|
||||
cache-read-only: true
|
||||
|
||||
- name: Run lint
|
||||
run: ./gradlew --build-cache --configuration-cache app:lintOseDebug
|
||||
- name: Run unit tests
|
||||
run: ./gradlew --build-cache --configuration-cache app:testOseDebugUnitTest
|
||||
- name: Restore Android environment
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ~/.config/.android
|
||||
key: android-${{ hashFiles('app/build.gradle.kts') }}
|
||||
|
||||
test_on_emulator:
|
||||
- name: Lint checks
|
||||
run: ./gradlew app:lintOseDebug
|
||||
|
||||
- name: Unit tests
|
||||
run: ./gradlew app:testOseDebugUnitTest
|
||||
|
||||
instrumented_tests:
|
||||
needs: compile
|
||||
if: ${{ !cancelled() }} # even if compile didn't run (because not on main branch)
|
||||
name: Instrumented tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -66,25 +93,41 @@ jobs:
|
||||
cache-encryption-key: ${{ secrets.gradle_encryption_key }}
|
||||
cache-read-only: true
|
||||
|
||||
- name: Restore Android environment
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ~/.config/.android
|
||||
key: android-${{ hashFiles('app/build.gradle.kts') }}
|
||||
|
||||
# gradle and Android SDK often take more space than what is available on the default runner.
|
||||
# We try to free a few GB here to make gradle-managed devices more reliable.
|
||||
- name: Free some disk space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
android: false # we need the Android SDK
|
||||
large-packages: false # apt takes too long
|
||||
swap-storage: false # gradle needs much memory
|
||||
|
||||
- name: Restore AVD
|
||||
id: restore-avd
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: ~/.config/.android/avd # where AVD is stored
|
||||
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there
|
||||
|
||||
# Enable virtualization for Android emulator
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Restore cached AVD
|
||||
id: restore-avd
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ~/.config/.android/avd
|
||||
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there
|
||||
|
||||
- name: Run device tests
|
||||
run: ./gradlew --build-cache --configuration-cache app:virtualCheck
|
||||
- name: Instrumented tests
|
||||
run: ./gradlew app:virtualOseDebugAndroidTest
|
||||
|
||||
- name: Cache AVD
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v5
|
||||
if: steps.restore-avd.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ~/.config/.android/avd
|
||||
path: ~/.config/.android/avd # where AVD is stored
|
||||
key: avd-${{ hashFiles('app/build.gradle.kts') }} # gradle-managed devices are defined there
|
||||
|
||||
@@ -19,8 +19,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "at.bitfire.davdroid"
|
||||
|
||||
versionCode = 405070100
|
||||
versionName = "4.5.7.1"
|
||||
versionCode = 405080000
|
||||
versionName = "4.5.8-alpha.1"
|
||||
|
||||
base.archivesName = "davx5-ose-$versionName"
|
||||
|
||||
|
||||
@@ -4,13 +4,9 @@
|
||||
|
||||
package at.bitfire.davdroid.resource
|
||||
|
||||
import android.accounts.Account
|
||||
import android.content.ContentProviderClient
|
||||
import androidx.core.content.contentValuesOf
|
||||
import at.bitfire.ical4android.DmfsTask
|
||||
import at.bitfire.ical4android.DmfsTaskList
|
||||
import at.bitfire.ical4android.DmfsTaskListFactory
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import org.dmfs.tasks.contract.TaskContract.TaskListColumns
|
||||
import org.dmfs.tasks.contract.TaskContract.Tasks
|
||||
import java.util.logging.Level
|
||||
@@ -21,40 +17,37 @@ import java.util.logging.Logger
|
||||
*
|
||||
* [TaskLists._SYNC_ID] corresponds to the database collection ID ([at.bitfire.davdroid.db.Collection.id]).
|
||||
*/
|
||||
class LocalTaskList private constructor(
|
||||
account: Account,
|
||||
provider: ContentProviderClient,
|
||||
providerName: TaskProvider.ProviderName,
|
||||
id: Long
|
||||
): DmfsTaskList(account, provider, providerName, id), LocalCollection<LocalTask> {
|
||||
class LocalTaskList (
|
||||
val dmfsTaskList: DmfsTaskList
|
||||
): LocalCollection<LocalTask> {
|
||||
|
||||
private val logger = Logger.getGlobal()
|
||||
|
||||
override val readOnly
|
||||
get() = accessLevel?.let {
|
||||
get() = dmfsTaskList.accessLevel?.let {
|
||||
it != TaskListColumns.ACCESS_LEVEL_UNDEFINED && it <= TaskListColumns.ACCESS_LEVEL_READ
|
||||
} ?: false
|
||||
|
||||
override val dbCollectionId: Long?
|
||||
get() = syncId?.toLongOrNull()
|
||||
get() = dmfsTaskList.syncId?.toLongOrNull()
|
||||
|
||||
override val tag: String
|
||||
get() = "tasks-${account.name}-$id"
|
||||
get() = "tasks-${dmfsTaskList.account.name}-${dmfsTaskList.id}"
|
||||
|
||||
override val title: String
|
||||
get() = name ?: id.toString()
|
||||
get() = dmfsTaskList.name ?: dmfsTaskList.id.toString()
|
||||
|
||||
override var lastSyncState: SyncState?
|
||||
get() = readSyncState()?.let { SyncState.fromString(it) }
|
||||
get() = dmfsTaskList.readSyncState()?.let { SyncState.fromString(it) }
|
||||
set(state) {
|
||||
writeSyncState(state.toString())
|
||||
dmfsTaskList.writeSyncState(state.toString())
|
||||
}
|
||||
|
||||
override fun findDeleted() = queryTasks(Tasks._DELETED, null)
|
||||
override fun findDeleted() = dmfsTaskList.queryTasks(Tasks._DELETED, null)
|
||||
.map { LocalTask(it) }
|
||||
|
||||
override fun findDirty(): List<LocalTask> {
|
||||
val dmfsTasks = queryTasks(Tasks._DIRTY, null)
|
||||
val dmfsTasks = dmfsTaskList.queryTasks(Tasks._DIRTY, null)
|
||||
for (localTask in dmfsTasks) {
|
||||
try {
|
||||
val task = requireNotNull(localTask.task)
|
||||
@@ -71,7 +64,7 @@ class LocalTaskList private constructor(
|
||||
}
|
||||
|
||||
override fun findByName(name: String) =
|
||||
queryTasks("${Tasks._SYNC_ID}=?", arrayOf(name))
|
||||
dmfsTaskList.queryTasks("${Tasks._SYNC_ID}=?", arrayOf(name))
|
||||
.firstOrNull()?.let {
|
||||
LocalTask(it)
|
||||
}
|
||||
@@ -79,32 +72,20 @@ class LocalTaskList private constructor(
|
||||
|
||||
override fun markNotDirty(flags: Int): Int {
|
||||
val values = contentValuesOf(DmfsTask.COLUMN_FLAGS to flags)
|
||||
return provider.update(tasksSyncUri(), values,
|
||||
return dmfsTaskList.provider.update(dmfsTaskList.tasksSyncUri(), values,
|
||||
"${Tasks.LIST_ID}=? AND ${Tasks._DIRTY}=0",
|
||||
arrayOf(id.toString()))
|
||||
arrayOf(dmfsTaskList.id.toString()))
|
||||
}
|
||||
|
||||
override fun removeNotDirtyMarked(flags: Int) =
|
||||
provider.delete(tasksSyncUri(),
|
||||
dmfsTaskList.provider.delete(dmfsTaskList.tasksSyncUri(),
|
||||
"${Tasks.LIST_ID}=? AND NOT ${Tasks._DIRTY} AND ${DmfsTask.COLUMN_FLAGS}=?",
|
||||
arrayOf(id.toString(), flags.toString()))
|
||||
arrayOf(dmfsTaskList.id.toString(), flags.toString()))
|
||||
|
||||
override fun forgetETags() {
|
||||
val values = contentValuesOf(DmfsTask.COLUMN_ETAG to null)
|
||||
provider.update(tasksSyncUri(), values, "${Tasks.LIST_ID}=?",
|
||||
arrayOf(id.toString()))
|
||||
}
|
||||
|
||||
|
||||
object Factory: DmfsTaskListFactory<LocalTaskList> {
|
||||
|
||||
override fun newInstance(
|
||||
account: Account,
|
||||
provider: ContentProviderClient,
|
||||
providerName: TaskProvider.ProviderName,
|
||||
id: Long
|
||||
) = LocalTaskList(account, provider, providerName, id)
|
||||
|
||||
dmfsTaskList.provider.update(dmfsTaskList.tasksSyncUri(), values, "${Tasks.LIST_ID}=?",
|
||||
arrayOf(dmfsTaskList.id.toString()))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class LocalTaskListStore @AssistedInject constructor(
|
||||
|
||||
logger.log(Level.INFO, "Adding local task list", fromCollection)
|
||||
val uri = create(account, client, providerName, fromCollection)
|
||||
return DmfsTaskList.findByID(account, client, providerName, LocalTaskList.Factory, ContentUris.parseId(uri))
|
||||
return LocalTaskList(DmfsTaskList.findByID(account, client, providerName, ContentUris.parseId(uri)))
|
||||
}
|
||||
|
||||
private fun create(account: Account, provider: ContentProviderClient, providerName: TaskProvider.ProviderName, fromCollection: Collection): Uri {
|
||||
@@ -81,7 +81,7 @@ class LocalTaskListStore @AssistedInject constructor(
|
||||
put(TaskLists.SYNC_ENABLED, 1)
|
||||
put(TaskLists.VISIBLE, 1)
|
||||
}
|
||||
return DmfsTaskList.Companion.create(account, provider, providerName, values)
|
||||
return DmfsTaskList.create(account, provider, providerName, values)
|
||||
}
|
||||
|
||||
private fun valuesFromCollectionInfo(info: Collection, withColor: Boolean): ContentValues {
|
||||
@@ -102,12 +102,13 @@ class LocalTaskListStore @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun getAll(account: Account, client: ContentProviderClient) =
|
||||
DmfsTaskList.find(account, LocalTaskList.Factory, client, providerName, null, null)
|
||||
DmfsTaskList.find(account, client, providerName, null, null)
|
||||
.map { LocalTaskList(it) }
|
||||
|
||||
override fun update(client: ContentProviderClient, localCollection: LocalTaskList, fromCollection: Collection) {
|
||||
logger.log(Level.FINE, "Updating local task list ${fromCollection.url}", fromCollection)
|
||||
val accountSettings = accountSettingsFactory.create(localCollection.account)
|
||||
localCollection.update(valuesFromCollectionInfo(fromCollection, withColor = accountSettings.getManageCalendarColors()))
|
||||
val accountSettings = accountSettingsFactory.create(localCollection.dmfsTaskList.account)
|
||||
localCollection.dmfsTaskList.update(valuesFromCollectionInfo(fromCollection, withColor = accountSettings.getManageCalendarColors()))
|
||||
}
|
||||
|
||||
override fun updateAccount(oldAccount: Account, newAccount: Account, @WillNotClose client: ContentProviderClient?) {
|
||||
@@ -119,7 +120,7 @@ class LocalTaskListStore @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun delete(localCollection: LocalTaskList) {
|
||||
localCollection.delete()
|
||||
localCollection.dmfsTaskList.delete()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -98,9 +98,9 @@ class AccountSettingsMigration20 @Inject constructor(
|
||||
for (taskList in taskListStore.getAll(account, provider)) {
|
||||
when (taskList) {
|
||||
is LocalTaskList -> { // tasks.org, OpenTasks
|
||||
val url = taskList.syncId ?: continue
|
||||
val url = taskList.dmfsTaskList.syncId ?: continue
|
||||
collectionRepository.getByServiceAndUrl(calDavServiceId, url)?.let { collection ->
|
||||
taskList.update(contentValuesOf(
|
||||
taskList.dmfsTaskList.update(contentValuesOf(
|
||||
TaskLists._SYNC_ID to collection.id.toString()
|
||||
))
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ abstract class SyncManager<LocalType: LocalResource, out CollectionType: LocalCo
|
||||
|
||||
suspend fun performSync() = withContext(syncDispatcher) {
|
||||
// dismiss previous error notifications
|
||||
syncNotificationManager.dismissInvalidResource(localCollectionTag = localCollection.tag)
|
||||
syncNotificationManager.dismissCollectionError(localCollectionTag = localCollection.tag)
|
||||
|
||||
try {
|
||||
logger.info("Preparing synchronization")
|
||||
|
||||
@@ -159,7 +159,7 @@ class SyncNotificationManager @AssistedInject constructor(
|
||||
/**
|
||||
* Sends a notification to inform the user that a push notification has been received, the
|
||||
* sync has been scheduled, but it still has not run.
|
||||
* Use [dismissInvalidResource] to dismiss the notification.
|
||||
* Use [dismissCollectionError] to dismiss the notification.
|
||||
*
|
||||
* @param dataType The type of data which was synced.
|
||||
* @param notificationTag The tag to use for the notification.
|
||||
@@ -200,7 +200,7 @@ class SyncNotificationManager @AssistedInject constructor(
|
||||
*
|
||||
* @param localCollectionTag The tag of the local collection which is used as notification tag also.
|
||||
*/
|
||||
fun dismissInvalidResource(localCollectionTag: String) =
|
||||
fun dismissCollectionError(localCollectionTag: String) =
|
||||
dismissNotification(localCollectionTag)
|
||||
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ class TaskSyncer @AssistedInject constructor(
|
||||
collectionRepository.getSyncTaskLists(serviceId)
|
||||
|
||||
override fun syncCollection(provider: ContentProviderClient, localCollection: LocalTaskList, remoteCollection: Collection) {
|
||||
logger.info("Synchronizing task list ${localCollection.id} with database collection ID: ${localCollection.dbCollectionId}")
|
||||
logger.info("Synchronizing task list ${localCollection.dmfsTaskList.id} with database collection ID: ${localCollection.dbCollectionId}")
|
||||
|
||||
val syncManager = tasksSyncManagerFactory.tasksSyncManager(
|
||||
account,
|
||||
|
||||
@@ -164,7 +164,7 @@ class TasksSyncManager @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun postProcess() {
|
||||
val touched = localCollection.touchRelations()
|
||||
val touched = localCollection.dmfsTaskList.touchRelations()
|
||||
logger.info("Touched $touched relations")
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ class TasksSyncManager @AssistedInject constructor(
|
||||
local.update(newData)
|
||||
} else {
|
||||
logger.log(Level.INFO, "Adding $fileName to local task list", newData)
|
||||
val newLocal = LocalTask(DmfsTask(localCollection, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT))
|
||||
val newLocal = LocalTask(DmfsTask(localCollection.dmfsTaskList, newData, fileName, eTag, LocalResource.FLAG_REMOTELY_PRESENT))
|
||||
SyncException.wrapWithLocalResource(newLocal) {
|
||||
newLocal.add()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ androidx-test-junit = "1.3.0"
|
||||
androidx-work = "2.11.0"
|
||||
bitfire-cert4android = "42d883e958"
|
||||
bitfire-dav4jvm = "57321c95ad"
|
||||
bitfire-synctools = "de78892b5c"
|
||||
bitfire-synctools = "42e82f4769"
|
||||
compose-accompanist = "0.37.3"
|
||||
compose-bom = "2025.12.00"
|
||||
conscrypt = "2.5.3"
|
||||
|
||||
@@ -19,4 +19,18 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
// use remote build cache, if configured
|
||||
if (System.getenv("GRADLE_BUILDCACHE_URL") != null) {
|
||||
buildCache {
|
||||
remote<HttpBuildCache> {
|
||||
url = uri(System.getenv("GRADLE_BUILDCACHE_URL"))
|
||||
credentials {
|
||||
username = System.getenv("GRADLE_BUILDCACHE_USERNAME")
|
||||
password = System.getenv("GRADLE_BUILDCACHE_PASSWORD")
|
||||
}
|
||||
isPush = true // read/write
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
Reference in New Issue
Block a user