Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upnpservice as bean #12

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {

implementation 'org.jupnp:org.jupnp:2.7.1'
implementation 'org.jupnp:org.jupnp.support:2.7.1'
implementation 'org.osgi:org.osgi.service.http:1.2.2'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
// to avoid snakeyaml-1.3 vulnerability CVE-2022-1471
implementation 'org.yaml:snakeyaml:2.2'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.schowek.nextclouddlna.controller


import net.schowek.nextclouddlna.dlna.media.MediaServer
import net.schowek.nextclouddlna.dlna.DlnaService
import net.schowek.nextclouddlna.dlna.MediaServer
import org.jupnp.support.model.DIDLObject
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
Expand All @@ -14,11 +14,14 @@ class UpnpControllerIntTest extends UpnpAwareSpecification {

@Autowired
private MediaServer mediaServer
@Autowired
private DlnaService dlnaService

def uid

def setup() {
uid = mediaServer.serviceIdentifier
dlnaService.start()
}

def "should serve icon"() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package net.schowek.nextclouddlna.dlna

import org.jupnp.UpnpService
import org.jupnp.model.message.discovery.OutgoingSearchRequest
import org.jupnp.model.message.header.HostHeader
import org.jupnp.model.message.header.MANHeader
import org.jupnp.model.message.header.STAllHeader
import org.jupnp.model.message.header.UpnpHeader
import org.jupnp.model.types.HostPort
import org.springframework.beans.factory.annotation.Autowired
import spock.util.concurrent.PollingConditions
import support.IntegrationSpecification
import support.beans.dlna.upnp.UpnpServiceConfigurationInt

import static org.jupnp.model.Constants.IPV4_UPNP_MULTICAST_GROUP
import static org.jupnp.model.Constants.UPNP_MULTICAST_PORT
import static org.jupnp.model.message.UpnpRequest.Method.MSEARCH
import static org.jupnp.model.message.header.UpnpHeader.Type.*
import static org.jupnp.model.types.NotificationSubtype.ALL
import static org.jupnp.model.types.NotificationSubtype.DISCOVER

class DlnaServiceIntTest extends IntegrationSpecification {
@Autowired
private UpnpService upnpService
@Autowired
private MediaServer mediaServer
def conditions = new PollingConditions(timeout: 1)

def "should send initial multicast Upnp datagrams on start"() {
given:
def configuration = upnpService.configuration as UpnpServiceConfigurationInt
def sut = new DlnaService(upnpService, mediaServer)

expect:
configuration.outgoingDatagramMessages == []

when:
sut.start()

then:
conditions.eventually {
assert configuration.outgoingDatagramMessages.any()
assert configuration.outgoingDatagramMessages[0].class == OutgoingSearchRequest
with(configuration.outgoingDatagramMessages[0] as OutgoingSearchRequest) {
assert it.operation.method == MSEARCH
assert it.destinationAddress == InetAddress.getByName(IPV4_UPNP_MULTICAST_GROUP)
assert it.destinationPort == UPNP_MULTICAST_PORT

assert header(it, MAN, MANHeader.class) == DISCOVER.headerString
assert header(it, ST, STAllHeader.class).headerString == ALL.headerString
assert header(it, HOST, HostHeader.class) == new HostPort(IPV4_UPNP_MULTICAST_GROUP, UPNP_MULTICAST_PORT)
}
}
}

def <T> T header(OutgoingSearchRequest request, UpnpHeader.Type type, Class<? extends UpnpHeader<T>> clazz) {
return clazz.cast(request.headers.get(type).find()).value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootContextLoader
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.context.annotation.Import
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import support.beans.TestConfig

import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT
import static org.springframework.test.annotation.DirtiesContext.ClassMode.*

@ContextConfiguration(loader = SpringBootContextLoader, classes = NextcloudDLNAApp.class)
@SpringBootTest(webEnvironment = DEFINED_PORT)
@ActiveProfiles("integration")
@Import(TestConfig.class)
@DirtiesContext(classMode = AFTER_CLASS)
class IntegrationSpecification extends Specification {
@Autowired
private TestRestTemplate restTemplate
Expand All @@ -27,6 +33,6 @@ class IntegrationSpecification extends Specification {
private ServerInfoProvider serverInfoProvider

protected String urlWithPort(String uri = "") {
return "http://localhost:" + serverInfoProvider.port + uri;
return "http://" + serverInfoProvider.host + ":" + serverInfoProvider.port + uri;
}
}
7 changes: 7 additions & 0 deletions src/integration/groovy/support/beans/TestConfig.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package support.beans

import org.springframework.context.annotation.ComponentScan

@ComponentScan(["support", "net.schowek.nextclouddlna"])
class TestConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package support.beans.dlna.upnp

import org.jupnp.DefaultUpnpServiceConfiguration
import org.jupnp.model.message.OutgoingDatagramMessage
import org.jupnp.transport.impl.DatagramIOConfigurationImpl
import org.jupnp.transport.impl.DatagramIOImpl
import org.jupnp.transport.spi.DatagramIO
import org.jupnp.transport.spi.NetworkAddressFactory
import org.jupnp.transport.spi.StreamClient
import org.jupnp.transport.spi.StreamServer
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component

@Component
@Profile("integration")
class UpnpServiceConfigurationInt extends DefaultUpnpServiceConfiguration {
List<OutgoingDatagramMessage> outgoingDatagramMessages = new ArrayList<>()

@Override
public StreamClient createStreamClient() {
return null
}

@Override
public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory) {
return null
}

@Override
public DatagramIO createDatagramIO(NetworkAddressFactory networkAddressFactory) {
return new MockDatagramIO(this, new DatagramIOConfigurationImpl())
}

private void onOutgoingDatagramMessage(OutgoingDatagramMessage message) {
outgoingDatagramMessages.add(message)
}

class MockDatagramIO extends DatagramIOImpl {
private final UpnpServiceConfigurationInt upnpServiceConfiguration

MockDatagramIO(UpnpServiceConfigurationInt upnpServiceConfiguration, DatagramIOConfigurationImpl configuration) {
super(configuration)
this.upnpServiceConfiguration = upnpServiceConfiguration
}

@Override
void send(OutgoingDatagramMessage message) {
upnpServiceConfiguration.onOutgoingDatagramMessage(message)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package net.schowek.nextclouddlna.nextcloud.config

package support.beans.nextcloud.config

import net.schowek.nextclouddlna.nextcloud.config.NextcloudAppPathProvider
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.schowek.nextclouddlna.util
package support.beans.util

import net.schowek.nextclouddlna.util.ServerInfoProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.util
package support.beans.util

import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.server.ConfigurableWebServerFactory
Expand Down
90 changes: 0 additions & 90 deletions src/main/kotlin/net/schowek/nextclouddlna/DllnaService.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package net.schowek.nextclouddlna.controller

import jakarta.servlet.http.HttpServletRequest
import mu.KLogging
import net.schowek.nextclouddlna.DlnaService
import net.schowek.nextclouddlna.dlna.DlnaService
import net.schowek.nextclouddlna.dlna.StreamMessageMapper
import net.schowek.nextclouddlna.dlna.media.MediaServer
import net.schowek.nextclouddlna.dlna.MediaServer
import org.springframework.core.io.InputStreamResource
import org.springframework.core.io.Resource
import org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE
Expand Down
48 changes: 48 additions & 0 deletions src/main/kotlin/net/schowek/nextclouddlna/dlna/DlnaService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.schowek.nextclouddlna.dlna

import jakarta.annotation.PreDestroy
import mu.KLogging
import org.jupnp.UpnpService
import org.jupnp.model.message.StreamRequestMessage
import org.jupnp.model.message.StreamResponseMessage
import org.jupnp.model.message.UpnpResponse
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component


@Component
class DlnaService(
private val upnpService: UpnpService,
private val mediaServer: MediaServer
) {
fun start() {
upnpService.startup()
upnpService.registry.addDevice(mediaServer.device)
}

@EventListener(condition = "[email protected]('integration')")
fun handleContextRefresh(event: ContextRefreshedEvent) {
start()
}

@PreDestroy
fun destroy() {
upnpService.shutdown()
}

fun processRequest(requestMsg: StreamRequestMessage): StreamResponseMessage {
logger.debug { "Processing $requestMsg" }
return with(upnpService.protocolFactory.createReceivingSync(requestMsg)) {
run()
outputMessage
?: StreamResponseMessage(UpnpResponse.Status.NOT_FOUND).also {
logger.warn { "Could not get response for ${requestMsg.operation.method} ${requestMsg}" }
}
}.also {
logger.debug { "Response: ${it.operation.statusCode} ${it.body}" }
}
}

companion object : KLogging()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.media
package net.schowek.nextclouddlna.dlna

import mu.KLogging
import net.schowek.nextclouddlna.util.ExternalUrls
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.schowek.nextclouddlna.dlna.upnp

import org.jupnp.UpnpServiceConfiguration
import org.jupnp.UpnpServiceImpl
import org.springframework.stereotype.Component

@Component
class MyUpnpService(
upnpServiceConfiguration: UpnpServiceConfiguration
) : UpnpServiceImpl(upnpServiceConfiguration)
Loading