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 package net.schowek.nextclouddlna.controller
import net.schowek.nextclouddlna.dlna.DlnaService
import net.schowek.nextclouddlna.dlna.media.MediaServer import net.schowek.nextclouddlna.dlna.MediaServer
import org.jupnp.support.model.DIDLObject import org.jupnp.support.model.DIDLObject
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
@ -14,11 +14,14 @@ class UpnpControllerIntTest extends UpnpAwareSpecification {
@Autowired @Autowired
private MediaServer mediaServer private MediaServer mediaServer
@Autowired
private DlnaService dlnaService
def uid def uid
def setup() { def setup() {
uid = mediaServer.serviceIdentifier uid = mediaServer.serviceIdentifier
dlnaService.start()
} }
def "should serve icon"() { 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.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.context.annotation.Import import org.springframework.context.annotation.Import
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification import spock.lang.Specification
import support.beans.TestConfig import support.beans.TestConfig
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT 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) @ContextConfiguration(loader = SpringBootContextLoader, classes = NextcloudDLNAApp.class)
@SpringBootTest(webEnvironment = DEFINED_PORT) @SpringBootTest(webEnvironment = DEFINED_PORT)
@ActiveProfiles("integration") @ActiveProfiles("integration")
@Import(TestConfig.class) @Import(TestConfig.class)
@DirtiesContext(classMode = AFTER_CLASS)
class IntegrationSpecification extends Specification { class IntegrationSpecification extends Specification {
@Autowired @Autowired
private TestRestTemplate restTemplate private TestRestTemplate restTemplate
@ -30,6 +33,6 @@ class IntegrationSpecification extends Specification {
private ServerInfoProvider serverInfoProvider private ServerInfoProvider serverInfoProvider
protected String urlWithPort(String uri = "") { 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 package support.beans.dlna.upnp
import org.jupnp.DefaultUpnpServiceConfiguration 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.DatagramIO
import org.jupnp.transport.spi.NetworkAddressFactory import org.jupnp.transport.spi.NetworkAddressFactory
import org.jupnp.transport.spi.StreamClient import org.jupnp.transport.spi.StreamClient
@ -11,19 +14,38 @@ import org.springframework.stereotype.Component
@Component @Component
@Profile("integration") @Profile("integration")
class UpnpServiceConfigurationInt extends DefaultUpnpServiceConfiguration { class UpnpServiceConfigurationInt extends DefaultUpnpServiceConfiguration {
List<OutgoingDatagramMessage> outgoingDatagramMessages = new ArrayList<>()
@Override @Override
public StreamClient createStreamClient() { public StreamClient createStreamClient() {
return null return null
} }
@Override @Override
public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory) { public StreamServer createStreamServer(NetworkAddressFactory networkAddressFactory) {
return null return null
} }
// @Override @Override
// public DatagramIO createDatagramIO(NetworkAddressFactory networkAddressFactory) { 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 jakarta.servlet.http.HttpServletRequest
import mu.KLogging 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.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.InputStreamResource
import org.springframework.core.io.Resource import org.springframework.core.io.Resource
import org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE 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 jakarta.annotation.PreDestroy
import mu.KLogging import mu.KLogging
import net.schowek.nextclouddlna.dlna.media.MediaServer
import org.jupnp.UpnpService import org.jupnp.UpnpService
import org.jupnp.model.message.StreamRequestMessage import org.jupnp.model.message.StreamRequestMessage
import org.jupnp.model.message.StreamResponseMessage import org.jupnp.model.message.StreamResponseMessage
@ -22,7 +21,7 @@ class DlnaService(
upnpService.registry.addDevice(mediaServer.device) upnpService.registry.addDevice(mediaServer.device)
} }
@EventListener @EventListener(condition = "!@environment.acceptsProfiles('integration')")
fun handleContextRefresh(event: ContextRefreshedEvent) { fun handleContextRefresh(event: ContextRefreshedEvent) {
start() start()
} }

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.media package net.schowek.nextclouddlna.dlna
import mu.KLogging import mu.KLogging
import net.schowek.nextclouddlna.util.ExternalUrls 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.UpnpServiceConfiguration
import org.jupnp.UpnpServiceImpl import org.jupnp.UpnpServiceImpl
import org.jupnp.protocol.ProtocolFactory
import org.jupnp.registry.RegistryImpl
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class MyUpnpService( class MyUpnpService(
upnpServiceConfiguration: UpnpServiceConfiguration upnpServiceConfiguration: UpnpServiceConfiguration
) : UpnpServiceImpl(upnpServiceConfiguration) { ) : UpnpServiceImpl(upnpServiceConfiguration)
override fun createRegistry(pf: ProtocolFactory) = RegistryImpl(this)
}

View file

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

View file

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

View file

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