integration tests added

This commit is contained in:
xis 2023-10-14 18:56:59 +02:00
parent 62d4eaf65d
commit a7998082a7
28 changed files with 471 additions and 46 deletions

View file

@ -24,6 +24,11 @@ repositories {
mavenCentral()
}
configurations {
integrationTestImplementation.extendsFrom(testImplementation)
integrationTestRuntimeOnly.extendsFrom(testRuntimeOnly)
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
@ -41,13 +46,14 @@ dependencies {
implementation 'org.jupnp:org.jupnp.support:2.7.1'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'org.apache.groovy:groovy:4.0.15'
testImplementation 'org.apache.groovy:groovy:4.0.15'
testImplementation('org.spockframework:spock-core:2.4-M1-groovy-4.0')
testImplementation('org.spockframework:spock-spring:2.4-M1-groovy-4.0')
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation('com.h2database:h2')
}
tasks.withType(KotlinCompile).configureEach {
@ -62,17 +68,22 @@ tasks.named('test') {
}
sourceSets {
integration {
java {
integrationTest {
groovy.srcDir "$projectDir/src/integration/groovy"
resources.srcDir "$projectDir/src/integration/resources"
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
}
}
}
tasks.register('integrationTest', Test) {
useJUnitPlatform()
description = "Run integration tests"
group = "verification"
testClassesDirs = sourceSets.integration.output.classesDirs
classpath = sourceSets.integration.runtimeClasspath
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
mustRunAfter tasks.test
}
check.dependsOn integrationTest

View file

@ -0,0 +1,33 @@
package net.schowek.nextclouddlna.controller
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import support.IntegrationSpecification
class ContentControllerIntTest extends IntegrationSpecification {
def "should process GET request for content"() {
when:
ResponseEntity<byte[]> response = restTemplate().getForEntity(urlWithPort("/c/19"), byte[]);
then:
response.statusCode == HttpStatus.OK
with(response.headers.each { it.key.toLowerCase() }) {
assert it['content-type'] == ['image/jpeg']
assert it['accept-ranges'] == ["bytes"]
assert it.containsKey('contentfeatures.dlna.org')
assert it.containsKey('transfermode.dlna.org')
assert it.containsKey('realtimeinfo.dlna.org')
}
response.body.length == 2170375
}
def "should return 404 if content does not exist"() {
when:
ResponseEntity<byte[]> response = restTemplate().getForEntity(urlWithPort("/c/blah-blah"), byte[]);
then:
response.statusCode == HttpStatus.NOT_FOUND
}
}

View file

@ -0,0 +1,65 @@
package net.schowek.nextclouddlna.controller
import net.schowek.nextclouddlna.dlna.media.MediaServer
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.xml.sax.InputSource
import support.IntegrationSpecification
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.xpath.XPath
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
import static javax.xml.xpath.XPathConstants.NODE
class UpnpControllerIntTest extends IntegrationSpecification {
@Autowired
private MediaServer mediaServer
def "should serve icon"() {
given:
def uid = mediaServer.serviceIdentifier
when:
ResponseEntity<byte[]> response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/icon.png"), byte[]);
then:
response.statusCode == HttpStatus.OK
with(response.headers.each { it.key.toLowerCase() }) {
assert it['content-type'] == ['application/octet-stream']
}
}
def "should serve service descriptor"() {
given:
def uid = mediaServer.serviceIdentifier
when:
ResponseEntity<String> response = restTemplate().getForEntity(urlWithPort("/dev/${uid}/desc"), String);
then:
response.statusCode == HttpStatus.OK
with(response.headers.each { it.key.toLowerCase() }) {
assert it['content-type'] == ['text/xml']
}
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
new InputSource(new StringReader(response.body))
);
then:
nodeValue(dom, "/root/device/friendlyName") == "nextcloud-dlna-int-test"
nodeValue(dom, "/root/device/UDN") == "uuid:${uid}"
nodeValue(dom, "/root/device/presentationURL") == urlWithPort()
}
private String nodeValue(Document dom, String pattern) {
XPath xpath = XPathFactory.newInstance().newXPath();
return (xpath.evaluate("$pattern/text()", dom, NODE) as Node).nodeValue
}
}

View file

@ -0,0 +1,14 @@
package net.schowek.nextclouddlna.nextcloud.config
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
@Component
@Profile("integration")
class NextcloudAppPathProviderInt implements NextcloudAppPathProvider {
@Override
File getNextcloudDir() {
return new File(getClass().getResource("/nextcloud/app/data").getFile())
}
}

View file

@ -0,0 +1,17 @@
package net.schowek.nextclouddlna.nextcloud.content
import org.springframework.beans.factory.annotation.Autowired
import support.IntegrationSpecification
class ContentTreeProviderIntTest extends IntegrationSpecification {
@Autowired
ContentTreeProvider contentTreeProvider
def "should foo"() {
when:
def result = contentTreeProvider.getItem("19")
then:
result.id == 19
}
}

View file

@ -0,0 +1,26 @@
package net.schowek.nextclouddlna.util
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
@Component
@Profile("integration")
class ServerInfoProviderInt implements ServerInfoProvider {
private final ServerPortCustomizer serverPortCustomizer
@Autowired
ServerInfoProviderInt(ServerPortCustomizer serverPortCustomizer) {
this.serverPortCustomizer = serverPortCustomizer
}
@Override
String getHost() {
return "localhost"
}
@Override
int getPort() {
return serverPortCustomizer.port
}
}

View file

@ -0,0 +1,23 @@
package net.schowek.nextclouddlna.util
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.web.server.ConfigurableWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
@Component
@Profile("integration")
public class ServerPortCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Value("\${random.int(9090,65535)}")
int port
int getPort() {
return port
}
@Override
void customize(ConfigurableWebServerFactory factory) {
factory.setPort(port);
}
}

View file

@ -0,0 +1,35 @@
package support
import net.schowek.nextclouddlna.NextcloudDLNAApp
import net.schowek.nextclouddlna.util.ServerInfoProvider
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.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT
@ContextConfiguration(loader = SpringBootContextLoader, classes = NextcloudDLNAApp.class)
@SpringBootTest(webEnvironment = DEFINED_PORT)
@ActiveProfiles("integration")
class IntegrationSpecification extends Specification {
private TestRestTemplate restTemplate = new TestRestTemplate()
// TODO BEAN
TestRestTemplate restTemplate() {
if (restTemplate == null) {
restTemplate = new TestRestTemplate()
}
return restTemplate
}
@Autowired
private ServerInfoProvider serverInfoProvider
protected String urlWithPort(String uri = "") {
return "http://localhost:" + serverInfoProvider.port + uri;
}
}

View file

@ -0,0 +1,19 @@
server:
friendlyName: "nextcloud-dlna-int-test"
spring:
datasource:
url: jdbc:h2:mem:nextcloud-dlna-integration
username: sa
password:
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: none
sql:
init:
mode: always
schema-locations: classpath:db/schema.sql
data-locations: classpath:db/data.sql

View file

@ -0,0 +1,4 @@
spring:
profiles:
active: integration

View file

@ -0,0 +1,58 @@
INSERT INTO `oc_appconfig` VALUES
('groupfolders','enabled','yes'),
('groupfolders','installed_version','15.3.1'),
('groupfolders','types','filesystem,dav');
INSERT INTO `oc_filecache` VALUES
(1,1,'','d41d8cd98f00b204e9800998ecf8427e',-1,'',2,1,5341566992,1696704138,1696613362,0,0,'',23,''),
(2,1,'files','45b963397aa40d4a0063e0d85e4fe7a1',1,'files',2,1,5341560694,1696704138,1696704138,0,0,'',31,''),
(3,2,'','d41d8cd98f00b204e9800998ecf8427e',-1,'',2,1,13286908,1696702221,1696695204,0,0,'',23,''),
(4,2,'appdata_integration','bed7fa8a60170b5d88c9da5e69eaeb5a',3,'appdata_integration',2,1,10274496,1695737790,1695737790,0,0,'',31,''),
(13,1,'files/Nextcloud intro.mp4','e4919345bcc87d4585a5525daaad99c0',2,'Nextcloud intro.mp4',9,8,3963036,1695737656,1695737656,0,0,'',27,''),
(14,1,'files/Nextcloud.png','',2,'Nextcloud.png',11,10,50598,1695737656,1695737656,0,0,'',27,''),
(15,1,'files/Photos','d01bb67e7b71dd49fd06bad922f521c9',2,'Photos',2,1,5656462,1695737827,1695737658,0,0,'',31,''),
(16,1,'files/Photos/Birdie.jpg','cd31c7af3a0ec6e15782b5edd2774549',15,'Birdie.jpg',12,10,593508,1695737656,1695737656,0,0,'',27,''),
(17,1,'files/Photos/Frog.jpg','d6219add1a9129ed0c1513af985e2081',15,'Frog.jpg',12,10,457744,1695737656,1695737656,0,0,'',27,''),
(18,1,'files/Photos/Gorilla.jpg','6d5f5956d8ff76a5f290cebb56402789',15,'Gorilla.jpg',12,10,474653,1695737656,1695737656,0,0,'',27,''),
(19,1,'files/Photos/Library.jpg','0b785d02a19fc00979f82f6b54a05805',15,'Library.jpg',12,10,2170375,1695737657,1695737657,0,0,'',27,''),
(20,1,'files/Photos/Nextcloud community.jpg','b9b3caef83a2a1c20354b98df6bcd9d0',15,'Nextcloud community.jpg',12,10,797325,1695737657,1695737657,0,0,
'',27,''),
(22,1,'files/Photos/Steps.jpg','7b2ca8d05bbad97e00cbf5833d43e912',15,'Steps.jpg',12,10,567689,1695737658,1695737658,0,0,'',27,''),
(23,1,'files/Photos/Toucan.jpg','681d1e78f46a233e12ecfa722cbc2aef',15,'Toucan.jpg',12,10,167989,1695737658,1695737658,0,0,'',27,''),
(24,1,'files/Photos/Vineyard.jpg','14e5f2670b0817614acd52269d971db8',15,'Vineyard.jpg',12,10,427030,1695737658,1695737658,0,0,'',27,''),
(69,2,'appdata_integration/preview','e771733d5f59ead277f502588282d693',4,'preview',2,1,5153144,1695738765,1695738765,0,0,'',31,'');
INSERT INTO `oc_group_folders` VALUES
(1,'family folder',-3,0);
INSERT INTO `oc_mimetypes` VALUES
(5,'application'),
(19,'application/gzip'),
(18,'application/javascript'),
(20,'application/json'),
(16,'application/octet-stream'),
(6,'application/pdf'),
(13,'application/vnd.oasis.opendocument.graphics'),
(15,'application/vnd.oasis.opendocument.presentation'),
(14,'application/vnd.oasis.opendocument.spreadsheet'),
(17,'application/vnd.oasis.opendocument.text'),
(7,'application/vnd.openxmlformats-officedocument.wordprocessingml.document'),
(22,'audio'),
(23,'audio/mpeg'),
(1,'httpd'),
(2,'httpd/unix-directory'),
(10,'image'),
(12,'image/jpeg'),
(11,'image/png'),
(21,'image/svg+xml'),
(3,'text'),
(4,'text/markdown'),
(8,'video'),
(9,'video/mp4');
INSERT INTO `oc_mounts` VALUES
(1,1,1,'johndoe','/johndoe/',NULL,'OC\\Files\\Mount\\LocalHomeMountProvider'),
(2,3,384,'janedoe','/janedoe/',NULL,'OC\\Files\\Mount\\LocalHomeMountProvider'),
(3,2,586,'johndoe','/johndoe/files/family folder/',NULL,'OCA\\GroupFolders\\Mount\\MountProvider'),
(4,2,586,'janedoe','/janedoe/files/family folder/',NULL,'OCA\\GroupFolders\\Mount\\MountProvider');

View file

@ -0,0 +1,76 @@
DROP TABLE IF EXISTS oc_appconfig;
CREATE TABLE `oc_appconfig` (
`appid` varchar(32) NOT NULL DEFAULT '',
`configkey` varchar(64) NOT NULL DEFAULT '',
`configvalue` longtext DEFAULT NULL,
PRIMARY KEY (`appid`,`configkey`)
);
CREATE INDEX `appconfig_config_key_index` ON oc_appconfig(`configkey`);
DROP TABLE IF EXISTS oc_filecache;
CREATE TABLE `oc_filecache` (
`fileid` NUMERIC(20) NOT NULL AUTO_INCREMENT,
`storage` NUMERIC(20) NOT NULL DEFAULT 0,
`path` varchar(4000) DEFAULT NULL,
`path_hash` varchar(32) NOT NULL DEFAULT '',
`parent` NUMERIC(20) NOT NULL DEFAULT 0,
`name` varchar(250) DEFAULT NULL,
`mimetype` NUMERIC(20) NOT NULL DEFAULT 0,
`mimepart` NUMERIC(20) NOT NULL DEFAULT 0,
`size` NUMERIC(20) NOT NULL DEFAULT 0,
`mtime` NUMERIC(20) NOT NULL DEFAULT 0,
`storage_mtime` NUMERIC(20) NOT NULL DEFAULT 0,
`encrypted` INTEGER NOT NULL DEFAULT 0,
`unencrypted_size` NUMERIC(20) NOT NULL DEFAULT 0,
`etag` varchar(40) DEFAULT NULL,
`permissions` INTEGER DEFAULT 0,
`checksum` varchar(255) DEFAULT NULL,
PRIMARY KEY (`fileid`)
);
CREATE UNIQUE INDEX `fs_storage_path_hash` ON oc_filecache(`storage`,`path_hash`);
CREATE INDEX `fs_parent_name_hash` ON oc_filecache(`parent`,`name`);
CREATE INDEX `fs_storage_mimetype` ON oc_filecache(`storage`,`mimetype`);
CREATE INDEX `fs_storage_mimepart` ON oc_filecache(`storage`,`mimepart`);
CREATE INDEX `fs_storage_size` ON oc_filecache(`storage`,`size`,`fileid`);
CREATE INDEX `fs_id_storage_size` ON oc_filecache(`fileid`,`storage`,`size`);
CREATE INDEX `fs_parent` ON oc_filecache(`parent`);
CREATE INDEX `fs_mtime` ON oc_filecache(`mtime`);
CREATE INDEX `fs_size` ON oc_filecache(`size`);
CREATE INDEX `fs_storage_path_prefix` ON oc_filecache(`storage`,`path`);
DROP TABLE IF EXISTS oc_group_folders;
CREATE TABLE `oc_group_folders` (
`folder_id` NUMERIC(20) NOT NULL AUTO_INCREMENT,
`mount_point` varchar(4000) NOT NULL,
`quota` NUMERIC(20) NOT NULL DEFAULT -3,
`acl` INTEGER DEFAULT 0,
PRIMARY KEY (`folder_id`)
);
DROP TABLE IF EXISTS oc_mimetypes;
CREATE TABLE `oc_mimetypes` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`mimetype` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
);
CREATE UNIQUE INDEX `mimetype_id_index` ON oc_mimetypes(`mimetype`);
DROP TABLE IF EXISTS oc_mounts;
CREATE TABLE `oc_mounts` (
`id` NUMERIC(20) NOT NULL AUTO_INCREMENT,
`storage_id` NUMERIC(20) NOT NULL,
`root_id` NUMERIC(20) NOT NULL,
`user_id` varchar(64) NOT NULL,
`mount_point` varchar(4000) NOT NULL,
`mount_id` NUMERIC(20) DEFAULT NULL,
`mount_provider_class` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE INDEX `mounts_storage_index` ON oc_mounts(`storage_id`);
CREATE INDEX `mounts_root_index` ON oc_mounts(`root_id`);
CREATE INDEX `mounts_mount_id_index` ON oc_mounts(`mount_id`);
CREATE INDEX `mounts_user_root_path_index` ON oc_mounts(`user_id`,`root_id`,`mount_point`);
CREATE INDEX `mounts_class_index` ON oc_mounts(`mount_provider_class`);
CREATE INDEX `mount_user_storage` ON oc_mounts(`storage_id`,`user_id`);

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View file

@ -37,7 +37,7 @@ class DlnaService(
private val mediaServer: MediaServer,
private val serverInfoProvider: ServerInfoProvider,
) {
private val addressesToBind: List<InetAddress> = listOf(serverInfoProvider.address!!)
private val addressesToBind: List<String> = listOf(serverInfoProvider.host)
var upnpService = MyUpnpService(MyUpnpServiceConfiguration())
fun start() {
@ -91,7 +91,7 @@ class DlnaService(
multicastResponsePort: Int
) : NetworkAddressFactoryImpl(streamListenPort, multicastResponsePort) {
override fun isUsableAddress(iface: NetworkInterface, address: InetAddress) =
addressesToBind.contains(address)
addressesToBind.contains(address.hostAddress)
}
companion object : KLogging()

View file

@ -38,11 +38,16 @@ class ContentController(
logger.info("Serving content {} {}", request.method, id)
}
val fileSystemResource = FileSystemResource(item.path)
if (!fileSystemResource.exists()) {
logger.info("Could not find file for item id: {}", id)
ResponseEntity(HttpStatus.NOT_FOUND)
} else {
response.addHeader("Content-Type", item.format.mime)
response.addHeader("contentFeatures.dlna.org", makeProtocolInfo(item.format).toString())
response.addHeader("transferMode.dlna.org", "Streaming")
response.addHeader("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*")
ResponseEntity(fileSystemResource, HttpStatus.OK)
}
} ?: let {
logger.info("Could not find item id: {}", id)
ResponseEntity(HttpStatus.NOT_FOUND)

View file

@ -21,18 +21,20 @@ class MediaServer(
private val friendlyName: String,
externalUrls: ExternalUrls
) {
final val device = LocalDevice(
val device = LocalDevice(
DeviceIdentity(uniqueSystemIdentifier("Nextcloud-DLNA-MediaServer"), ADVERTISEMENT_AGE_IN_S),
UDADeviceType(DEVICE_TYPE, VERSION),
DeviceDetails(friendlyName, externalUrls.selfURI),
createDeviceIcon(),
arrayOf(contentDirectoryService, connectionManagerService)
)
val serviceIdentifier: String get() = device.identity.udn.identifierString
init {
logger.info("uniqueSystemIdentifier: {} ({})", device.identity.udn, friendlyName)
}
companion object : KLogging() {
const val ICON_FILENAME = "icon.png"
private const val DEVICE_TYPE = "MediaServer"

View file

@ -115,7 +115,7 @@ class ApacheStreamClient(
): Callable<StreamResponseMessage> {
return Callable<StreamResponseMessage> {
logger.trace("Sending HTTP request: $requestMessage")
httpClient.execute<StreamResponseMessage>(request, createResponseHandler(requestMessage))
httpClient.execute(request, createResponseHandler())
}
}
@ -141,7 +141,7 @@ class ApacheStreamClient(
clientConnectionManager.shutdown()
}
private fun createResponseHandler(requestMessage: StreamRequestMessage?): ResponseHandler<StreamResponseMessage> {
private fun createResponseHandler(): ResponseHandler<StreamResponseMessage> {
return ResponseHandler<StreamResponseMessage> { response ->
val statusLine = response.statusLine
logger.trace("Received HTTP response: $statusLine")

View file

@ -2,6 +2,7 @@ package net.schowek.nextclouddlna.nextcloud
import jakarta.annotation.PostConstruct
import mu.KLogging
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
@ -96,9 +97,9 @@ class NextcloudDB(
private fun buildPath(f: Filecache): String {
return if (storageUsersMap.containsKey(f.storage)) {
val userName: String? = storageUsersMap[f.storage]
"${nextcloudConfig.nextcloudDir}/$userName/${f.path}"
"${nextcloudConfig.nextcloudDir.absolutePath}/$userName/${f.path}"
} else {
"${nextcloudConfig.nextcloudDir}/${f.path}"
"${nextcloudConfig.nextcloudDir.absolutePath}/${f.path}"
}
}

View file

@ -0,0 +1,30 @@
package net.schowek.nextclouddlna.nextcloud.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.io.File
interface NextcloudAppPathProvider {
val nextcloudDir: File
}
@Component
@Profile("!integration")
class NextcloudAppPathsProviderImpl(
@Value("\${nextcloud.filesDir}")
private val dirPath: String,
) : NextcloudAppPathProvider {
override val nextcloudDir = ensureNextcloudDir(dirPath)
private fun ensureNextcloudDir(dirPath: String): File {
if (dirPath.isEmpty()) {
throw RuntimeException("No nextcloud data directory name provided")
}
return File(dirPath).also {
if (!it.exists() || !it.isDirectory) {
throw RuntimeException("Invalid nextcloud data directory specified")
}
}
}
}

View file

@ -1,31 +1,28 @@
package net.schowek.nextclouddlna.nextcloud
package net.schowek.nextclouddlna.nextcloud.config
import jakarta.annotation.PostConstruct
import mu.KLogging
import net.schowek.nextclouddlna.nextcloud.db.AppConfigRepository
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.io.File
import java.util.*
import java.util.Arrays.*
import java.util.Objects.*
@Component
class NextcloudConfigDiscovery(
@Value("\${nextcloud.filesDir}")
val nextcloudDir: String,
val appConfigRepository: AppConfigRepository
val appConfigRepository: AppConfigRepository,
val nextcloudDirProvider: NextcloudAppPathProvider
) {
final val appDataDir: String = findAppDataDir()
final val supportsGroupFolders: Boolean = checkGroupFoldersSupport()
val appDataDir: String = findAppDataDir()
val nextcloudDir: File get() = nextcloudDirProvider.nextcloudDir
val supportsGroupFolders: Boolean = checkGroupFoldersSupport()
private fun checkGroupFoldersSupport(): Boolean {
return "yes" == appConfigRepository.getValue("groupfolders", "enabled")
}
private fun findAppDataDir(): String {
return stream(requireNonNull(File(nextcloudDir).listFiles { f ->
return stream(requireNonNull(nextcloudDir.listFiles { f ->
f.isDirectory && f.name.matches(APPDATA_NAME_PATTERN.toRegex())
})).findFirst().orElseThrow().name
.also {

View file

@ -10,8 +10,8 @@ class ExternalUrls(
) {
val selfUriString: String =
when (serverInfoProvider.port) {
80 -> "http://${serverInfoProvider.address!!.hostAddress}"
else -> "http://${serverInfoProvider.address!!.hostAddress}:${serverInfoProvider.port}"
80 -> "http://${serverInfoProvider.host}"
else -> "http://${serverInfoProvider.host}:${serverInfoProvider.port}"
}

View file

@ -3,15 +3,23 @@ package net.schowek.nextclouddlna.util
import jakarta.annotation.PostConstruct
import mu.KLogging
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.net.*
import java.util.*
interface ServerInfoProvider {
val host: String
val port: Int
}
@Component
class ServerInfoProvider(
@param:Value("\${server.port}") val port: Int,
@Profile("!integration")
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
@PostConstruct
@ -22,9 +30,11 @@ class ServerInfoProvider(
private fun guessInetAddress(): InetAddress {
return try {
val en0 = NetworkInterface.getByName(networkInterface).inetAddresses
while (en0.hasMoreElements()) {
val x = en0.nextElement()
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
}

View file

@ -4,7 +4,7 @@ server:
friendlyName: ${NEXTCLOUD_DLNA_FRIENDLY_NAME:Nextcloud-DLNA}
nextcloud:
filesDir: ${NEXTCLOUD_DATA_DIR:/path/to/your/nextcloud/dir/ending/with/data}
filesDir: ${NEXTCLOUD_DATA_DIR}
spring:
datasource:
@ -15,3 +15,7 @@ spring:
jpa:
hibernate:
ddl-auto: none
sql:
init:
mode: never

View file

@ -3,17 +3,12 @@ package net.schowek.nextclouddlna.util
import spock.lang.Specification
class ExternalUrlsTest extends Specification {
def inetAddress = Mock(InetAddress)
def serverInfoProvider = Mock(ServerInfoProvider)
def setup() {
serverInfoProvider.address >> inetAddress
}
def "should generate main url for the service"() {
given:
inetAddress.getHostAddress() >> host
serverInfoProvider.getPort() >> port
serverInfoProvider.getHost() >> host
def sut = new ExternalUrls(serverInfoProvider)
when:
@ -30,8 +25,8 @@ class ExternalUrlsTest extends Specification {
def "should generate content urls"() {
given:
inetAddress.getHostAddress() >> host
serverInfoProvider.getPort() >> port
serverInfoProvider.getHost() >> host
def sut = new ExternalUrls(serverInfoProvider)
when: