Skip to content

Commit

Permalink
Merge pull request #3370 from AndBible/nextcloud
Browse files Browse the repository at this point in the history
Nextcloud implementation for Device Sync
  • Loading branch information
tuomas2 authored Feb 13, 2025
2 parents 6356575 + bf85847 commit 4f7c1fc
Show file tree
Hide file tree
Showing 20 changed files with 718 additions and 59 deletions.
40 changes: 40 additions & 0 deletions app/bibleview-js/nextcloud-search.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
### Nextcloud WebDAV SEARCH request (for debugging Nextcloud sync)
SEARCH http://127.0.0.1:8081/remote.php/dav/
Authorization: Basic admin admin
Content-Type: text/xml

<d:searchrequest xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:basicsearch>
<d:select>
<d:prop>
<oc:fileid/>
<d:displayname/>
<oc:size/>
<d:getcontenttype/>
<d:getlastmodified/>
</d:prop>
</d:select>
<d:from>
<d:scope>
<d:href>/files/admin/Photos</d:href>
<d:depth>infinity</d:depth>
</d:scope>
</d:from>
<d:where>
<d:gt>
<d:prop>
<d:getlastmodified/>
</d:prop>
<d:literal>2025-01-10T17:00:00Z</d:literal>
</d:gt>
</d:where>
<d:orderby>
<d:order>
<d:prop>
<d:getlastmodified/>
</d:prop>
<d:ascending/>
</d:order>
</d:orderby>
</d:basicsearch>
</d:searchrequest>
9 changes: 8 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ dependencies {
exclude("org.apache.httpcomponents")
}
}

//implementation("androidx.recyclerview:recyclerview-selection:1.0.0")

//implementation("com.jaredrummler:colorpicker:1.1.0")
Expand Down Expand Up @@ -416,6 +415,14 @@ dependencies {
implementation("org.jdom:jdom2:$jdomVersion")
implementation("jaxen:jaxen:2.0.0")

// Next cloud related dependencies
implementation("com.github.nextcloud:android-library:2.19.1") {
exclude(group = "org.ogce", module = "xpp3") // unused in Android and brings wrong Junit version
}
implementation("commons-httpclient:commons-httpclient:3.1@jar") // Make sure this is same version as in NextCloud lib
implementation("org.apache.jackrabbit:jackrabbit-webdav:2.13.5") // Make sure this is same version as in NextCloud lib


debugImplementation("com.facebook.stetho:stetho:1.6.0")

// TESTS
Expand Down
45 changes: 45 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,48 @@
-dontwarn java.lang.invoke.StringConcatFactory

-keep class org.jaxen.saxpath.base.XPathReader

# Nextcloud
-keep,allowshrinking class com.owncloud.android.** { *; }
-keep,allowshrinking class org.apache.jackrabbit.webdav.** { *; }
-keep,allowshrinking class org.apache.commons.codec.** { *; }
-keep,allowshrinking class org.apache.commons.logging.** { *; }
-keep class org.apache.commons.httpclient.** { *; }
-keep,allowshrinking class org.parceler.** { *; }
-keep,allowshrinking class org.slf4j.** { *; }

#ignore nextcloud related warnings
-dontwarn com.owncloud.android.lib.**
-dontwarn org.apache.jackrabbit.webdav.**
-dontwarn org.apache.commons.codec.**
-dontwarn org.apache.commons.logging.**
-dontwarn org.slf4j.**

-dontskipnonpubliclibraryclasses

-keepclasseswithmembernames class * {
native <methods>;
}

-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}




-keepattributes InnerClasses
#end nextcloud
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"
android:versionCode="845"
android:versionName="5.0.845">
android:versionCode="846"
android:versionName="5.0.846">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" android:minSdkVersion="24"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,15 @@ interface SyncDao {
@Query("SELECT booleanVAlue FROM SyncConfiguration WHERE keyName = :keyName")
fun getBoolean(keyName: String): Boolean?

@Query("SELECT * FROM SyncConfiguration WHERE keyName = :keyName")
fun getConfig(keyName: String): SyncConfiguration?

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun setConfig(config: SyncConfiguration)

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun setConfig(configs: List<SyncConfiguration>)

@Query("DELETE FROM SyncConfiguration WHERE keyName = :keyName")
fun removeConfig(keyName: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ class MainBibleActivity : CustomTitlebarActivityBase() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
networkAvailable = true
syncScope.launch { startSync() }
if (!paused) {
syncScope.launch { startSync() }
}
}

override fun onLost(network: Network) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,12 @@ class SyncSettingsActivity: ActivityBase() {
}

class SyncSettingsFragment: PreferenceFragmentCompat() {
private val hourglassContainer = lazy { Hourglass(requireContext()) }
private val hourglass get() = hourglassContainer.value
private fun setupDrivePref(pref: SwitchPreferenceCompat) {
val category = SyncableDatabaseDefinition.nameToCategory[pref.key.split("_")[1].uppercase()]!!
pref.setOnPreferenceClickListener {
if(category.syncEnabled) {
lifecycleScope.launch {
val hourglass = Hourglass(requireContext())
hourglass.show(R.string.synchronizing)
if (!CloudSync.signedIn) {
CloudSync.signIn(activity as ActivityBase)
Expand Down Expand Up @@ -105,6 +104,7 @@ class SyncSettingsFragment: PreferenceFragmentCompat() {
setOnPreferenceClickListener {
lifecycleScope.launch {
if(Dialogs.simpleQuestion(requireContext(), message =getString(R.string.sync_confirmation))) {
val hourglass = Hourglass(requireContext())
hourglass.show()
CloudSync.signOut()
hourglass.dismiss()
Expand All @@ -125,6 +125,10 @@ class SyncSettingsFragment: PreferenceFragmentCompat() {
}
}
}
val usernamePref = preferenceScreen.findPreference<Preference>("gdrive_username")!!
val passwordPref = preferenceScreen.findPreference<Preference>("gdrive_password")!!
val serverUrlPref = preferenceScreen.findPreference<Preference>("gdrive_server_url")!!

preferenceScreen.findPreference<ListPreference>("sync_adapter")!!.run {
if(CloudSync.signedIn) {
isEnabled = false
Expand All @@ -133,15 +137,24 @@ class SyncSettingsFragment: PreferenceFragmentCompat() {
val sum1 = getString(R.string.prefs_sync_introduction_summary1)
val driveSum = getString(R.string.prefs_sync_introduction_summary2, getString(R.string.app_name_medium))
var result = sum1
if(newValue == CloudAdapters.GOOGLE_DRIVE) {
val isGoogleDrive = newValue == CloudAdapters.GOOGLE_DRIVE
if(isGoogleDrive) {
result += " $driveSum"
}
result += " " + getString(R.string.sync_adapter_summary, getString(newValue.displayName))
usernamePref.isVisible = !isGoogleDrive
passwordPref.isVisible = !isGoogleDrive
serverUrlPref.isVisible = !isGoogleDrive
if(CloudSync.signedIn) {
usernamePref.isEnabled = false
passwordPref.isEnabled = false
serverUrlPref.isEnabled = false
}
result += " " + getString(R.string.sync_adapter_summary, newValue.displayName)
summary = result
}
setSummary(CloudAdapters.current)
entryValues = CloudAdapters.values().map { it.name }.toTypedArray()
entries = CloudAdapters.values().map { getString(it.displayName) }.toTypedArray()
entryValues = CloudAdapters.allEnabled.map { it.name }.toTypedArray()
entries = CloudAdapters.allEnabled.map { it.displayName }.toTypedArray()
setOnPreferenceChangeListener { _, newValue ->
setSummary(CloudAdapters.valueOf(newValue as String))
true
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/net/bible/android/view/util/Hourglass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,23 @@ class Hourglass(val context: Context) {
hourglass.setMessage(application.getText(messageId))
hourglass.isIndeterminate = true
hourglass.setCancelable(false)
Log.e(TAG, "Show")
hourglass.show()
}
}

suspend fun dismiss() {
withContext(Dispatchers.Main) {
if (hourglass == null) {
Log.e(TAG, "Hourglass already dismissed!")
} else {
Log.e(TAG, "Dismiss")
}
hourglass?.dismiss()
hourglass = null
}
}
companion object {
private val TAG = "Hourglass"
}
}
18 changes: 11 additions & 7 deletions app/src/main/java/net/bible/service/cloudsync/CloudAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package net.bible.service.cloudsync

import net.bible.android.database.SyncConfiguration
import net.bible.android.view.activity.base.ActivityBase
import java.io.File
import java.io.OutputStream
Expand All @@ -33,16 +34,19 @@ interface CloudAdapter {
val signedIn: Boolean
suspend fun signIn(activity: ActivityBase): Boolean
suspend fun signOut()
fun get(id: String): CloudFile
fun listFiles(
suspend fun get(id: String): CloudFile
suspend fun listFiles(
parentsIds: List<String>? = null,
name: String? = null,
mimeType: String? = null,
createdTimeAtLeast: Long? = null
): List<CloudFile>
fun getFolders(parentId: String): List<CloudFile>
fun download(id: String, outputStream: OutputStream)
fun createNewFolder(name: String, parentId: String? = null): CloudFile
fun upload(name: String, file: File, parentId: String? = null): CloudFile
fun delete(id: String)
suspend fun getFolders(parentId: String): List<CloudFile>
suspend fun download(id: String, outputStream: OutputStream)
suspend fun createNewFolder(name: String, parentId: String? = null): CloudFile
suspend fun upload(name: String, file: File, parentId: String): CloudFile
suspend fun delete(id: String)
suspend fun isSyncFolderKnown(dbDef: SyncableDatabaseAccessor<*>, name: String, id: String): Boolean
suspend fun makeSyncFolderKnown(dbDef: SyncableDatabaseAccessor<*>, name: String, id: String)
fun getConfigs(dbDef: SyncableDatabaseAccessor<*>): List<SyncConfiguration>
}
Loading

0 comments on commit 4f7c1fc

Please sign in to comment.