Multicast search request tested

This commit is contained in:
xis 2023-10-19 19:42:51 +02:00
parent f5a8a6e982
commit c3dadfd845
12 changed files with 109 additions and 37 deletions

View file

@ -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
@ -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"() {

View file

@ -0,0 +1,55 @@
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 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 "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:
configuration.outgoingDatagramMessages.any()
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
}
}

View file

@ -7,17 +7,20 @@ 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
@ -30,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;
}
}

View file

@ -1,6 +1,9 @@
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
@ -11,19 +14,38 @@ 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) {
//
// }
@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)
}
}
}

View file

@ -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

View file

@ -1,8 +1,7 @@
package net.schowek.nextclouddlna
package net.schowek.nextclouddlna.dlna
import jakarta.annotation.PreDestroy
import mu.KLogging
import net.schowek.nextclouddlna.dlna.media.MediaServer
import org.jupnp.UpnpService
import org.jupnp.model.message.StreamRequestMessage
import org.jupnp.model.message.StreamResponseMessage
@ -22,7 +21,7 @@ class DlnaService(
upnpService.registry.addDevice(mediaServer.device)
}
@EventListener
@EventListener(condition = "!@environment.acceptsProfiles('integration')")
fun handleContextRefresh(event: ContextRefreshedEvent) {
start()
}

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.media
package net.schowek.nextclouddlna.dlna
import mu.KLogging
import net.schowek.nextclouddlna.util.ExternalUrls

View file

@ -2,13 +2,9 @@ package net.schowek.nextclouddlna.dlna.upnp
import org.jupnp.UpnpServiceConfiguration
import org.jupnp.UpnpServiceImpl
import org.jupnp.protocol.ProtocolFactory
import org.jupnp.registry.RegistryImpl
import org.springframework.stereotype.Component
@Component
class MyUpnpService(
upnpServiceConfiguration: UpnpServiceConfiguration
) : UpnpServiceImpl(upnpServiceConfiguration) {
override fun createRegistry(pf: ProtocolFactory) = RegistryImpl(this)
}
) : UpnpServiceImpl(upnpServiceConfiguration)

View file

@ -32,12 +32,12 @@ class MyUpnpServiceConfiguration(
}
override fun createNetworkAddressFactory(streamListenPort: Int, multicastResponsePort: Int) =
MyNetworkAddressFactory(serverInfoProvider, multicastResponsePort)
MyNetworkAddressFactory(streamListenPort, multicastResponsePort)
inner class MyNetworkAddressFactory(
private val serverInfoProvider: ServerInfoProvider,
streamListenPort: Int,
multicastResponsePort: Int
) : NetworkAddressFactoryImpl(serverInfoProvider.port, multicastResponsePort) {
) : NetworkAddressFactoryImpl(streamListenPort, multicastResponsePort) {
override fun isUsableAddress(iface: NetworkInterface, address: InetAddress) =
addressesToBind.contains(address.hostAddress)
}

View file

@ -1,7 +1,7 @@
package net.schowek.nextclouddlna.nextcloud.content
import mu.KLogging
import net.schowek.nextclouddlna.nextcloud.NextcloudDB
import net.schowek.nextclouddlna.nextcloud.db.NextcloudDB
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.Clock
@ -100,8 +100,8 @@ class ContentTreeProvider(
}
}
fun getItem(id: String): ContentItem? = tree.getItem(id)
fun getNode(id: String): ContentNode? = tree.getNode(id)
fun getItem(id: String) = tree.getItem(id)
fun getNode(id: String) = tree.getNode(id)
companion object : KLogging() {
const val REBUILD_TREE_DELAY_IN_MS = 1000 * 60L // 1m
@ -115,13 +115,11 @@ class ContentTree {
private val nodes: MutableMap<String, ContentNode> = HashMap()
private val items: MutableMap<String, ContentItem> = HashMap()
fun getNode(id: String): ContentNode? {
return nodes[id]
}
val itemsCount get() = items.size
val nodesCount get() = nodes.size
fun getItem(id: String): ContentItem? {
return items[id]
}
fun getNode(id: String) = nodes[id]
fun getItem(id: String) = items[id]
fun addItem(item: ContentItem) {
items["${item.id}"] = item
@ -130,8 +128,5 @@ class ContentTree {
fun addNode(node: ContentNode) {
nodes["${node.id}"] = node
}
val itemsCount get() = items.size
val nodesCount get() = nodes.size
}

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.nextcloud
package net.schowek.nextclouddlna.nextcloud.db
import jakarta.annotation.PostConstruct
import mu.KLogging
@ -6,7 +6,6 @@ import net.schowek.nextclouddlna.nextcloud.config.NextcloudConfigDiscovery
import net.schowek.nextclouddlna.nextcloud.content.ContentItem
import net.schowek.nextclouddlna.nextcloud.content.ContentNode
import net.schowek.nextclouddlna.nextcloud.content.MediaFormat
import net.schowek.nextclouddlna.nextcloud.db.*
import net.schowek.nextclouddlna.nextcloud.db.Filecache.Companion.FOLDER_MIME_TYPE
import org.springframework.dao.InvalidDataAccessResourceUsageException
import org.springframework.stereotype.Component

View file

@ -1,6 +1,6 @@
package net.schowek.nextclouddlna.nextcloud.content
import net.schowek.nextclouddlna.nextcloud.NextcloudDB
import net.schowek.nextclouddlna.nextcloud.db.NextcloudDB
import spock.lang.Specification
import java.time.Clock
import java.time.ZoneId