commit
2a33a89722
6 changed files with 137 additions and 42 deletions
30
README.md
30
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 |
|
||||
| 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<br/>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
|
||||
### 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
|
||||
|
|
|
@ -50,10 +50,8 @@ enum class MediaFormat(
|
|||
}
|
||||
|
||||
companion object {
|
||||
val EXT_TO_FORMAT: Map<String, MediaFormat> = 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") }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,27 +19,27 @@ 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
|
||||
private val address: InetAddress = getInetAddress()
|
||||
|
||||
@PostConstruct
|
||||
fun init() {
|
||||
address = guessInetAddress()
|
||||
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 {
|
||||
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
|
||||
}
|
||||
private fun getInetAddress(): InetAddress {
|
||||
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<Inet4Address>().first()
|
||||
} else {
|
||||
logger.info { "No network interface name given, trying to use default local address" }
|
||||
guessLocalAddress()
|
||||
}.also {
|
||||
logger.debug { "Found local address ${it.hostAddress}" }
|
||||
}
|
||||
InetAddress.getLocalHost()
|
||||
} catch (e: UnknownHostException) {
|
||||
throw RuntimeException(e)
|
||||
} catch (e: SocketException) {
|
||||
|
@ -47,5 +47,18 @@ 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)
|
||||
s.localAddress
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn { e.message }
|
||||
InetAddress.getLocalHost()
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue