This commit is contained in:
xis 2023-10-12 19:40:38 +02:00
parent 70ed4185fd
commit aecc8c5973
4 changed files with 32 additions and 62 deletions

View file

@ -32,48 +32,31 @@ class UpnpController(
@PathVariable("uid") uid: String, @PathVariable("uid") uid: String,
request: HttpServletRequest request: HttpServletRequest
): Resource { ): Resource {
logger.info { "GET ICON request from ${request.remoteAddr}: ${request.requestURI}" } logger.info { "GET icon request from ${request.remoteAddr}: ${request.requestURI}" }
return InputStreamResource(MediaServer.iconResource()); return InputStreamResource(MediaServer.iconResource());
} }
@RequestMapping( @RequestMapping(
method = [GET, HEAD], value = [ method = [GET, HEAD, POST], value = [
"/dev/{uid}/desc", "/dev/{uid}/desc",
"/dev/{uid}/svc/upnp-org/ContentDirectory/desc", "/dev/{uid}/svc/upnp-org/ContentDirectory/desc",
"/dev/{uid}/svc/upnp-org/ConnectionManager/desc" "/dev/{uid}/svc/upnp-org/ConnectionManager/desc",
],
produces = ["application/xml;charset=utf8", "text/xml;charset=utf8"]
)
fun handleGet(
@PathVariable("uid") uid: String,
request: HttpServletRequest
): ResponseEntity<Any> {
logger.info { "GET request from ${request.remoteAddr}: ${request.requestURI}" }
val r = upnpStreamProcessor.processMessage(streamRequestMapper.map(request))
return ResponseEntity(
r.body,
HttpHeaders().also { h -> r.headers.entries.forEach { h.add(it.key, it.value.joinToString { it }) } },
HttpStatusCode.valueOf(r.operation.statusCode)
)
}
@RequestMapping(
method = [POST], value = [
"/dev/{uid}/svc/upnp-org/ContentDirectory/action" "/dev/{uid}/svc/upnp-org/ContentDirectory/action"
], ],
produces = ["application/xml;charset=utf8", "text/xml;charset=utf8"] produces = ["application/xml;charset=utf8", "text/xml;charset=utf8"]
) )
fun handlePost( fun handleUpnp(
@PathVariable("uid") uid: String, @PathVariable("uid") uid: String,
request: HttpServletRequest request: HttpServletRequest
): ResponseEntity<Any> { ): ResponseEntity<Any> {
logger.info { "POST request from ${request.remoteAddr}: ${request.requestURI}" } logger.info { "Upnp ${request.method} request from ${request.remoteAddr}: ${request.requestURI}" }
val r = upnpStreamProcessor.processMessage(streamRequestMapper.map(request)) return with(upnpStreamProcessor.processMessage(streamRequestMapper.map(request))) {
return ResponseEntity( ResponseEntity(
r.body, body,
HttpHeaders().also { h -> r.headers.entries.forEach { h.add(it.key, it.value.joinToString { it }) } }, HttpHeaders().also { h -> headers.entries.forEach { e -> h.add(e.key, e.value.joinToString { it }) } },
HttpStatusCode.valueOf(r.operation.statusCode) HttpStatusCode.valueOf(operation.statusCode)
) )
}
} }
companion object : KLogging() companion object : KLogging()

View file

@ -14,7 +14,7 @@ import java.util.function.Consumer
@Component @Component
class MediaDB( class NextcloudDB(
private val nextcloudConfig: NextcloudConfigDiscovery, private val nextcloudConfig: NextcloudConfigDiscovery,
private val mimetypeRepository: MimetypeRepository, private val mimetypeRepository: MimetypeRepository,
private val filecacheRepository: FilecacheRepository, private val filecacheRepository: FilecacheRepository,

View file

@ -1,25 +1,26 @@
package net.schowek.nextclouddlna.nextcloud.content package net.schowek.nextclouddlna.nextcloud.content
import jakarta.annotation.PostConstruct
import mu.KLogging import mu.KLogging
import net.schowek.nextclouddlna.nextcloud.MediaDB import net.schowek.nextclouddlna.nextcloud.NextcloudDB
import org.springframework.scheduling.annotation.Scheduled import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.Instant
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.regex.Pattern import java.util.regex.Pattern
@Component @Component
class ContentTreeProvider( class ContentTreeProvider(
private val mediaDB: MediaDB private val nextcloudDB: NextcloudDB
) { ) {
private var tree = buildContentTree() private var tree = buildContentTree()
private var lastMTime = 0L private var lastMTime = 0L
@Scheduled(fixedDelay = 1000 * 60, initialDelay = 1000 * 60) @Scheduled(fixedDelay = REBUILD_TREE_DELAY_IN_MS, initialDelay = REBUILD_TREE_INIT_DELAY_IN_MS)
final fun rebuildTree() { final fun rebuildTree() {
val maxMtime: Long = mediaDB.maxMtime() val maxMtime: Long = nextcloudDB.maxMtime()
if (lastMTime < maxMtime) { val now = Instant.now().epochSecond
if (lastMTime < maxMtime || lastMTime + MAX_REBUILD_OFFSET_IN_S > now) {
logger.info("ContentTree seems to be outdated - Loading...") logger.info("ContentTree seems to be outdated - Loading...")
this.tree = buildContentTree() this.tree = buildContentTree()
lastMTime = maxMtime lastMTime = maxMtime
@ -30,12 +31,12 @@ class ContentTreeProvider(
val tree = ContentTree() val tree = ContentTree()
val root = ContentNode(0, -1, "ROOT") val root = ContentNode(0, -1, "ROOT")
tree.addNode(root) tree.addNode(root)
mediaDB.mainNodes().forEach { n -> nextcloudDB.mainNodes().forEach { n ->
root.addNode(n) root.addNode(n)
fillNode(n, tree) fillNode(n, tree)
} }
logger.info("Getting content from group folders...") logger.info("Getting content from group folders...")
mediaDB.groupFolders().forEach { n -> nextcloudDB.groupFolders().forEach { n ->
logger.info(" Group folder found: {}", n.name) logger.info(" Group folder found: {}", n.name)
root.addNode(n) root.addNode(n)
fillNode(n, tree) fillNode(n, tree)
@ -48,7 +49,7 @@ class ContentTreeProvider(
private fun loadThumbnails(tree: ContentTree) { private fun loadThumbnails(tree: ContentTree) {
logger.info("Loading thumbnails...") logger.info("Loading thumbnails...")
val thumbsCount = AtomicInteger() val thumbsCount = AtomicInteger()
mediaDB.processThumbnails { thumb -> nextcloudDB.processThumbnails { thumb ->
val id = getItemIdForThumbnail(thumb) val id = getItemIdForThumbnail(thumb)
if (id != null) { if (id != null) {
val item = tree.getItem(id) val item = tree.getItem(id)
@ -72,7 +73,7 @@ class ContentTreeProvider(
} }
private fun fillNode(node: ContentNode, tree: ContentTree) { private fun fillNode(node: ContentNode, tree: ContentTree) {
mediaDB.appendChildren(node) nextcloudDB.appendChildren(node)
tree.addNode(node) tree.addNode(node)
node.getItems().forEach { item -> node.getItems().forEach { item ->
logger.debug("Adding item[{}]: " + item.path, item.id) logger.debug("Adding item[{}]: " + item.path, item.id)
@ -87,7 +88,11 @@ class ContentTreeProvider(
fun getItem(id: String): ContentItem? = tree.getItem(id) fun getItem(id: String): ContentItem? = tree.getItem(id)
fun getNode(id: String): ContentNode? = tree.getNode(id) fun getNode(id: String): ContentNode? = tree.getNode(id)
companion object : KLogging() companion object : KLogging() {
const val REBUILD_TREE_DELAY_IN_MS = 1000 * 60L // 1m
const val REBUILD_TREE_INIT_DELAY_IN_MS = 1000 * 60L // 1m
const val MAX_REBUILD_OFFSET_IN_S = 60 * 60 * 12L // 12h
}
} }

View file

@ -12,8 +12,9 @@ class StreamRequestMapper {
fun map(request: HttpServletRequest): StreamRequestMessage { fun map(request: HttpServletRequest): StreamRequestMessage {
val requestMessage = StreamRequestMessage( val requestMessage = StreamRequestMessage(
UpnpRequest.Method.getByHttpName(request.method), UpnpRequest.Method.getByHttpName(request.method),
URI(request.requestURI) URI(request.requestURI),
// TODO put request.inputStream.readBytes() here // TODO check if request is binary and create body as unwrapped byteArray
String(request.inputStream.readBytes())
) )
if (requestMessage.operation.method == UpnpRequest.Method.UNKNOWN) { if (requestMessage.operation.method == UpnpRequest.Method.UNKNOWN) {
logger.warn("Method not supported by UPnP stack: {}", request.method) logger.warn("Method not supported by UPnP stack: {}", request.method)
@ -22,28 +23,9 @@ class StreamRequestMapper {
requestMessage.connection = MyHttpServerConnection(request) requestMessage.connection = MyHttpServerConnection(request)
requestMessage.headers = createHeaders(request) requestMessage.headers = createHeaders(request)
setBody(request, requestMessage)
return requestMessage return requestMessage
} }
private fun setBody(
request: HttpServletRequest,
requestMessage: StreamRequestMessage
) {
val bodyBytes = request.inputStream.readBytes()
logger.debug(" Reading request body bytes: " + bodyBytes.size)
if (bodyBytes.isNotEmpty() && requestMessage.isContentTypeMissingOrText) {
logger.debug("Request contains textual entity body, converting then setting string on message")
requestMessage.setBodyCharacters(bodyBytes)
} else if (bodyBytes.isNotEmpty()) {
logger.debug("Request contains binary entity body, setting bytes on message")
requestMessage.setBody(UpnpMessage.BodyType.BYTES, bodyBytes)
} else {
logger.debug("Request did not contain entity body")
}
}
private fun createHeaders(request: HttpServletRequest): UpnpHeaders { private fun createHeaders(request: HttpServletRequest): UpnpHeaders {
val headers = mutableMapOf<String, List<String>>() val headers = mutableMapOf<String, List<String>>()
with(request.headerNames) { with(request.headerNames) {