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

Dev #13

Merged
merged 9 commits into from
Oct 22, 2023
Merged

Dev #13

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
52 changes: 30 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,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`

Expand All @@ -16,6 +42,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 |
Expand All @@ -24,34 +52,14 @@ 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 |
| NEXTCLOUD_DB_USER | nextcloud | nextcloud database username |
| 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
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
84 changes: 84 additions & 0 deletions examples/docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -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

13 changes: 13 additions & 0 deletions examples/docker-compose/readme.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions examples/docker-compose/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

docker-compose up -d
2 changes: 2 additions & 0 deletions examples/docker-standalone/readme.md
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions examples/docker-standalone/run.sh
Original file line number Diff line number Diff line change
@@ -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
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
Loading