davx5/app/src/main/kotlin/at/bitfire/davdroid/db/WebDavDocument.kt

140 lines
4.6 KiB
Kotlin
Raw Normal View History

2025-11-20 14:05:57 +01:00
/*
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
*/
package at.bitfire.davdroid.db
import android.annotation.SuppressLint
import android.os.Bundle
import android.provider.DocumentsContract.Document
import android.webkit.MimeTypeMap
import androidx.core.os.bundleOf
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import at.bitfire.davdroid.util.DavUtils.MEDIA_TYPE_OCTET_STREAM
import at.bitfire.davdroid.webdav.DocumentState
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import java.io.FileNotFoundException
import java.time.Instant
@Entity(
tableName = "webdav_document",
foreignKeys = [
ForeignKey(entity = WebDavMount::class, parentColumns = ["id"], childColumns = ["mountId"], onDelete = ForeignKey.CASCADE),
ForeignKey(entity = WebDavDocument::class, parentColumns = ["id"], childColumns = ["parentId"], onDelete = ForeignKey.CASCADE)
],
indices = [
Index("mountId", "parentId", "name", unique = true),
Index("parentId")
]
)
// If any column name is modified, also change it in [DavDocumentsProvider$queryChildDocuments]
data class WebDavDocument(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
/** refers to the [WebDavMount] the document belongs to */
val mountId: Long,
/** refers to parent document (*null* when this document is a root document) */
val parentId: Long?,
/** file name (without any slashes) */
val name: String,
val isDirectory: Boolean = false,
val displayName: String? = null,
val mimeType: MediaType? = null,
val eTag: String? = null,
val lastModified: Long? = null,
val size: Long? = null,
val mayBind: Boolean? = null,
val mayUnbind: Boolean? = null,
val mayWriteContent: Boolean? = null,
val quotaAvailable: Long? = null,
val quotaUsed: Long? = null
) {
fun cacheKey(): CacheKey? {
if (eTag != null || lastModified != null)
return CacheKey(id, DocumentState(eTag, lastModified?.let { ts -> Instant.ofEpochMilli(ts) }))
return null
}
@SuppressLint("InlinedApi")
fun toBundle(parent: WebDavDocument?): Bundle {
if (parent?.isDirectory == false)
throw IllegalArgumentException("Parent must be a directory")
val bundle = bundleOf(
Document.COLUMN_DOCUMENT_ID to id.toString(),
Document.COLUMN_DISPLAY_NAME to name
)
displayName?.let { bundle.putString(Document.COLUMN_SUMMARY, it) }
size?.let { bundle.putLong(Document.COLUMN_SIZE, it) }
lastModified?.let { bundle.putLong(Document.COLUMN_LAST_MODIFIED, it) }
// see RFC 3744 appendix B for required privileges for the various operations
var flags = Document.FLAG_SUPPORTS_COPY
if (isDirectory) {
bundle.putString(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR)
if (mayBind != false)
flags += Document.FLAG_DIR_SUPPORTS_CREATE
} else {
val reportedMimeType = mimeType ?:
MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(name)
)?.toMediaTypeOrNull() ?:
MEDIA_TYPE_OCTET_STREAM
bundle.putString(Document.COLUMN_MIME_TYPE, reportedMimeType.toString())
if (mimeType?.type == "image")
flags += Document.FLAG_SUPPORTS_THUMBNAIL
if (mayWriteContent != false)
flags += Document.FLAG_SUPPORTS_WRITE
}
if (parent?.mayUnbind != false)
flags += Document.FLAG_SUPPORTS_DELETE or
Document.FLAG_SUPPORTS_MOVE or
Document.FLAG_SUPPORTS_RENAME
bundle.putInt(Document.COLUMN_FLAGS, flags)
return bundle
}
suspend fun toHttpUrl(db: AppDatabase): HttpUrl {
val mount = db.webDavMountDao().getById(mountId)
val segments = mutableListOf(name)
var parentIter = parentId
while (parentIter != null) {
val parent = db.webDavDocumentDao().get(parentIter) ?: throw FileNotFoundException()
segments += parent.name
parentIter = parent.parentId
}
val builder = mount.url.newBuilder()
for (segment in segments.reversed())
builder.addPathSegment(segment)
return builder.build()
}
/**
* Represents a WebDAV document in a given state (with a given ETag/Last-Modified).
*/
data class CacheKey(
val docId: Long,
val documentState: DocumentState
)
}