feature: minidlna app

Closes #1679

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Nektarios Katakis 2019-11-12 23:52:21 +00:00 committed by James Valleroy
parent e1f9dfacaa
commit ef5f5a21de
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
10 changed files with 495 additions and 0 deletions

106
actions/minidlna Executable file
View File

@ -0,0 +1,106 @@
#!/usr/bin/python3
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Configuration actions for the minidlna server.
"""
import argparse
from tempfile import mkstemp
from shutil import move
from os import fdopen, remove
import augeas
from plinth.utils import grep
config_path = '/etc/minidlna.conf'
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('setup', help='Setup SSH server')
subparsers.add_parser('get-media-dir', help='Get media directory')
set_media_dir = subparsers.add_parser('set-media-dir',
help='Set custom media directory')
set_media_dir.add_argument('--dir')
subparsers.required = True
return parser.parse_args()
def subcommand_setup(arguments):
"""
Increase inotify watches per folder to allow minidlna to
monitor changes in large media-dirs.
"""
aug = augeas.Augeas(
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
aug.set('/augeas/load/Sysctl/lens', 'Sysctl.lns')
aug.set('/augeas/load/Sysctl/incl[last() + 1]', '/etc/sysctl.conf')
aug.load()
aug.set('/files/etc/sysctl.conf/fs.inotify.max_user_watches', '100000')
aug.save()
def subcommand_get_media_dir(arguments):
"""Retrieve media directory from minidlna.conf"""
line = grep('^media_dir=', config_path)
print(line[0].split("=")[1])
def subcommand_set_media_dir(arguments):
"""Set media directory in minidlna.conf"""
line = grep('^media_dir=', config_path)[0]
new_line = 'media_dir=%s\n' % arguments.dir
replace_in_config_file(config_path, line, new_line)
def replace_in_config_file(file_path, pattern, subst):
"""
Create a temporary minidlna.conf file,
replace the media dir config,
remove original one and move the temporary file.
"""
temp_file, temp_file_path = mkstemp()
with fdopen(temp_file, 'w') as new_file:
with open(file_path) as old_file:
for line in old_file:
new_file.write(line.replace(pattern, subst))
remove(file_path)
move(temp_file_path, file_path)
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,116 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
FreedomBox app to configure minidlna.
"""
from django.utils.translation import ugettext_lazy as _
from plinth import actions
import plinth.app as app_module
from plinth import frontpage, menu
from plinth.modules.apache.components import Webserver
from plinth.modules.firewall.components import Firewall
from plinth.modules.users import register_group
from plinth.action_utils import diagnose_url
from .manifest import backup, clients # noqa
version = 1
name = 'minidlna'
icon_name = name
managed_packages = ['minidlna']
short_description = _('Simple Media Server')
description = [
_('MiniDLNA is a simple media server software, with the aim of being '
'fully compliant with DLNA/UPnP-AV clients. '
'The MiniDNLA daemon serves media files '
'(music, pictures, and video) to clients on a network. '
'DNLA/UPnP is zero configuration protocol and is compliant '
'with any device passing the DLNA Certification like portable '
'media players, Smartphones, Televisions, and gaming systems ('
'such as PS3 and Xbox 360) or applications such as totem and Kodi.')
]
clients = clients
group = ('minidlna', _('Media streaming server'))
app = None
class MiniDLNAApp(app_module.App):
"""Freedombox app managing miniDlna"""
app_id = 'minidlna'
def __init__(self):
"""Initialize the app components"""
super().__init__()
menu_item = menu.Menu(
'menu-minidlna',
name=name,
short_description=short_description,
url_name='minidlna:index',
parent_url_name='apps',
icon=icon_name,
)
firewall = Firewall('firewall-minidlna', name, ports=['minidlna'],
is_external=False)
webserver = Webserver('webserver-minidlna', 'minidlna-plinth')
shortcut = frontpage.Shortcut(
'shortcut-minidlna',
name,
short_description=short_description,
description=description,
icon=icon_name,
url='/_minidlna/',
login_required=True,
)
self.add(menu_item)
self.add(webserver)
self.add(firewall)
self.add(shortcut)
def init():
global app
app = MiniDLNAApp()
register_group(group)
setup_helper = globals()['setup_helper']
if setup_helper.get_state() != 'needs-setup' and app.is_enabled():
app.set_enabled(True)
def setup(helper, old_version=None):
"""Install and configure the package"""
helper.install(managed_packages)
helper.call('post', actions.superuser_run, 'minidlna', ['setup'])
helper.call('post', app.enable)
def diagnose():
"""Check if the http page listening on 8200 is accessible"""
results = []
results.append(diagnose_url('http://localhost:8200/'))
return results

View File

@ -0,0 +1,3 @@
<Location /_minidlna/>
ProxyPass http://localhost:8200/
</Location>

View File

@ -0,0 +1 @@
plinth.modules.minidlna

View File

@ -0,0 +1,39 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
FreedomBox configuration form for MiniDLNA server.
"""
from django import forms
from django.utils.translation import ugettext_lazy as _
from plinth.forms import AppForm
class MiniDLNAServerForm(AppForm):
"""MiniDLNA server configuration form."""
media_dir = forms.CharField(
label=_('Media Files Directory'),
help_text=_('Directory that MiniDLNA Server will read for content. All'
' sub-directories of this will be also scanned for media '
'files. '
'If you change the default ensure that the new directory '
'exists and that is readable from the "minidlna" user. '
'Any user media directories ("/home/username/") will '
'usually work.'),
required=False,
)

View File

@ -0,0 +1,138 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from django.utils.translation import ugettext_lazy as _
from plinth.modules.backups.api import validate as validate_backup
from plinth.clients import validate, store_url
clients = validate([
{
'name': _('vlc'),
'platforms': [
{
'type': 'package',
'os': 'gnu-linux',
'format': 'deb',
'name': 'vlc',
},
{
'type': 'package',
'os': 'gnu-linux',
'format': 'rpm',
'name': 'vlc',
},
{
'type': 'download',
'os': 'windows',
'url': 'https://www.videolan.org/vlc/download-windows.html',
},
{
'type': 'download',
'os': 'macos',
'url': 'https://www.videolan.org/vlc/download-macosx.html',
},
{
'type': 'store',
'os': 'android',
'store_name': 'google-play',
'url': store_url('google-play', 'org.videolan.vlc')
},
{
'type': 'store',
'os': 'android',
'store_name': 'f-droid',
'url': store_url('f-droid', 'org.videolan.vlc')
},
]
},
{
'name': _('kodi'),
'platforms': [
{
'type': 'package',
'os': 'gnu-linux',
'format': 'deb',
'name': 'kodi',
},
{
'type': 'package',
'os': 'gnu-linux',
'format': 'rpm',
'name': 'kodi',
},
{
'type': 'download',
'os': 'windows',
'url': 'http://kodi.tv/download/',
},
{
'type': 'download',
'os': 'macos',
'url': 'http://kodi.tv/download/',
},
{
'type': 'store',
'os': 'android',
'store_name': 'google-play',
'url': store_url('google-play', 'org.xbmc.kodi')
},
{
'type': 'store',
'os': 'android',
'store_name': 'f-droid',
'url': store_url('f-droid', 'org.xbmc.kodi')
},
]
},
{
'name': _('yaacc'),
'platforms': [
{
'type': 'store',
'os': 'android',
'store_name': 'f-droid',
'url': store_url('f-droid', 'de.yaacc')
},
]
},
{
'name': _('totem'),
'platforms': [
{
'type': 'package',
'os': 'gnu-linux',
'format': 'deb',
'name': 'totem',
},
{
'type': 'package',
'os': 'gnu-linux',
'format': 'rpm',
'name': 'totem',
},
]
},
])
# TODO: get all media directories from config file
# for now hard code default media folder.
backup = validate_backup({
'data': {
'directories': ['/var/lib/minidlna']
}
})

View File

@ -0,0 +1,27 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
URLs for the minidlna Server module.
"""
from django.conf.urls import url
from plinth.modules.minidlna.views import MiniDLNAAppView
urlpatterns = [
url(r'^apps/minidlna/$', MiniDLNAAppView.as_view(), name='index'),
]

View File

@ -0,0 +1,65 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Views for the minidlna module
"""
import os
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth.views import AppView
from plinth.modules import minidlna
from .forms import MiniDLNAServerForm
class MiniDLNAAppView(AppView):
app_id = 'minidlna'
name = minidlna.name
description = minidlna.description
form_class = MiniDLNAServerForm
diagnostics_module_name = 'minidlna'
def get_initial(self):
"""Initial form value as found in the minidlna.conf"""
initial = super().get_initial()
initial.update({
'media_dir': actions.superuser_run('minidlna', ['get-media-dir']),
})
return initial
def form_valid(self, form):
"""Apply changes from the form"""
old_config = form.initial
new_config = form.cleaned_data
if old_config['media_dir'].strip() != new_config['media_dir']:
if os.path.isdir(new_config['media_dir']) is False:
messages.error(self.request,
_('Specified directory does not exist.'))
else:
actions.superuser_run(
'minidlna',
['set-media-dir', '--dir', new_config['media_dir']]
)
actions.superuser_run('service', ['restart', 'minidlna'])
messages.success(self.request, _('Updated media directory'))
return super().form_valid(form)

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB