refactoring
This commit is contained in:
parent
f57e5b304e
commit
eef25cd418
15 changed files with 151 additions and 426 deletions
|
@ -71,39 +71,27 @@ class DlnaService(
|
|||
inner class MyUpnpService(
|
||||
configuration: UpnpServiceConfiguration
|
||||
) : UpnpServiceImpl(configuration) {
|
||||
override fun createRegistry(pf: ProtocolFactory): Registry {
|
||||
return RegistryImplWithOverrides(this)
|
||||
}
|
||||
override fun createRegistry(pf: ProtocolFactory) =
|
||||
RegistryImplWithOverrides(this)
|
||||
}
|
||||
|
||||
private inner class MyUpnpServiceConfiguration : DefaultUpnpServiceConfiguration(serverInfoProvider.port) {
|
||||
override fun createStreamClient(): StreamClient<*> {
|
||||
return ApacheStreamClient(
|
||||
ApacheStreamClientConfiguration(syncProtocolExecutorService)
|
||||
)
|
||||
}
|
||||
override fun createStreamClient() =
|
||||
ApacheStreamClient(ApacheStreamClientConfiguration(syncProtocolExecutorService))
|
||||
|
||||
override fun createStreamServer(networkAddressFactory: NetworkAddressFactory): StreamServer<*> {
|
||||
return MyStreamServerImpl(
|
||||
MyStreamServerConfiguration(networkAddressFactory.streamListenPort)
|
||||
)
|
||||
}
|
||||
override fun createStreamServer(networkAddressFactory: NetworkAddressFactory) =
|
||||
MyStreamServerImpl(MyStreamServerConfiguration(networkAddressFactory.streamListenPort))
|
||||
|
||||
override fun createNetworkAddressFactory(
|
||||
streamListenPort: Int,
|
||||
multicastResponsePort: Int
|
||||
): NetworkAddressFactory {
|
||||
return MyNetworkAddressFactory(streamListenPort, multicastResponsePort)
|
||||
}
|
||||
override fun createNetworkAddressFactory(streamListenPort: Int, multicastResponsePort: Int) =
|
||||
MyNetworkAddressFactory(streamListenPort, multicastResponsePort)
|
||||
}
|
||||
|
||||
inner class MyNetworkAddressFactory(
|
||||
streamListenPort: Int,
|
||||
multicastResponsePort: Int
|
||||
) : NetworkAddressFactoryImpl(streamListenPort, multicastResponsePort) {
|
||||
override fun isUsableAddress(iface: NetworkInterface, address: InetAddress): Boolean {
|
||||
return addressesToBind.contains(address)
|
||||
}
|
||||
override fun isUsableAddress(iface: NetworkInterface, address: InetAddress) =
|
||||
addressesToBind.contains(address)
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.util.*
|
|||
@RestController
|
||||
class ContentController(
|
||||
private val contentTreeProvider: ContentTreeProvider
|
||||
) {
|
||||
) {
|
||||
@RequestMapping(method = [RequestMethod.GET, RequestMethod.HEAD], value = ["/c/{id}"])
|
||||
@ResponseBody
|
||||
fun getResource(
|
||||
|
@ -33,20 +33,20 @@ class ContentController(
|
|||
request: HttpServletRequest,
|
||||
response: HttpServletResponse
|
||||
): ResponseEntity<FileSystemResource> {
|
||||
val item = contentTreeProvider.getItem(id)
|
||||
if (item == null) {
|
||||
return contentTreeProvider.getItem(id)?.let { item ->
|
||||
if (!request.getHeaders("range").hasMoreElements()) {
|
||||
logger.info("Serving content {} {}", request.method, id)
|
||||
}
|
||||
val fileSystemResource = FileSystemResource(item.path)
|
||||
response.addHeader("Content-Type", item.format.mime)
|
||||
response.addHeader("contentFeatures.dlna.org", makeProtocolInfo(item.format).toString())
|
||||
response.addHeader("transferMode.dlna.org", "Streaming")
|
||||
response.addHeader("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*")
|
||||
ResponseEntity(fileSystemResource, HttpStatus.OK)
|
||||
} ?: let {
|
||||
logger.info("Could not find item id: {}", id)
|
||||
return ResponseEntity(HttpStatus.NOT_FOUND)
|
||||
ResponseEntity(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
if (!request.getHeaders("range").hasMoreElements()) {
|
||||
logger.info("Serving content {} {}", request.method, id)
|
||||
}
|
||||
val fileSystemResource = FileSystemResource(item.path)
|
||||
response.addHeader("Content-Type", item.format.mime)
|
||||
response.addHeader("contentFeatures.dlna.org", makeProtocolInfo(item.format).toString())
|
||||
response.addHeader("transferMode.dlna.org", "Streaming")
|
||||
response.addHeader("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*")
|
||||
return ResponseEntity(fileSystemResource, HttpStatus.OK)
|
||||
}
|
||||
|
||||
@RequestMapping(method = [RequestMethod.GET], value = ["/rebuild"])
|
||||
|
@ -57,9 +57,7 @@ class ContentController(
|
|||
}
|
||||
|
||||
private fun makeProtocolInfo(mediaFormat: MediaFormat): DLNAProtocolInfo {
|
||||
val attributes = EnumMap<Type, DLNAAttribute<*>>(
|
||||
Type::class.java
|
||||
)
|
||||
val attributes = EnumMap<Type, DLNAAttribute<*>>(Type::class.java)
|
||||
if (mediaFormat.contentGroup === VIDEO) {
|
||||
attributes[DLNA_ORG_PN] = DLNAProfileAttribute(AVC_MP4_LPCM)
|
||||
attributes[DLNA_ORG_OP] = DLNAOperationsAttribute(RANGE)
|
||||
|
@ -73,6 +71,7 @@ class ContentController(
|
|||
}
|
||||
return DLNAProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mediaFormat.mime, attributes)
|
||||
}
|
||||
companion object: KLogging()
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
||||
|
|
|
@ -34,11 +34,5 @@ class RegistryImplWithOverrides(
|
|||
} else super.getResource(pathQuery)
|
||||
}
|
||||
|
||||
fun maintain() {
|
||||
logger.info { "REGISTRY MAINTAIN" }
|
||||
Thread(registryMaintainer).start()
|
||||
logger.info { "REGISTRY MAINTAIN DONE" }
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ class StreamRequestMapper {
|
|||
throw RuntimeException("Method not supported: {}" + request.method)
|
||||
}
|
||||
|
||||
requestMessage.connection = MyHttpServerConnection(request)
|
||||
requestMessage.headers = createHeaders(request)
|
||||
return requestMessage
|
||||
}
|
||||
|
@ -38,18 +37,6 @@ class StreamRequestMapper {
|
|||
return UpnpHeaders(headers)
|
||||
}
|
||||
|
||||
inner class MyHttpServerConnection(
|
||||
private val request: HttpServletRequest
|
||||
) : Connection {
|
||||
override fun isOpen() = true
|
||||
|
||||
override fun getRemoteAddress(): InetAddress? =
|
||||
request.remoteAddr?.let { InetAddress.getByName(request.remoteAddr) }
|
||||
|
||||
override fun getLocalAddress(): InetAddress? =
|
||||
request.localAddr?.let { InetAddress.getByName(request.localAddr) }
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
||||
|
|
|
@ -43,27 +43,29 @@ class ContentDirectoryService(
|
|||
): BrowseResult {
|
||||
val startTime = System.nanoTime()
|
||||
return try {
|
||||
// TODO optimize:
|
||||
// * checking if it's node or item before fetching them in two queries
|
||||
// * not fetching children for METADATA browse flag
|
||||
val node = contentTreeProvider.getNode(objectID)
|
||||
if (node != null) {
|
||||
contentTreeProvider.getNode(objectID)?.let { node ->
|
||||
if (browseFlag == METADATA) {
|
||||
val didl = DIDLContent()
|
||||
didl.addContainer(nodeConverter.makeContainerWithoutSubContainers(node))
|
||||
return BrowseResult(DIDLParser().generate(didl), 1, 1)
|
||||
val result = DIDLParser().generate(
|
||||
DIDLContent().also {
|
||||
it.addContainer(nodeConverter.makeContainerWithoutSubContainers(node))
|
||||
}
|
||||
)
|
||||
return BrowseResult(result, 1, 1)
|
||||
}
|
||||
val containers: List<Container> = nodeConverter.makeSubContainersWithoutTheirSubContainers(node)
|
||||
val items: List<Item> = nodeConverter.makeItems(node)
|
||||
return toRangedResult(containers, items, firstResult, maxResults)
|
||||
}
|
||||
val item = contentTreeProvider.getItem(objectID)
|
||||
if (item != null) {
|
||||
val didl = DIDLContent()
|
||||
didl.addItem(nodeConverter.makeItem(item))
|
||||
val result = DIDLParser().generate(didl)
|
||||
|
||||
contentTreeProvider.getItem(objectID)?.let { item ->
|
||||
val result = DIDLParser().generate(
|
||||
DIDLContent().also {
|
||||
it.addItem(nodeConverter.makeItem(item))
|
||||
}
|
||||
)
|
||||
return BrowseResult(result, 1, 1)
|
||||
}
|
||||
|
||||
BrowseResult(DIDLParser().generate(DIDLContent()), 0, 0)
|
||||
} catch (e: Exception) {
|
||||
logger.warn(
|
||||
|
@ -78,33 +80,32 @@ class ContentDirectoryService(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : KLogging() {
|
||||
@Throws(Exception::class)
|
||||
private fun toRangedResult(
|
||||
containers: List<Container>,
|
||||
items: List<Item>,
|
||||
firstResult: Long,
|
||||
maxResultsParam: Long
|
||||
): BrowseResult {
|
||||
val maxResults = if (maxResultsParam == 0L) (containers.size + items.size).toLong() else maxResultsParam
|
||||
val didl = DIDLContent()
|
||||
if (containers.size > firstResult) {
|
||||
val from = firstResult.toInt()
|
||||
val to = min((firstResult + maxResults).toInt(), containers.size)
|
||||
didl.containers = containers.subList(from, to)
|
||||
}
|
||||
if (didl.containers.size < maxResults) {
|
||||
val from = max(firstResult - containers.size, 0).toInt()
|
||||
val to = min(items.size, from + (maxResults - didl.containers.size).toInt())
|
||||
didl.items = items.subList(from, to)
|
||||
}
|
||||
return BrowseResult(
|
||||
DIDLParser().generate(didl),
|
||||
(didl.containers.size + didl.items.size).toLong(),
|
||||
(containers.size + items.size).toLong()
|
||||
)
|
||||
@Throws(Exception::class)
|
||||
private fun toRangedResult(
|
||||
containers: List<Container>,
|
||||
items: List<Item>,
|
||||
firstResult: Long,
|
||||
maxResultsParam: Long
|
||||
): BrowseResult {
|
||||
val maxResults = if (maxResultsParam == 0L) (containers.size + items.size).toLong() else maxResultsParam
|
||||
val didl = DIDLContent()
|
||||
if (containers.size > firstResult) {
|
||||
val from = firstResult.toInt()
|
||||
val to = min((firstResult + maxResults).toInt(), containers.size)
|
||||
didl.containers = containers.subList(from, to)
|
||||
}
|
||||
if (didl.containers.size < maxResults) {
|
||||
val from = max(firstResult - containers.size, 0).toInt()
|
||||
val to = min(items.size, from + (maxResults - didl.containers.size).toInt())
|
||||
didl.items = items.subList(from, to)
|
||||
}
|
||||
return BrowseResult(
|
||||
DIDLParser().generate(didl),
|
||||
(didl.containers.size + didl.items.size).toLong(),
|
||||
(containers.size + items.size).toLong()
|
||||
)
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package net.schowek.nextclouddlna.dlna.media
|
||||
|
||||
import jakarta.annotation.PostConstruct
|
||||
import mu.KLogging
|
||||
import net.schowek.nextclouddlna.util.ExternalUrls
|
||||
import org.jupnp.model.meta.*
|
||||
|
@ -10,7 +9,6 @@ import org.springframework.beans.factory.annotation.Qualifier
|
|||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
@Component
|
||||
|
@ -24,7 +22,7 @@ class MediaServer(
|
|||
externalUrls: ExternalUrls
|
||||
) {
|
||||
final val device = LocalDevice(
|
||||
DeviceIdentity(uniqueSystemIdentifier("Nextcloud-DLNA-MediaServer"), 300),
|
||||
DeviceIdentity(uniqueSystemIdentifier("Nextcloud-DLNA-MediaServer"), ADVERTISEMENT_AGE_IN_S),
|
||||
UDADeviceType(DEVICE_TYPE, VERSION),
|
||||
DeviceDetails(friendlyName, externalUrls.selfURI),
|
||||
createDeviceIcon(),
|
||||
|
@ -36,24 +34,22 @@ class MediaServer(
|
|||
}
|
||||
|
||||
companion object : KLogging() {
|
||||
const val ICON_FILENAME = "icon.png"
|
||||
private const val DEVICE_TYPE = "MediaServer"
|
||||
private const val VERSION = 1
|
||||
const val ICON_FILENAME = "icon.png"
|
||||
private const val ADVERTISEMENT_AGE_IN_S = 60
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun createDeviceIcon(): Icon {
|
||||
val resource = iconResource()
|
||||
return resource.use { res ->
|
||||
fun createDeviceIcon() = with(iconResource()) {
|
||||
this.use { res ->
|
||||
Icon("image/png", 48, 48, 8, ICON_FILENAME, res).also {
|
||||
it.validate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun iconResource(): InputStream {
|
||||
return MediaServer::class.java.getResourceAsStream("/$ICON_FILENAME")
|
||||
?: throw IllegalStateException("Icon not found.")
|
||||
}
|
||||
fun iconResource() = MediaServer::class.java.getResourceAsStream("/$ICON_FILENAME")
|
||||
?: throw IllegalStateException("Icon not found.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,24 +31,20 @@ class NodeConverter(
|
|||
val externalUrls: ExternalUrls
|
||||
) {
|
||||
fun makeSubContainersWithoutTheirSubContainers(n: ContentNode) =
|
||||
n.getNodes().map { node -> makeContainerWithoutSubContainers(node) }.toList()
|
||||
n.nodes.map { node -> makeContainerWithoutSubContainers(node) }.toList()
|
||||
|
||||
fun makeContainerWithoutSubContainers(n: ContentNode): Container {
|
||||
val c = Container()
|
||||
c.setClazz(DIDLObject.Class("object.container"))
|
||||
c.setId("${n.id}")
|
||||
c.setParentID("${n.parentId}")
|
||||
c.setTitle(n.name)
|
||||
fun makeContainerWithoutSubContainers(n: ContentNode) = Container().also { c ->
|
||||
c.clazz = DIDLObject.Class("object.container")
|
||||
c.id = "${n.id}"
|
||||
c.parentID = "${n.parentId}"
|
||||
c.title = n.name
|
||||
c.childCount = n.nodeAndItemCount
|
||||
c.setRestricted(true)
|
||||
c.setWriteStatus(NOT_WRITABLE)
|
||||
c.isRestricted = true
|
||||
c.writeStatus = NOT_WRITABLE
|
||||
c.isSearchable = true
|
||||
return c
|
||||
}
|
||||
|
||||
fun makeItems(n: ContentNode): List<Item> =
|
||||
n.getItems().map { item -> makeItem(item) }.toList()
|
||||
|
||||
fun makeItems(n: ContentNode): List<Item> = n.items.map { item -> makeItem(item) }.toList()
|
||||
|
||||
fun makeItem(c: ContentItem): Item {
|
||||
val res = Res(c.format.asMimetype(), c.fileLength, externalUrls.contentUrl(c.id))
|
||||
|
@ -58,10 +54,14 @@ class NodeConverter(
|
|||
AUDIO -> AudioItem("${c.id}", "${c.parentId}", c.name, "", res)
|
||||
else -> throw IllegalArgumentException()
|
||||
}.also {
|
||||
val t = c.thumb
|
||||
if (t != null) {
|
||||
val thumbUri: String = externalUrls.contentUrl(t.id)
|
||||
it.addResource(Res(makeProtocolInfo(t.format.asMimetype()), t.fileLength, thumbUri))
|
||||
c.thumb?.let { t ->
|
||||
it.addResource(
|
||||
Res(
|
||||
makeProtocolInfo(t.format.asMimetype()),
|
||||
t.fileLength,
|
||||
externalUrls.contentUrl(t.id)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
package net.schowek.nextclouddlna.dlna.transport
|
||||
|
||||
import mu.KLogging
|
||||
import org.apache.http.*
|
||||
import org.apache.http.HttpMessage
|
||||
import org.apache.http.HttpVersion
|
||||
import org.apache.http.NoHttpResponseException
|
||||
import org.apache.http.client.ResponseHandler
|
||||
import org.apache.http.client.config.RequestConfig
|
||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase
|
||||
import org.apache.http.client.methods.HttpGet
|
||||
import org.apache.http.client.methods.HttpPost
|
||||
import org.apache.http.client.methods.HttpRequestBase
|
||||
import org.apache.http.config.ConnectionConfig
|
||||
import org.apache.http.config.RegistryBuilder
|
||||
import org.apache.http.conn.socket.ConnectionSocketFactory
|
||||
import org.apache.http.conn.socket.PlainConnectionSocketFactory
|
||||
import org.apache.http.entity.ByteArrayEntity
|
||||
import org.apache.http.entity.StringEntity
|
||||
import org.apache.http.impl.client.CloseableHttpClient
|
||||
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler
|
||||
import org.apache.http.impl.client.HttpClients
|
||||
|
@ -24,7 +22,6 @@ import org.jupnp.model.message.*
|
|||
import org.jupnp.model.message.header.UpnpHeader
|
||||
import org.jupnp.transport.spi.AbstractStreamClient
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.UnsupportedCharsetException
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
|
||||
|
@ -77,42 +74,17 @@ class ApacheStreamClient(
|
|||
|
||||
override fun createRequest(requestMessage: StreamRequestMessage): HttpRequestBase {
|
||||
val requestOperation = requestMessage.operation
|
||||
val request: HttpRequestBase
|
||||
when (requestOperation.method) {
|
||||
UpnpRequest.Method.GET -> {
|
||||
request = HttpGet(requestOperation.uri)
|
||||
}
|
||||
|
||||
val request = when (requestOperation.method) {
|
||||
UpnpRequest.Method.GET,
|
||||
UpnpRequest.Method.NOTIFY,
|
||||
UpnpRequest.Method.POST,
|
||||
UpnpRequest.Method.UNSUBSCRIBE,
|
||||
UpnpRequest.Method.SUBSCRIBE -> {
|
||||
request = object : HttpGet(requestOperation.uri) {
|
||||
override fun getMethod(): String {
|
||||
return UpnpRequest.Method.SUBSCRIBE.httpName
|
||||
}
|
||||
object : HttpGet(requestOperation.uri) {
|
||||
override fun getMethod() = requestOperation.method.httpName
|
||||
}
|
||||
}
|
||||
|
||||
UpnpRequest.Method.UNSUBSCRIBE -> {
|
||||
request = object : HttpGet(requestOperation.uri) {
|
||||
override fun getMethod(): String {
|
||||
return UpnpRequest.Method.UNSUBSCRIBE.httpName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpnpRequest.Method.POST -> {
|
||||
request = HttpPost(requestOperation.uri)
|
||||
(request as HttpEntityEnclosingRequestBase).entity = createHttpRequestEntity(requestMessage)
|
||||
}
|
||||
|
||||
UpnpRequest.Method.NOTIFY -> {
|
||||
request = object : HttpPost(requestOperation.uri) {
|
||||
override fun getMethod(): String {
|
||||
return UpnpRequest.Method.NOTIFY.httpName
|
||||
}
|
||||
}
|
||||
(request as HttpEntityEnclosingRequestBase).entity = createHttpRequestEntity(requestMessage)
|
||||
}
|
||||
|
||||
else -> throw RuntimeException("Unknown HTTP method: " + requestOperation.httpMethodName)
|
||||
}
|
||||
|
||||
|
@ -143,9 +115,6 @@ class ApacheStreamClient(
|
|||
): Callable<StreamResponseMessage> {
|
||||
return Callable<StreamResponseMessage> {
|
||||
logger.trace("Sending HTTP request: $requestMessage")
|
||||
if (logger.isTraceEnabled) {
|
||||
StreamsLoggerHelper.logStreamClientRequestMessage(requestMessage)
|
||||
}
|
||||
httpClient.execute<StreamResponseMessage>(request, createResponseHandler(requestMessage))
|
||||
}
|
||||
}
|
||||
|
@ -172,79 +141,50 @@ class ApacheStreamClient(
|
|||
clientConnectionManager.shutdown()
|
||||
}
|
||||
|
||||
private fun createHttpRequestEntity(upnpMessage: UpnpMessage<*>): HttpEntity {
|
||||
return if (upnpMessage.bodyType == UpnpMessage.BodyType.BYTES) {
|
||||
logger.trace("Preparing HTTP request entity as byte[]")
|
||||
ByteArrayEntity(upnpMessage.bodyBytes)
|
||||
} else {
|
||||
logger.trace("Preparing HTTP request entity as string")
|
||||
var charset = upnpMessage.contentTypeCharset
|
||||
if (charset == null) {
|
||||
charset = "UTF-8"
|
||||
}
|
||||
try {
|
||||
StringEntity(upnpMessage.bodyString, charset)
|
||||
} catch (ex: UnsupportedCharsetException) {
|
||||
logger.trace("HTTP request does not support charset: {}", charset)
|
||||
throw RuntimeException(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createResponseHandler(requestMessage: StreamRequestMessage?): ResponseHandler<StreamResponseMessage> {
|
||||
return ResponseHandler<StreamResponseMessage> { httpResponse: HttpResponse ->
|
||||
val statusLine = httpResponse.statusLine
|
||||
return ResponseHandler<StreamResponseMessage> { response ->
|
||||
val statusLine = response.statusLine
|
||||
logger.trace("Received HTTP response: $statusLine")
|
||||
|
||||
// Status
|
||||
val responseOperation = UpnpResponse(statusLine.statusCode, statusLine.reasonPhrase)
|
||||
|
||||
// Message
|
||||
val responseMessage = StreamResponseMessage(responseOperation)
|
||||
|
||||
// Headers
|
||||
responseMessage.headers = UpnpHeaders(getHeaders(httpResponse))
|
||||
|
||||
responseMessage.headers = UpnpHeaders(getHeaders(response))
|
||||
// Body
|
||||
val entity = httpResponse.entity
|
||||
if (entity == null || entity.contentLength == 0L) {
|
||||
response.entity?.let { entity ->
|
||||
val data = EntityUtils.toByteArray(entity)
|
||||
if (data != null) {
|
||||
if (responseMessage.isContentTypeMissingOrText) {
|
||||
logger.trace("HTTP response message contains text entity")
|
||||
responseMessage.setBodyCharacters(data)
|
||||
} else {
|
||||
logger.trace("HTTP response message contains binary entity")
|
||||
responseMessage.setBody(UpnpMessage.BodyType.BYTES, data)
|
||||
}
|
||||
} else {
|
||||
logger.trace("HTTP response message has no entity")
|
||||
}
|
||||
responseMessage
|
||||
} ?: let {
|
||||
logger.trace("HTTP response message has no entity")
|
||||
return@ResponseHandler responseMessage
|
||||
}
|
||||
val data = EntityUtils.toByteArray(entity)
|
||||
if (data != null) {
|
||||
if (responseMessage.isContentTypeMissingOrText) {
|
||||
logger.trace("HTTP response message contains text entity")
|
||||
responseMessage.setBodyCharacters(data)
|
||||
} else {
|
||||
logger.trace("HTTP response message contains binary entity")
|
||||
responseMessage.setBody(UpnpMessage.BodyType.BYTES, data)
|
||||
}
|
||||
} else {
|
||||
logger.trace("HTTP response message has no entity")
|
||||
}
|
||||
if (logger.isTraceEnabled) {
|
||||
StreamsLoggerHelper.logStreamClientResponseMessage(responseMessage, requestMessage)
|
||||
}
|
||||
responseMessage
|
||||
}
|
||||
}
|
||||
|
||||
companion object : KLogging() {
|
||||
private fun addHeaders(httpMessage: HttpMessage, headers: Headers) {
|
||||
for ((key, value1) in headers) {
|
||||
for (value in value1) {
|
||||
httpMessage.addHeader(key, value)
|
||||
}
|
||||
}
|
||||
headers.map { header -> header.value.forEach { httpMessage.addHeader(header.key, it) } }
|
||||
}
|
||||
|
||||
private fun getHeaders(httpMessage: HttpMessage): Headers {
|
||||
val headers = Headers()
|
||||
for (header in httpMessage.allHeaders) {
|
||||
headers.add(header.name, header.value)
|
||||
return Headers().also { headers ->
|
||||
httpMessage.allHeaders.forEach {
|
||||
headers.add(it.name, it.value)
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,12 @@ import org.jupnp.transport.spi.AbstractStreamClientConfiguration
|
|||
import java.util.concurrent.ExecutorService
|
||||
|
||||
|
||||
class ApacheStreamClientConfiguration : AbstractStreamClientConfiguration {
|
||||
var maxTotalConnections = 1024
|
||||
|
||||
var maxTotalPerRoute = 100
|
||||
|
||||
var contentCharset = "UTF-8" // UDA spec says it's always UTF-8 entity content
|
||||
|
||||
constructor(timeoutExecutorService: ExecutorService?) : super(timeoutExecutorService)
|
||||
constructor(timeoutExecutorService: ExecutorService?, timeoutSeconds: Int) : super(
|
||||
timeoutExecutorService,
|
||||
timeoutSeconds
|
||||
)
|
||||
class ApacheStreamClientConfiguration(
|
||||
timeoutExecutorService: ExecutorService
|
||||
) : AbstractStreamClientConfiguration(timeoutExecutorService) {
|
||||
val maxTotalConnections = 1024
|
||||
val maxTotalPerRoute = 100
|
||||
val contentCharset = "UTF-8" // UDA spec says it's always UTF-8 entity content
|
||||
|
||||
val socketBufferSize: Int get() = -1
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@ import org.jupnp.transport.spi.StreamServerConfiguration
|
|||
class MyStreamServerConfiguration(
|
||||
private val listenPort: Int
|
||||
) : StreamServerConfiguration {
|
||||
val tcpConnectionBacklog = 0
|
||||
override fun getListenPort(): Int {
|
||||
return listenPort
|
||||
}
|
||||
override fun getListenPort() = listenPort
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package net.schowek.nextclouddlna.dlna.transport
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange
|
||||
import mu.KLogging
|
||||
import org.jupnp.transport.Router
|
||||
import org.jupnp.transport.spi.StreamServer
|
||||
|
@ -10,28 +9,16 @@ import java.net.InetAddress
|
|||
class MyStreamServerImpl(
|
||||
private val configuration: MyStreamServerConfiguration
|
||||
) : StreamServer<MyStreamServerConfiguration> {
|
||||
override fun init(bindAddress: InetAddress, router: Router) {
|
||||
}
|
||||
override fun init(bindAddress: InetAddress, router: Router) {}
|
||||
|
||||
override fun getPort(): Int {
|
||||
return configuration.listenPort;
|
||||
}
|
||||
override fun getPort() = configuration.listenPort
|
||||
|
||||
override fun getConfiguration(): MyStreamServerConfiguration {
|
||||
return configuration
|
||||
}
|
||||
override fun getConfiguration() = configuration
|
||||
|
||||
override fun run() {
|
||||
}
|
||||
override fun run() {}
|
||||
|
||||
override fun stop() {
|
||||
}
|
||||
override fun stop() {}
|
||||
|
||||
private fun isConnectionOpen(exchange: HttpExchange?): Boolean {
|
||||
logger.warn("Can't check client connection, socket access impossible on JDK webserver!")
|
||||
return true
|
||||
}
|
||||
|
||||
companion object: KLogging()
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
package net.schowek.nextclouddlna.dlna.transport
|
||||
|
||||
import io.micrometer.common.util.StringUtils
|
||||
import mu.KLogging
|
||||
import org.jupnp.model.message.StreamRequestMessage
|
||||
import org.jupnp.model.message.StreamResponseMessage
|
||||
import org.jupnp.model.message.UpnpMessage
|
||||
|
||||
|
||||
class StreamsLoggerHelper {
|
||||
companion object : KLogging() {
|
||||
private const val HTTPSERVER_REQUEST_BEGIN =
|
||||
"================================== HTTPSERVER REQUEST BEGIN ====================================="
|
||||
private const val HTTPSERVER_REQUEST_END =
|
||||
"================================== HTTPSERVER REQUEST END ======================================="
|
||||
private const val HTTPSERVER_RESPONSE_BEGIN =
|
||||
"================================== HTTPSERVER RESPONSE BEGIN ===================================="
|
||||
private const val HTTPSERVER_RESPONSE_END =
|
||||
"================================== HTTPSERVER RESPONSE END ======================================"
|
||||
private const val HTTPCLIENT_REQUEST_BEGIN =
|
||||
"==================================== HTTPCLIENT REQUEST BEGIN ===================================="
|
||||
private const val HTTPCLIENT_REQUEST_END =
|
||||
"==================================== HTTPCLIENT REQUEST END ======================================"
|
||||
private const val HTTPCLIENT_RESPONSE_BEGIN =
|
||||
"==================================== HTTPCLIENT RESPONSE BEGIN ==================================="
|
||||
private const val HTTPCLIENT_RESPONSE_END =
|
||||
"==================================== HTTPCLIENT RESPONSE END ====================================="
|
||||
|
||||
fun logStreamServerRequestMessage(requestMessage: StreamRequestMessage) {
|
||||
val formattedRequest = getFormattedRequest(requestMessage)
|
||||
val formattedHeaders = getFormattedHeaders(requestMessage)
|
||||
val formattedBody = getFormattedBody(requestMessage)
|
||||
logger.trace(
|
||||
"Received a request from {}:\n{}\n{}{}{}{}",
|
||||
requestMessage.connection.remoteAddress.hostAddress,
|
||||
HTTPSERVER_REQUEST_BEGIN,
|
||||
formattedRequest,
|
||||
formattedHeaders,
|
||||
formattedBody,
|
||||
HTTPSERVER_REQUEST_END
|
||||
)
|
||||
}
|
||||
|
||||
fun logStreamServerResponseMessage(
|
||||
responseMessage: StreamResponseMessage,
|
||||
requestMessage: StreamRequestMessage
|
||||
) {
|
||||
val formattedResponse = getFormattedResponse(responseMessage)
|
||||
val formattedHeaders = getFormattedHeaders(responseMessage)
|
||||
val formattedBody = getFormattedBody(responseMessage)
|
||||
logger.trace(
|
||||
"Send a response to {}:\n{}\n{}{}{}{}",
|
||||
requestMessage.connection.remoteAddress.hostAddress,
|
||||
HTTPSERVER_RESPONSE_BEGIN,
|
||||
formattedResponse,
|
||||
formattedHeaders,
|
||||
formattedBody,
|
||||
HTTPSERVER_RESPONSE_END
|
||||
)
|
||||
}
|
||||
|
||||
fun logStreamClientRequestMessage(requestMessage: StreamRequestMessage) {
|
||||
val formattedRequest = getFormattedRequest(requestMessage)
|
||||
val formattedHeaders = getFormattedHeaders(requestMessage)
|
||||
val formattedBody = getFormattedBody(requestMessage)
|
||||
logger.trace(
|
||||
"Send a request to {}:\n{}\n{}{}{}{}",
|
||||
requestMessage.uri.host,
|
||||
HTTPCLIENT_REQUEST_BEGIN,
|
||||
formattedRequest,
|
||||
formattedHeaders,
|
||||
formattedBody,
|
||||
HTTPCLIENT_REQUEST_END
|
||||
)
|
||||
}
|
||||
|
||||
fun logStreamClientResponseMessage(
|
||||
responseMessage: StreamResponseMessage,
|
||||
requestMessage: StreamRequestMessage?
|
||||
) {
|
||||
val formattedResponse = getFormattedResponse(responseMessage)
|
||||
val formattedHeaders = getFormattedHeaders(responseMessage)
|
||||
val formattedBody = getFormattedBody(responseMessage)
|
||||
logger.trace(
|
||||
"Received a response from {}:\n{}\n{}{}{}{}",
|
||||
requestMessage?.uri?.host,
|
||||
HTTPCLIENT_RESPONSE_BEGIN,
|
||||
formattedResponse,
|
||||
formattedHeaders,
|
||||
formattedBody,
|
||||
HTTPCLIENT_RESPONSE_END
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFormattedRequest(requestMessage: StreamRequestMessage): String {
|
||||
val request = StringBuilder()
|
||||
request.append(requestMessage.operation.httpMethodName).append(" ").append(requestMessage.uri.path)
|
||||
request.append(" HTTP/1.").append(requestMessage.operation.httpMinorVersion).append("\n")
|
||||
return request.toString()
|
||||
}
|
||||
|
||||
private fun getFormattedResponse(responseMessage: StreamResponseMessage): String {
|
||||
val response = StringBuilder()
|
||||
response.append("HTTP/1.").append(responseMessage.operation.httpMinorVersion)
|
||||
response.append(" ").append(responseMessage.operation.responseDetails).append("\n")
|
||||
return response.toString()
|
||||
}
|
||||
|
||||
private fun getFormattedHeaders(message: UpnpMessage<*>): String {
|
||||
val headers = StringBuilder()
|
||||
for ((key, value1) in message.headers) {
|
||||
if (StringUtils.isNotEmpty(key)) {
|
||||
for (value in value1) {
|
||||
headers.append(" ").append(key).append(": ").append(value).append("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (headers.isNotEmpty()) {
|
||||
headers.insert(0, "\nHEADER:\n")
|
||||
}
|
||||
return headers.toString()
|
||||
}
|
||||
|
||||
private fun getFormattedBody(message: UpnpMessage<*>): String {
|
||||
var formattedBody = ""
|
||||
//message.isBodyNonEmptyString throw StringIndexOutOfBoundsException if string is empty
|
||||
try {
|
||||
val bodyNonEmpty = message.body != null &&
|
||||
(message.body is String && (message.body as String).isNotEmpty()
|
||||
|| message.body is ByteArray && (message.body as ByteArray).isNotEmpty())
|
||||
if (bodyNonEmpty && message.isBodyNonEmptyString) {
|
||||
formattedBody = message.bodyString
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
formattedBody = ""
|
||||
}
|
||||
formattedBody = if (StringUtils.isNotEmpty(formattedBody)) {
|
||||
"\nCONTENT:\n$formattedBody"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
return formattedBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package net.schowek.nextclouddlna.nextcloud.content
|
||||
|
||||
class ContentItem(
|
||||
data class ContentItem(
|
||||
val id: Int,
|
||||
val parentId: Int,
|
||||
val name: String,
|
||||
|
@ -11,14 +11,13 @@ class ContentItem(
|
|||
var thumb: ContentItem? = null
|
||||
}
|
||||
|
||||
class ContentNode(
|
||||
data class ContentNode(
|
||||
val id: Int,
|
||||
val parentId: Int,
|
||||
val name: String
|
||||
) {
|
||||
private val nodes: MutableList<ContentNode> = ArrayList()
|
||||
private val items: MutableList<ContentItem> = ArrayList()
|
||||
|
||||
val nodes: MutableList<ContentNode> = ArrayList()
|
||||
val items: MutableList<ContentItem> = ArrayList()
|
||||
|
||||
private val nodeCount: Int get() = nodes.size
|
||||
private val itemCount: Int get() = items.size
|
||||
|
@ -31,13 +30,4 @@ class ContentNode(
|
|||
fun addNode(node: ContentNode) {
|
||||
nodes.add(node)
|
||||
}
|
||||
|
||||
fun getItems(): List<ContentItem> {
|
||||
return items
|
||||
}
|
||||
|
||||
fun getNodes(): List<ContentNode> {
|
||||
return nodes
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -50,14 +50,14 @@ class ContentTreeProvider(
|
|||
logger.info("Loading thumbnails...")
|
||||
val thumbsCount = AtomicInteger()
|
||||
nextcloudDB.processThumbnails { thumb ->
|
||||
val id = getItemIdForThumbnail(thumb)
|
||||
if (id != null) {
|
||||
val item = tree.getItem(id)
|
||||
if (item != null && item.thumb == null) {
|
||||
logger.debug("Adding thumbnail for item {}: {}", id, thumb)
|
||||
item.thumb = thumb
|
||||
tree.addItem(thumb)
|
||||
thumbsCount.getAndIncrement()
|
||||
getItemIdForThumbnail(thumb)?.let { id ->
|
||||
tree.getItem(id)?.let { item ->
|
||||
item.thumb ?: let {
|
||||
logger.debug("Adding thumbnail for item {}: {}", id, thumb)
|
||||
item.thumb = thumb
|
||||
tree.addItem(thumb)
|
||||
thumbsCount.getAndIncrement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,11 +75,11 @@ class ContentTreeProvider(
|
|||
private fun fillNode(node: ContentNode, tree: ContentTree) {
|
||||
nextcloudDB.appendChildren(node)
|
||||
tree.addNode(node)
|
||||
node.getItems().forEach { item ->
|
||||
node.items.forEach { item ->
|
||||
logger.debug("Adding item[{}]: " + item.path, item.id)
|
||||
tree.addItem(item)
|
||||
}
|
||||
node.getNodes().forEach { n ->
|
||||
node.nodes.forEach { n ->
|
||||
logger.debug("Adding node: " + n.name)
|
||||
fillNode(n, tree)
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ class ExternalUrls(private val serverInfoProvider: ServerInfoProvider) {
|
|||
|
||||
val selfURI : URI get() = URI(selfUriString)
|
||||
|
||||
fun contentUrl(id: Int): String {
|
||||
return "$selfUriString/c/$id"
|
||||
}
|
||||
fun contentUrl(id: Int) = "$selfUriString/c/$id"
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue