merge trunk

This commit is contained in:
Jonathan Springer 2015-12-13 19:21:58 -05:00
commit 0a935bdb90
5 changed files with 157 additions and 19 deletions

View File

@ -514,17 +514,17 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
:param display: Which display to use
:param service_item: The ServiceItem containing the details to be played.
"""
used_players = get_media_players()
default_player = used_players[0]
used_players = get_media_players()[0]
# If no player, we can't play
if not used_players:
return False
default_player = [used_players[0]]
if service_item.processor and service_item.processor != UiStrings().Automatic:
# check to see if the player is usable else use the default one.
if not service_item.processor.lower() in used_players:
used_players = default_player
else:
used_players = [service_item.processor.lower()]
# If no player, we can't play
if not used_players:
return False
if controller.media_info.file_info.isFile():
suffix = '*.%s' % controller.media_info.file_info.suffix().lower()
for title in used_players:

View File

@ -149,6 +149,7 @@ class HttpRouter(RegistryProperties):
self.routes = [
('^/$', {'function': self.serve_file, 'secure': False}),
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
@ -169,6 +170,7 @@ class HttpRouter(RegistryProperties):
self.settings_section = 'remotes'
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
def do_post_processor(self):
"""
@ -339,24 +341,32 @@ class HttpRouter(RegistryProperties):
'settings': translate('RemotePlugin.Mobile', 'Settings'),
}
def serve_file(self, file_name=None):
def stages(self, url_path, file_name):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Allow Stage view to be delivered with custom views.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
:param url_path: base path of the URL. Not used but passed by caller
:param file_name: file name with path
:return:
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
parts = file_name.split('/')
if len(parts) == 1:
file_name = os.path.join(parts[0], 'stage.html')
elif len(parts) == 3:
file_name = os.path.join(parts[1], parts[2])
path = os.path.normpath(os.path.join(self.config_dir, file_name))
if not path.startswith(self.config_dir):
return self.do_not_found()
return self._process_file(path)
def _process_file(self, path):
"""
Common file processing code
:param path: path to file to be loaded
:return: web resource to be loaded
"""
content = None
ext, content_type = self.get_content_type(path)
file_handle = None
@ -379,10 +389,32 @@ class HttpRouter(RegistryProperties):
self.end_headers()
return content
def serve_file(self, file_name=None):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
return self.do_not_found()
return self._process_file(path)
def get_content_type(self, file_name):
"""
Examines the extension of the file and determines what the content_type should be, defaults to text/plain
Returns the extension and the content_type
:param file_name: name of file
"""
ext = os.path.splitext(file_name)[1]
content_type = FILE_TYPES.get(ext, 'text/plain')
@ -391,6 +423,10 @@ class HttpRouter(RegistryProperties):
def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
"""
Serve an image file. If not found return 404.
:param file_name: file name to be served
:param dimensions: image size
:param controller_name: controller to be called
"""
log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name))
supported_controllers = ['presentations', 'images']
@ -495,6 +531,8 @@ class HttpRouter(RegistryProperties):
def controller_text(self, var):
"""
Perform an action on the slide controller.
:param var: variable - not used
"""
log.debug("controller_text var = %s" % var)
current_item = self.live_controller.service_item
@ -628,6 +666,8 @@ class HttpRouter(RegistryProperties):
def go_live(self, plugin_name):
"""
Go live on an item of type ``plugin``.
:param plugin_name: name of plugin
"""
try:
request_id = json.loads(self.request_data)['request']['id']
@ -641,6 +681,8 @@ class HttpRouter(RegistryProperties):
def add_to_service(self, plugin_name):
"""
Add item of type ``plugin_name`` to the end of the service.
:param plugin_name: name of plugin to be called
"""
try:
request_id = json.loads(self.request_data)['request']['id']

View File

@ -167,7 +167,7 @@ class HTTPSServer(HTTPServer):
local_data = AppLocation.get_directory(AppLocation.DataDir)
self.socket = ssl.SSLSocket(
sock=socket.socket(self.address_family, self.socket_type),
ssl_version=ssl.PROTOCOL_TLSv1,
ssl_version=ssl.PROTOCOL_TLSv1_2,
certfile=os.path.join(local_data, 'remotes', 'openlp.crt'),
keyfile=os.path.join(local_data, 'remotes', 'openlp.key'),
server_side=True)

View File

@ -120,6 +120,66 @@ class TestMediaController(TestCase, TestMixin):
# THEN: it should return False
self.assertFalse(ret, '_check_file_type should return False when the processor for service_item is None.')
@patch('openlp.core.ui.media.mediacontroller.get_media_players')
@patch('openlp.core.ui.media.mediacontroller.UiStrings')
def check_file_type_automatic_processor_test(self, mocked_uistrings, mocked_get_media_players):
"""
Test that we can play media when players are available and we have a automatic processor from the service item
"""
# GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item
mocked_get_media_players.return_value = (['vlc', 'webkit'], '')
mocked_ret_uistrings = MagicMock()
mocked_ret_uistrings.Automatic = 1
mocked_uistrings.return_value = mocked_ret_uistrings
media_controller = MediaController()
mocked_vlc = MagicMock()
mocked_vlc.video_extensions_list = ['*.mp4']
media_controller.media_players = {'vlc': mocked_vlc, 'webkit': MagicMock()}
mocked_controller = MagicMock()
mocked_suffix = MagicMock()
mocked_suffix.return_value = 'mp4'
mocked_controller.media_info.file_info.suffix = mocked_suffix
mocked_display = MagicMock()
mocked_service_item = MagicMock()
mocked_service_item.processor = 1
# WHEN: calling _check_file_type when the processor for the service item is None
ret = media_controller._check_file_type(mocked_controller, mocked_display, mocked_service_item)
# THEN: it should return True
self.assertTrue(ret, '_check_file_type should return True when mediaplayers are available and '
'the service item has an automatic processor.')
@patch('openlp.core.ui.media.mediacontroller.get_media_players')
@patch('openlp.core.ui.media.mediacontroller.UiStrings')
def check_file_type_processor_different_from_available_test(self, mocked_uistrings, mocked_get_media_players):
"""
Test that we can play media when players available are different from the processor from the service item
"""
# GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item
mocked_get_media_players.return_value = (['phonon'], '')
mocked_ret_uistrings = MagicMock()
mocked_ret_uistrings.Automatic = 'automatic'
mocked_uistrings.return_value = mocked_ret_uistrings
media_controller = MediaController()
mocked_phonon = MagicMock()
mocked_phonon.video_extensions_list = ['*.mp4']
media_controller.media_players = {'phonon': mocked_phonon}
mocked_controller = MagicMock()
mocked_suffix = MagicMock()
mocked_suffix.return_value = 'mp4'
mocked_controller.media_info.file_info.suffix = mocked_suffix
mocked_display = MagicMock()
mocked_service_item = MagicMock()
mocked_service_item.processor = 'vlc'
# WHEN: calling _check_file_type when the processor for the service item is None
ret = media_controller._check_file_type(mocked_controller, mocked_display, mocked_service_item)
# THEN: it should return True
self.assertTrue(ret, '_check_file_type should return True when the players available are different'
'from the processor from the service item.')
def media_play_msg_test(self):
"""
Test that the media controller responds to the request to play a loaded video

View File

@ -343,3 +343,39 @@ class TestRouter(TestCase, TestMixin):
# THEN: service_manager.next_item() should have been called
self.assertTrue(mocked_previous_item.called, 'previous_item() should have been called in service_manager')
def remote_stage_personal_html_test(self):
"""
Test the stage url with a parameter after loaded a url/stage.html file
"""
# GIVEN: initial route
self.router.config_dir = ''
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router._process_file = MagicMock()
# WHEN: I call stage with a suffix
self.router.stages('stages', 'trb')
# THEN: we should use the specific stage file instance
self.router._process_file.assert_called_with(os.path.join('trb', 'stage.html'))
def remote_stage_personal_css_test(self):
"""
Test the html with reference stages/trb/trb.css then loaded a stages/trb/trb.css file
"""
# GIVEN: initial route
self.router.config_dir = ''
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router._process_file = MagicMock()
# WHEN: I call stage with a suffix
self.router.stages('stages', 'stages/trb/trb.css')
# THEN: we should use the specific stage file instance
self.router._process_file.assert_called_with(os.path.join('trb', 'trb.css'))