Merge pull request #13 from thanek/dev

Dev
This commit is contained in:
xis 2023-10-22 15:01:46 +02:00 committed by GitHub
commit 08210ad3eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 464 additions and 146 deletions

View file

@ -6,7 +6,33 @@ DLNA addon for your self-hosted Nextcloud app instance that allows you to stream
devices in your network. devices in your network.
It supports the group folders as well. It supports the group folders as well.
Just edit the `application.yml` and rebuild the project with: ## Running in Docker
You can use the docker image with nextcloud-dlna e.g.:
```bash
docker run -d \
--name="nextcloud-dlna" \
--net=host \
-v /path/to/nextcloud/app/ending/with/data:/nextcloud \
-e NEXTCLOUD_DATA_DIR=/nextcloud \
-e NEXTCLOUD_DB_HOST='<your_nextcloud_db_host_ip_here>' \
-e NEXTCLOUD_DB_PASS='<your_nextcloud_db_pass_here>' \
nextcloud-dlna
```
or, if used together with the official Nextcloud docker image using the docker-composer. See the [examples](./examples)
directory. for more details about running nextcloud-dlna server in the docker container.
You can pass to the container other env variables that are listed below.
Note that it would not work on Mac OS since docker is a Linux container and the `host` networking mode doesn't actually
share the host's network interfaces.
See https://hub.docker.com/r/thanek/nextcloud-dlna for more docker image details.
## Building the project
Build the project with:
`./gradlew clean bootRun` `./gradlew clean bootRun`
@ -18,6 +44,8 @@ or, if you've already built the project and created the jar file:
`NEXTCLOUD_DLNA_SERVER_PORT=9999 java -jar nextcloud-dlna-X.Y.Z.jar` `NEXTCLOUD_DLNA_SERVER_PORT=9999 java -jar nextcloud-dlna-X.Y.Z.jar`
## ENV variables
Available env variables with their default values that you can overwrite: Available env variables with their default values that you can overwrite:
| env variable | default value | description | | env variable | default value | description |
@ -26,6 +54,7 @@ Available env variables with their default values that you can overwrite:
| NEXTCLOUD_DLNA_INTERFACE | eth0 | interface the server will be listening on | | NEXTCLOUD_DLNA_INTERFACE | eth0 | interface the server will be listening on |
| NEXTCLOUD_DLNA_FRIENDLY_NAME | Nextcloud-DLNA | friendly name of the DLNA service | | NEXTCLOUD_DLNA_FRIENDLY_NAME | Nextcloud-DLNA | friendly name of the DLNA service |
| NEXTCLOUD_DATA_DIR | | nextcloud installation directory (that ends with /data) | | NEXTCLOUD_DATA_DIR | | nextcloud installation directory (that ends with /data) |
| NEXTCLOUD_DB_TYPE | mariadb | nextcloud database type (mysql, mariadb, postgresql) |
| NEXTCLOUD_DB_HOST | localhost | nextcloud database host | | NEXTCLOUD_DB_HOST | localhost | nextcloud database host |
| NEXTCLOUD_DB_PORT | 3306 | nextcloud database port | | NEXTCLOUD_DB_PORT | 3306 | nextcloud database port |
| NEXTCLOUD_DB_NAME | nextcloud | nextcloud database name | | NEXTCLOUD_DB_NAME | nextcloud | nextcloud database name |
@ -33,27 +62,6 @@ Available env variables with their default values that you can overwrite:
| NEXTCLOUD_DB_PASS | nextcloud | nextcloud database password | | NEXTCLOUD_DB_PASS | nextcloud | nextcloud database password |
## Running in Docker
You can use the docker image with nextcloud-dlna e.g.:
```
docker run -d \
--name="nextcloud-dlna" \
--net=host \
-v /path/to/nextcloud/app/ending/with/data:/nextcloud \
-e NEXTCLOUD_DATA_DIR=/nextcloud \
-e NEXTCLOUD_DB_HOST='<your_nextcloud_db_host_ip_here>' \
-e NEXTCLOUD_DB_PASS='<your_nextcloud_db_pass_here>' \
nextcloud-dlna
```
You can pass to the container other env variables that are listed above.
Note that it would not work on Mac OS since docker is a Linux container and the `host` networking mode doesn't actually
share the host's network interfaces.
See https://hub.docker.com/r/thanek/nextcloud-dlna for more docker image details.
### Code used ### Code used
Some java code was taken from https://github.com/haku/dlnatoad Some java code was taken from https://github.com/haku/dlnatoad

View file

@ -42,9 +42,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.mariadb.jdbc:mariadb-java-client:3.2.0' implementation 'org.mariadb.jdbc:mariadb-java-client:3.2.0'
implementation 'org.postgresql:postgresql:42.6.0'
implementation 'org.jupnp:org.jupnp:2.7.1' implementation 'org.jupnp:org.jupnp:2.7.1'
implementation 'org.jupnp:org.jupnp.support: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' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
// to avoid snakeyaml-1.3 vulnerability CVE-2022-1471 // to avoid snakeyaml-1.3 vulnerability CVE-2022-1471
implementation 'org.yaml:snakeyaml:2.2' implementation 'org.yaml:snakeyaml:2.2'

View file

@ -0,0 +1,84 @@
version: '2'
volumes:
app:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/app
app_etc:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/etc/apache2
db_data:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/db
db_etc:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/etc/mysql
services:
db:
image: mariadb:10.5
restart: always
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
volumes:
- db_data:/var/lib/mysql
- db_etc:/etc/mysql
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=sql
- MYSQL_PASSWORD=secret
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
redis:
image: redis
restart: always
app:
image: nextcloud
restart: always
ports:
- "80:80"
- "443:443"
links:
- db
- redis
volumes:
- app:/var/www/html
- app_etc:/etc/apache2
environment:
- PHP_MEMORY_LIMIT=1G
- PHP_UPLOAD_LIMIT=4G
- MYSQL_PASSWORD=secret
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=db
dlna:
image: thanek/nextcloud-dlna
restart: always
volumes:
- app:/nextcloud
network_mode: "host"
ports:
- "9999:9999"
environment:
- NEXTCLOUD_DLNA_SERVER_PORT=9999
- NEXTCLOUD_DLNA_FRIENDLY_NAME=Nextcloud
- NEXTCLOUD_DATA_DIR=/nextcloud/data
- NEXTCLOUD_DB_TYPE=mariadb
- NEXTCLOUD_DB_HOST=localhost
- NEXTCLOUD_DB_PASS=secret

View file

@ -0,0 +1,13 @@
This will run the nextcloud-dlna in docker together with the full Nextcloud installation (containing the app, database
and redis) located in the `./app` directory.
Note: in order to enable network access to the MariaDB server, after the first run, you'll need to edit
the `./etc/mariadb.cnf`, section `[client-config]` by adding the line:
```
port = 3306
```
and removing the line:
```
socket = /var/run/mysqld/mysqld.sock
```
, then restart the `db` (`nextcloud-db-1`) container.

View file

@ -0,0 +1,3 @@
#!/bin/bash
docker-compose up -d

View file

@ -0,0 +1,2 @@
This will run the nextcloud-dlna in docker and connect to the Nextcloud installation assuming it is located in the
`/opt/nextcloud` directory.

View file

@ -0,0 +1,14 @@
#!/bin/bash
docker run -d \
--name="nextcloud-dlna" \
--restart=unless-stopped \
--net=host \
-p 9999:9999 \
-e NEXTCLOUD_DLNA_SERVER_PORT=9999 \
-e NEXTCLOUD_DLNA_FRIENDLY_NAME="Nextcloud" \
-e NEXTCLOUD_DB_HOST='localhost' \
-e NEXTCLOUD_DB_PASS='secret' \
-v '/opt/nextcloud/data:/nextcloud' \
-e NEXTCLOUD_DATA_DIR=/nextcloud \
thanek/nextcloud-dlna

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,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
}
}

View file

@ -6,15 +6,21 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootContextLoader 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.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 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)
@DirtiesContext(classMode = AFTER_CLASS)
class IntegrationSpecification extends Specification { class IntegrationSpecification extends Specification {
@Autowired @Autowired
private TestRestTemplate restTemplate private TestRestTemplate restTemplate
@ -27,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

@ -0,0 +1,7 @@
package support.beans
import org.springframework.context.annotation.ComponentScan
@ComponentScan(["support", "net.schowek.nextclouddlna"])
class TestConfig {
}

View file

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

View file

@ -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.context.annotation.Profile
import org.springframework.stereotype.Component import org.springframework.stereotype.Component

View file

@ -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.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Profile import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component import org.springframework.stereotype.Component

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.util package support.beans.util
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.server.ConfigurableWebServerFactory import org.springframework.boot.web.server.ConfigurableWebServerFactory

View file

@ -1,90 +0,0 @@
package net.schowek.nextclouddlna
import jakarta.annotation.PreDestroy
import mu.KLogging
import net.schowek.nextclouddlna.dlna.media.MediaServer
import net.schowek.nextclouddlna.dlna.transport.ApacheStreamClient
import net.schowek.nextclouddlna.dlna.transport.ApacheStreamClientConfiguration
import net.schowek.nextclouddlna.dlna.transport.MyStreamServerConfiguration
import net.schowek.nextclouddlna.dlna.transport.MyStreamServerImpl
import net.schowek.nextclouddlna.util.ServerInfoProvider
import org.jupnp.DefaultUpnpServiceConfiguration
import org.jupnp.UpnpServiceConfiguration
import org.jupnp.UpnpServiceImpl
import org.jupnp.model.message.StreamRequestMessage
import org.jupnp.model.message.StreamResponseMessage
import org.jupnp.model.message.UpnpResponse
import org.jupnp.protocol.ProtocolFactory
import org.jupnp.registry.RegistryImpl
import org.jupnp.transport.impl.NetworkAddressFactoryImpl
import org.jupnp.transport.spi.NetworkAddressFactory
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component
import java.net.InetAddress
import java.net.NetworkInterface
@Component
class DlnaService(
private val mediaServer: MediaServer,
private val serverInfoProvider: ServerInfoProvider,
) {
private val addressesToBind: List<String> = listOf(serverInfoProvider.host)
var upnpService = MyUpnpService(MyUpnpServiceConfiguration())
fun start() {
upnpService.startup()
upnpService.registry.addDevice(mediaServer.device)
}
@EventListener
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}" }
}
}
inner class MyUpnpService(
configuration: UpnpServiceConfiguration
) : UpnpServiceImpl(configuration) {
override fun createRegistry(pf: ProtocolFactory) = RegistryImpl(this)
}
private inner class MyUpnpServiceConfiguration : DefaultUpnpServiceConfiguration(serverInfoProvider.port) {
override fun createStreamClient() =
ApacheStreamClient(ApacheStreamClientConfiguration(syncProtocolExecutorService))
override fun createStreamServer(networkAddressFactory: NetworkAddressFactory) =
MyStreamServerImpl(MyStreamServerConfiguration(networkAddressFactory.streamListenPort))
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) =
addressesToBind.contains(address.hostAddress)
}
companion object : KLogging()
}

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

@ -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 = "!@environment.acceptsProfiles('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()
}

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

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

View file

@ -0,0 +1,44 @@
package net.schowek.nextclouddlna.dlna.upnp
import net.schowek.nextclouddlna.dlna.upnp.transport.ApacheStreamClient
import net.schowek.nextclouddlna.dlna.upnp.transport.ApacheStreamClientConfiguration
import net.schowek.nextclouddlna.dlna.upnp.transport.MyStreamServerConfiguration
import net.schowek.nextclouddlna.dlna.upnp.transport.MyStreamServerImpl
import net.schowek.nextclouddlna.util.ServerInfoProvider
import org.jupnp.DefaultUpnpServiceConfiguration
import org.jupnp.transport.impl.NetworkAddressFactoryImpl
import org.jupnp.transport.spi.DatagramIO
import org.jupnp.transport.spi.NetworkAddressFactory
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.net.InetAddress
import java.net.NetworkInterface
@Component
@Profile("!integration")
class MyUpnpServiceConfiguration(
private val serverInfoProvider: ServerInfoProvider
) : DefaultUpnpServiceConfiguration(serverInfoProvider.port) {
val addressesToBind = listOf(serverInfoProvider.host)
override fun createStreamClient() =
ApacheStreamClient(ApacheStreamClientConfiguration(syncProtocolExecutorService))
override fun createStreamServer(networkAddressFactory: NetworkAddressFactory) =
MyStreamServerImpl(MyStreamServerConfiguration(networkAddressFactory.streamListenPort))
override fun createDatagramIO(networkAddressFactory: NetworkAddressFactory): DatagramIO<*> {
return super.createDatagramIO(networkAddressFactory)
}
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) =
addressesToBind.contains(address.hostAddress)
}
}

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.transport package net.schowek.nextclouddlna.dlna.upnp.transport
import mu.KLogging import mu.KLogging
import org.apache.http.HttpMessage import org.apache.http.HttpMessage

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.transport package net.schowek.nextclouddlna.dlna.upnp.transport
import org.jupnp.transport.spi.AbstractStreamClientConfiguration import org.jupnp.transport.spi.AbstractStreamClientConfiguration
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.transport package net.schowek.nextclouddlna.dlna.upnp.transport
import org.jupnp.transport.spi.StreamServerConfiguration import org.jupnp.transport.spi.StreamServerConfiguration

View file

@ -1,4 +1,4 @@
package net.schowek.nextclouddlna.dlna.transport package net.schowek.nextclouddlna.dlna.upnp.transport
import mu.KLogging import mu.KLogging
import org.jupnp.transport.Router import org.jupnp.transport.Router

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

@ -0,0 +1,57 @@
package net.schowek.nextclouddlna.util
import mu.KLogging
import net.schowek.nextclouddlna.util.NextcloudDBType.*
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.jdbc.datasource.DriverManagerDataSource
@Configuration
@Profile("!integration")
@EnableConfigurationProperties(NextcloudDBConfigProperties::class)
class DriverManagerDataSourceConfig {
@Bean
fun driverManagerDataSource(props: NextcloudDBConfigProperties): DriverManagerDataSource {
logger.info { "Using Nextcloud DB connection parameters: $props" }
return DriverManagerDataSource().also { dataSource ->
when (props.type) {
MARIADB, MYSQL -> {
dataSource.setDriverClassName("org.mariadb.jdbc.Driver");
dataSource.url = "jdbc:mariadb://${props.host}:${props.port}/${props.name}";
}
POSTGRES -> {
dataSource.setDriverClassName("org.mariadb.jdbc.Driver");
dataSource.url = "jdbc:postgresql://${props.host}:${props.port}/${props.name}";
dataSource.connectionProperties?.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect")
}
else -> throw RuntimeException("Unsupported DB type")
}
dataSource.username = props.user;
dataSource.password = props.pass;
}
}
companion object : KLogging()
}
@Profile("!integration")
@ConfigurationProperties(prefix = "nextcloud.db")
data class NextcloudDBConfigProperties(
val type: NextcloudDBType,
val host: String,
val port: Int,
val name: String,
val user: String,
val pass: String
)
enum class NextcloudDBType(val value: String) {
MYSQL("mysql"),
MARIADB("mariadb"),
POSTGRES("postgres")
}

View file

@ -5,13 +5,15 @@ server:
nextcloud: nextcloud:
filesDir: ${NEXTCLOUD_DATA_DIR} filesDir: ${NEXTCLOUD_DATA_DIR}
db:
type: ${NEXTCLOUD_DB_TYPE:mariadb}
host: ${NEXTCLOUD_DB_HOST:localhost}
port: ${NEXTCLOUD_DB_PORT:3306}
name: ${NEXTCLOUD_DB_NAME:nextcloud}
user: ${NEXTCLOUD_DB_USER:nextcloud}
pass: ${NEXTCLOUD_DB_PASS:nextcloud}
spring: spring:
datasource:
url: "jdbc:mariadb://${NEXTCLOUD_DB_HOST:localhost}:${NEXTCLOUD_DB_PORT:3306}/${NEXTCLOUD_DB_NAME:nextcloud}"
username: ${NEXTCLOUD_DB_USER:nextcloud}
password: ${NEXTCLOUD_DB_PASS:nextcloud}
driver-class-name: org.mariadb.jdbc.Driver
jpa: jpa:
hibernate: hibernate:
ddl-auto: none ddl-auto: none

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