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.
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`
@ -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`
## ENV variables
Available env variables with their default values that you can overwrite:
| 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_FRIENDLY_NAME | Nextcloud-DLNA | friendly name of the DLNA service |
| 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_PORT | 3306 | nextcloud database port |
| 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 |
## 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
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.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.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'

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
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,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.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
@ -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;
}
}

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.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.context.annotation.Profile
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.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 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

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

View file

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

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

@ -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:
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:
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:
hibernate:
ddl-auto: none

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