From 23e02c1b6fb6e859ebb06c6eddd8f819ed0175b5 Mon Sep 17 00:00:00 2001 From: xis Date: Mon, 23 Oct 2023 20:04:28 +0200 Subject: [PATCH 1/6] Fix catching exception while building the content tree --- .../nextclouddlna/nextcloud/db/NextcloudDB.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/db/NextcloudDB.kt b/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/db/NextcloudDB.kt index 8292cad..ed3c7f5 100644 --- a/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/db/NextcloudDB.kt +++ b/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/db/NextcloudDB.kt @@ -87,12 +87,14 @@ class NextcloudDB( children.filter { f -> f.mimetype == folderMimeType } .forEach { folder -> n.addNode(asNode(folder)) } - try { - children.filter { f -> f.mimetype != folderMimeType } - .forEach { file -> n.addItem(asItem(file)) } - } catch (e: Exception) { - logger.warn(e.message) - } + children.filter { f -> f.mimetype != folderMimeType } + .forEach { file -> + runCatching { + n.addItem(asItem(file)) + }.recover { + logger.warn(it.message) + } + } } fun maxMtime(): Long = filecacheRepository.findFirstByOrderByStorageMtimeDesc().storageMtime From 66d74acbfcc8a32a48bc3809fc114ecb82ea954e Mon Sep 17 00:00:00 2001 From: xis Date: Mon, 23 Oct 2023 20:56:22 +0200 Subject: [PATCH 2/6] Default network interface used --- .../nextclouddlna/util/ServerInfoProvider.kt | 29 ++++++++++--------- src/main/resources/application.yml | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt b/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt index 18c155a..7cba39d 100644 --- a/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt +++ b/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt @@ -19,27 +19,28 @@ class ServerInfoProviderImpl( @param:Value("\${server.port}") override val port: Int, @param:Value("\${server.interface}") private val networkInterface: String ) : ServerInfoProvider { - override val host: String get() = address!!.hostAddress - var address: InetAddress? = null + override val host: String get() = address.hostAddress + val address: InetAddress = guessInetAddress() @PostConstruct fun init() { - address = guessInetAddress() - logger.info("Using server address: {} and port {}", address!!.hostAddress, port) + logger.info("Using server address: {} and port {}", address.hostAddress, port) } private fun guessInetAddress(): InetAddress { - return try { - val iface = NetworkInterface.getByName(networkInterface) - ?: throw RuntimeException("Could not find network interface $networkInterface") - val addresses = iface.inetAddresses - while (addresses.hasMoreElements()) { - val x = addresses.nextElement() - if (x is Inet4Address) { - return x - } + try { + return if (networkInterface.isNotEmpty()) { + logger.debug { "Using network interface $networkInterface" } + val iface = NetworkInterface.getByName(networkInterface) + ?: throw RuntimeException("Could not find network interface $networkInterface") + + iface.inetAddresses.toList().filterIsInstance().first() + } else { + logger.info { "No network interface given, using default local address" } + InetAddress.getLocalHost() + }.also { + logger.debug { "Found local address ${it.hostAddress}" } } - InetAddress.getLocalHost() } catch (e: UnknownHostException) { throw RuntimeException(e) } catch (e: SocketException) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 978edef..f116b70 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ server: port: ${NEXTCLOUD_DLNA_SERVER_PORT:8080} - interface: ${NEXTCLOUD_DLNA_INTERFACE:eth0} + interface: ${NEXTCLOUD_DLNA_INTERFACE:} friendlyName: ${NEXTCLOUD_DLNA_FRIENDLY_NAME:Nextcloud-DLNA} nextcloud: From e57fa6930496a5c88e1d1701e7b53574a125acea Mon Sep 17 00:00:00 2001 From: xis Date: Mon, 23 Oct 2023 21:18:32 +0200 Subject: [PATCH 3/6] readme update --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0aefe87..fce6ce3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ devices in your network. It supports the group folders as well. ## Running in Docker + You can use the docker image with nextcloud-dlna e.g.: ```bash @@ -20,7 +21,7 @@ docker run -d \ thanek/nextcloud-dlna ``` -or, if used together with the official Nextcloud docker image using the docker-composer. See the [examples](./examples) +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. @@ -48,21 +49,20 @@ or, if you've already built the project and created the jar file: Available env variables with their default values that you can overwrite: -| env variable | default value | description | -|------------------------------|----------------|---------------------------------------------------------| -| NEXTCLOUD_DLNA_SERVER_PORT | 8080 | port on which the contentController will listen | -| 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 | - - -### Code used +| env variable | default value | description | +|------------------------------|----------------|---------------------------------------------------------------------------------------------------------------| +| NEXTCLOUD_DLNA_SERVER_PORT | 8080 | port on which the contentController will listen | +| NEXTCLOUD_DLNA_INTERFACE | | (optional) interface the server will be listening on
if not given, the default local address will be used | +| 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 | + +### Code used Some java code was taken from https://github.com/haku/dlnatoad and https://github.com/UniversalMediaServer/UniversalMediaServer converted to Kotlin with upgrade to jupnp instead of From 4e64f4add4d479d83387f0d735fa2b3ec5051fa0 Mon Sep 17 00:00:00 2001 From: xis Date: Tue, 24 Oct 2023 19:30:03 +0200 Subject: [PATCH 4/6] test for adding children to node added --- .../nextcloud/content/MediaFormat.kt | 4 +- .../nextcloud/db/NextcloudDBTest.groovy | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/test/groovy/net/schowek/nextclouddlna/nextcloud/db/NextcloudDBTest.groovy diff --git a/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/content/MediaFormat.kt b/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/content/MediaFormat.kt index 06d6ea3..d84800d 100644 --- a/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/content/MediaFormat.kt +++ b/src/main/kotlin/net/schowek/nextclouddlna/nextcloud/content/MediaFormat.kt @@ -50,10 +50,8 @@ enum class MediaFormat( } companion object { - val EXT_TO_FORMAT: Map = values().associateBy { it.ext } - fun fromMimeType(mimetype: String): MediaFormat { - return stream(values()).filter { i -> i.mime.equals(mimetype) } + return stream(values()).filter { i -> i.mime == mimetype } .findFirst() .orElseThrow { RuntimeException("Unknown mime type $mimetype") } } diff --git a/src/test/groovy/net/schowek/nextclouddlna/nextcloud/db/NextcloudDBTest.groovy b/src/test/groovy/net/schowek/nextclouddlna/nextcloud/db/NextcloudDBTest.groovy new file mode 100644 index 0000000..241668f --- /dev/null +++ b/src/test/groovy/net/schowek/nextclouddlna/nextcloud/db/NextcloudDBTest.groovy @@ -0,0 +1,82 @@ +package net.schowek.nextclouddlna.nextcloud.db + +import net.schowek.nextclouddlna.nextcloud.config.NextcloudConfigDiscovery +import net.schowek.nextclouddlna.nextcloud.content.ContentNode +import spock.lang.Specification + +import static net.schowek.nextclouddlna.nextcloud.content.MediaFormat.* + +class NextcloudDBTest extends Specification { + static final def thumbStorageId = 2 + static final def TEXT_MD = 1 + static final def VIDEO_MP4 = 2 + static final def IMAGE_JPG = 3 + static final def DIRECTORY = 4 + static final def AUDIO_MP3 = 5 + static final def APP_PDF = 6 + def configDiscovery = Mock(NextcloudConfigDiscovery) + def mimeTypeRepository = Mock(MimetypeRepository) + def filecacheRepository = Mock(FilecacheRepository) + def groupFolderRepository = Mock(GroupFolderRepository) + def tmpDir = File.createTempDir() + + def setup() { + configDiscovery.getAppDataDir() >> "/tmp/app_0987654321" + configDiscovery.getNextcloudDir() >> tmpDir + filecacheRepository.findFirstByPath(configDiscovery.appDataDir) + >> new Filecache(999, thumbStorageId, "thumbs", 0, "thumbs", DIRECTORY, 123L, 0L, 0L) + mimeTypeRepository.findAll() >> [ + new Mimetype(DIRECTORY, 'httpd/unix-directory'), + new Mimetype(TEXT_MD, 'text/markdown'), + new Mimetype(AUDIO_MP3, 'audio/mpeg'), + new Mimetype(VIDEO_MP4, 'video/mp4'), + new Mimetype(IMAGE_JPG, 'image/jpeg'), + new Mimetype(APP_PDF, 'application/pdf') + ] + } + + def cleanup() { + tmpDir.delete() + } + + def "should append children to the parent node"() { + given: + def parentId = 123 + def parentNode = new ContentNode(parentId, 0, "stuff") + filecacheRepository.findByParent(parentId) >> [ + aFilecache(1, "/stuff/foo.jpg", parentId, IMAGE_JPG), + aFilecache(2, "/stuff/readme.md", parentId, TEXT_MD), + aFilecache(4, "/stuff/baz.mp3", parentId, AUDIO_MP3), + aFilecache(3, "/stuff/bar.jpeg", parentId, IMAGE_JPG), + aFilecache(5, "/stuff/blah.mp4", parentId, VIDEO_MP4), + aFilecache(6, "/stuff/documents", parentId, DIRECTORY), + aFilecache(7, "/stuff/documents/resume.pdf", 6, APP_PDF) + ] + + def sut = new NextcloudDB(configDiscovery, mimeTypeRepository, filecacheRepository, groupFolderRepository) + + when: + sut.appendChildren(parentNode) + + then: "appends only items with known media types" + parentNode.items.any() + parentNode.items.find { it.name == 'foo.jpg' }.format.mime == JPEG.mime + parentNode.items.find { it.name == 'bar.jpeg' }.format.mime == JPEG.mime + parentNode.items.find { it.name == 'baz.mp3' }.format.mime == MP3.mime + parentNode.items.find { it.name == 'blah.mp4' }.format.mime == MP4.mime + parentNode.items.find { it.name == 'readme.md' } == null + + then: "appends all subnodes without their children" + parentNode.nodes.size() == 1 + with(parentNode.nodes[0]) { + assert it.name == 'documents' + assert it.items == [] + assert it.nodes == [] + } + } + + private static def aFilecache(int id, String path, int parent, int mimeType) { + def name = new File(path).getName() + return new Filecache(id, thumbStorageId, path, parent, name, mimeType, 0L, 0L, 0L) + } +} From 66cfe2a6ade662aa86f50f7c770e87fa8fa0aaf8 Mon Sep 17 00:00:00 2001 From: xis Date: Tue, 24 Oct 2023 20:23:55 +0200 Subject: [PATCH 5/6] guessing the default host address when no interface name provided --- .../nextclouddlna/util/ServerInfoProvider.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt b/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt index 7cba39d..e81f7d5 100644 --- a/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt +++ b/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt @@ -20,14 +20,13 @@ class ServerInfoProviderImpl( @param:Value("\${server.interface}") private val networkInterface: String ) : ServerInfoProvider { override val host: String get() = address.hostAddress - val address: InetAddress = guessInetAddress() + private val address: InetAddress = getInetAddress() - @PostConstruct - fun init() { - logger.info("Using server address: {} and port {}", address.hostAddress, port) + init { + logger.info("Using server address: ${address.hostAddress} and port $port") } - private fun guessInetAddress(): InetAddress { + private fun getInetAddress(): InetAddress { try { return if (networkInterface.isNotEmpty()) { logger.debug { "Using network interface $networkInterface" } @@ -36,8 +35,8 @@ class ServerInfoProviderImpl( iface.inetAddresses.toList().filterIsInstance().first() } else { - logger.info { "No network interface given, using default local address" } - InetAddress.getLocalHost() + logger.info { "No network interface name given, trying to use default local address" } + guessLocalAddress() }.also { logger.debug { "Found local address ${it.hostAddress}" } } @@ -48,5 +47,15 @@ class ServerInfoProviderImpl( } } + private fun guessLocalAddress() = try { + DatagramSocket().use { s -> + s.connect(InetAddress.getByAddress(byteArrayOf(1, 1, 1, 1)), 80) + s.localAddress + } + } catch (e: Exception) { + logger.warn { e.message } + InetAddress.getLocalHost() + } + companion object : KLogging() } From 54a0e140f61070645f50312d4c7adab2caadb143 Mon Sep 17 00:00:00 2001 From: xis Date: Tue, 24 Oct 2023 20:30:31 +0200 Subject: [PATCH 6/6] added comment --- .../net/schowek/nextclouddlna/util/ServerInfoProvider.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt b/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt index e81f7d5..1473442 100644 --- a/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt +++ b/src/main/kotlin/net/schowek/nextclouddlna/util/ServerInfoProvider.kt @@ -47,6 +47,9 @@ class ServerInfoProviderImpl( } } + // perform fake request to 1.1.1.1:80 just to get the localAddress + // with use of the default routing. + // if it fails, we use the localAddress() which can be wrong private fun guessLocalAddress() = try { DatagramSocket().use { s -> s.connect(InetAddress.getByAddress(byteArrayOf(1, 1, 1, 1)), 80)