def set_up_fault_handling(): """ Set up the Python fault handler """ # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file create_paths(AppLocation.get_directory(AppLocation.CacheDir)) faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
def __init__(self, plugin=None, name='PresentationController', document_class=PresentationDocument): """ This is the constructor for the presentationcontroller object. This provides an easy way for descendent plugins to populate common data. This method *must* be overridden, like so:: class MyPresentationController(PresentationController): def __init__(self, plugin): PresentationController.__init( self, plugin, 'My Presenter App') :param plugin: Defaults to *None*. The presentationplugin object :param name: Name of the application, to appear in the application :param document_class: """ self.supports = [] self.also_supports = [] self.docs = [] self.plugin = plugin self.name = name self.document_class = document_class self.settings_section = self.plugin.settings_section self.available = None self.temp_folder = AppLocation.get_section_data_path( self.settings_section) / name self.thumbnail_folder = AppLocation.get_section_data_path( self.settings_section) / 'thumbnails' self.thumbnail_prefix = 'slide' create_paths(self.thumbnail_folder, self.temp_folder)
def check_installed(self): """ Check the viewer is installed. :return: True if program to open PDF-files was found, otherwise False. """ log.debug('check_installed Pdf') self.mudrawbin = None self.mutoolbin = None self.gsbin = None self.also_supports = [] # Use the user defined program if given if Registry().get('settings').value( 'presentations/enable_pdf_program'): program_path = Registry().get('settings').value( 'presentations/pdf_program') program_type = self.process_check_binary(program_path) if program_type == 'gs': self.gsbin = program_path elif program_type == 'mudraw': self.mudrawbin = program_path elif program_type == 'mutool': self.mutoolbin = program_path elif PYMUPDF_AVAILABLE: self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2'] return True else: # Fallback to autodetection application_path = AppLocation.get_directory(AppLocation.AppDir) if is_win(): # for windows we only accept mudraw.exe or mutool.exe in the base folder if (application_path / 'mudraw.exe').is_file(): self.mudrawbin = application_path / 'mudraw.exe' elif (application_path / 'mutool.exe').is_file(): self.mutoolbin = application_path / 'mutool.exe' else: # First try to find mudraw self.mudrawbin = which('mudraw') # if mudraw isn't installed, try mutool if not self.mudrawbin: self.mutoolbin = which('mutool') # Check we got a working mutool if not self.mutoolbin or self.process_check_binary( self.mutoolbin) != 'mutool': self.gsbin = which('gs') # Last option: check if mudraw or mutool is placed in OpenLP base folder if not self.mudrawbin and not self.mutoolbin and not self.gsbin: application_path = AppLocation.get_directory( AppLocation.AppDir) if (application_path / 'mudraw').is_file(): self.mudrawbin = application_path / 'mudraw' elif (application_path / 'mutool').is_file(): self.mutoolbin = application_path / 'mutool' if self.mudrawbin or self.mutoolbin: self.also_supports = ['xps', 'oxps', 'epub', 'cbz', 'fb2'] return True elif self.gsbin: return True return False
def _update_background_audio(self, song, item): song.media_files = [] for i, bga in enumerate(item.background_audio): dest_path =\ AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga)[1] create_paths(dest_path.parent) copyfile(AppLocation.get_section_data_path('servicemanager') / bga, dest_path) song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path)) self.plugin.manager.save_object(song, True)
def initialise(): """ Create the internal file structure if it does not exist :return: """ create_paths( AppLocation.get_section_data_path('remotes') / 'assets', AppLocation.get_section_data_path('remotes') / 'images', AppLocation.get_section_data_path('remotes') / 'static', AppLocation.get_section_data_path('remotes') / 'static' / 'index', AppLocation.get_section_data_path('remotes') / 'templates')
def download_and_check(callback=None): """ Download the web site and deploy it. """ sha256, version = download_sha256() file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip') callback.setRange(0, file_size) if download_file(callback, 'https://get.openlp.org/webclient/site.zip', AppLocation.get_section_data_path('remotes') / 'site.zip', sha256=sha256): deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'site.zip')
def _download_selected(self): """ Download selected songs, bibles and themes. Returns False on download error """ # Build directories for downloads songs_destination_path = Path(gettempdir(), 'openlp') bibles_destination_path = AppLocation.get_section_data_path('bibles') themes_destination_path = AppLocation.get_section_data_path('themes') missed_files = [] # Download songs for i in range(self.songs_list_widget.count()): item = self.songs_list_widget.item(i) if item.checkState() == QtCore.Qt.Checked: filename, sha256 = item.data(QtCore.Qt.UserRole) self._increment_progress_bar(self.downloading.format(name=filename), 0) self.previous_size = 0 destination = songs_destination_path / str(filename) if not download_file(self, '{path}{name}'.format(path=self.songs_url, name=filename), destination, sha256): missed_files.append('Song: {name}'.format(name=filename)) # Download Bibles bibles_iterator = QtWidgets.QTreeWidgetItemIterator(self.bibles_tree_widget) while bibles_iterator.value(): item = bibles_iterator.value() if item.parent() and item.checkState(0) == QtCore.Qt.Checked: bible, sha256 = item.data(0, QtCore.Qt.UserRole) self._increment_progress_bar(self.downloading.format(name=bible), 0) self.previous_size = 0 if not download_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible), bibles_destination_path / bible, sha256): missed_files.append('Bible: {name}'.format(name=bible)) bibles_iterator += 1 # Download themes for item in self.themes_list_widget.selectedItems(): self._increment_progress_bar(self.downloading.format(name=item.file_name), 0) self.previous_size = 0 if not download_file(self, '{url}{file}'.format(url=self.themes_url, file=item.file_name), themes_destination_path / item.file_name, item.sha256): missed_files.append('Theme: name'.format(name=item.file_name)) if missed_files: file_list = '' for entry in missed_files: file_list += '{text}<br \\>'.format(text=entry) msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Warning) msg.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'Network Error')) msg.setText(translate('OpenLP.FirstTimeWizard', 'Unable to download some files')) msg.setInformativeText(translate('OpenLP.FirstTimeWizard', 'The following files were not able to be ' 'downloaded:<br \\>{text}'.format(text=file_list))) msg.setStandardButtons(msg.Ok) msg.exec() return True
def find_qm_files(): """ Find all available language files in this OpenLP install """ log.debug('Translation files: {files}'.format( files=AppLocation.get_directory(AppLocation.LanguageDir))) trans_dir = QtCore.QDir( str(AppLocation.get_directory(AppLocation.LanguageDir))) file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name) # Remove qm files from the list which start with "qt". file_names = [ file_ for file_ in file_names if not file_.startswith('qt') ] return list(map(trans_dir.filePath, file_names))
def upgrade_2(session, metadata): """ Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev """ log.debug('Starting upgrade_2 for file_path to JSON') old_table = Table('image_filenames', metadata, autoload=True) if 'file_path' not in [col.name for col in old_table.c.values()]: op = get_upgrade_op(session) op.add_column('image_filenames', Column('file_path', PathType())) conn = op.get_bind() results = conn.execute('SELECT * FROM image_filenames') data_path = AppLocation.get_data_path() for row in results.fetchall(): file_path_json = json.dumps(Path(row.filename), cls=OpenLPJsonEncoder, base_path=data_path) sql = 'UPDATE image_filenames SET file_path = \'{file_path_json}\' WHERE id = {id}'.format( file_path_json=file_path_json, id=row.id) conn.execute(sql) # Drop old columns if metadata.bind.url.get_dialect().name == 'sqlite': drop_columns(op, 'image_filenames', [ 'filename', ]) else: op.drop_constraint('image_filenames', 'foreignkey') op.drop_column('image_filenames', 'filenames')
def get_version(): """ Returns the application version of the running instance of OpenLP:: {'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'} """ global APPLICATION_VERSION if APPLICATION_VERSION: return APPLICATION_VERSION file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version' try: full_version = file_path.read_text().rstrip() except OSError: log.exception('Error in version file.') full_version = '0.0.0' bits = full_version.split('-') APPLICATION_VERSION = { 'full': full_version, 'version': bits[0], 'build': bits[1] if len(bits) > 1 else None } if APPLICATION_VERSION['build']: log.info('Openlp version {version} build {build}'.format( version=APPLICATION_VERSION['version'], build=APPLICATION_VERSION['build'])) else: log.info('Openlp version {version}'.format( version=APPLICATION_VERSION['version'])) return APPLICATION_VERSION
def get_version(): """ Returns the application version of the running instance of OpenLP:: {'full': '2.9.0.dev2963+97ba02d1f', 'version': '2.9.0', 'build': '97ba02d1f'} or {'full': '2.9.0', 'version': '2.9.0', 'build': None} """ global APPLICATION_VERSION if APPLICATION_VERSION: return APPLICATION_VERSION file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version' try: full_version = file_path.read_text().rstrip() except OSError: log.exception('Error in version file.') full_version = '0.0.0' bits = full_version.split('.dev') APPLICATION_VERSION = { 'full': full_version, 'version': bits[0], 'build': full_version.split('+')[1] if '+' in full_version else None } if APPLICATION_VERSION['build']: log.info('OpenLP version {version} build {build}'.format( version=APPLICATION_VERSION['version'], build=APPLICATION_VERSION['build'])) else: log.info('OpenLP version {version}'.format( version=APPLICATION_VERSION['version'])) return APPLICATION_VERSION
def update_preview_text(self): """ Creates the html text and updates the html of *self.document*. """ html_data = self._add_element('html') self._add_element('head', parent=html_data) self._add_element('title', self.title_line_edit.text(), html_data.head) css_path = AppLocation.get_data_path() / 'serviceprint' / 'service_print.css' custom_css = get_text_file_string(css_path) if not custom_css: custom_css = DEFAULT_CSS self._add_element('style', custom_css, html_data.head, attribute=('type', 'text/css')) self._add_element('body', parent=html_data) self._add_element('h1', html.escape(self.title_line_edit.text()), html_data.body, class_id='serviceTitle') for index, item in enumerate(self.service_manager.service_items): self._add_preview_item(html_data.body, item['service_item'], index) if not self.show_chords_check_box.isChecked(): # Remove chord row and spacing span elements when not printing chords for chord_row in html_data.find_class('chordrow'): chord_row.drop_tree() for spacing_span in html_data.find_class('chordspacing'): spacing_span.drop_tree() # Add the custom service notes: if self.footer_text_edit.toPlainText(): div = self._add_element('div', parent=html_data.body, class_id='customNotes') self._add_element( 'span', translate('OpenLP.ServiceManager', 'Service Notes: '), div, class_id='customNotesTitle') self._add_element('span', html.escape(self.footer_text_edit.toPlainText()), div, class_id='customNotesText') self.document.setHtml(lxml.html.tostring(html_data).decode()) self.preview_widget.updatePreview()
def on_clone_click(self): """ Clone a Song """ log.debug('on_clone_click') if check_item_selected(self.list_view, UiStrings().SelectEdit): self.edit_item = self.list_view.currentItem() item_id = self.edit_item.data(QtCore.Qt.UserRole) old_song = self.plugin.manager.get_object(Song, item_id) song_xml = self.open_lyrics.song_to_xml(old_song) new_song = self.open_lyrics.xml_to_song(song_xml) new_song.title = '{title} <{text}>'.format(title=new_song.title, text=translate('SongsPlugin.MediaItem', 'copy', 'For song cloning')) # Copy audio files from the old to the new song if len(old_song.media_files) > 0: save_path = AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(new_song.id) create_paths(save_path) for media_file in old_song.media_files: new_media_file_path = save_path / media_file.file_path.name copyfile(media_file.file_path, new_media_file_path) new_media_file = MediaFile() new_media_file.file_path = new_media_file_path new_media_file.type = media_file.type new_media_file.weight = media_file.weight new_song.media_files.append(new_media_file) self.plugin.manager.save_object(new_song) self.on_song_list_load()
def is_data_path_missing(): """ Check if the data folder path exists. """ data_folder_path = AppLocation.get_data_path() if not data_folder_path.exists(): log.critical('Database was not found in: %s', data_folder_path) status = QtWidgets.QMessageBox.critical( None, translate('OpenLP', 'Data Directory Error'), translate( 'OpenLP', 'OpenLP data folder was not found in:\n\n{path}\n\nThe location of the data folder ' 'was previously changed from the OpenLP\'s default location. If the data was ' 'stored on removable device, that device needs to be made available.\n\nYou may ' 'reset the data location back to the default location, or you can try to make the ' 'current location available.\n\nDo you want to reset to the default data location? ' 'If not, OpenLP will be closed so you can try to fix the the problem.' ).format(path=data_folder_path), QtWidgets.QMessageBox.StandardButtons( QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), QtWidgets.QMessageBox.No) if status == QtWidgets.QMessageBox.No: # If answer was "No", return "True", it will shutdown OpenLP in def main log.info('User requested termination') return True # If answer was "Yes", remove the custom data path thus resetting the default location. Settings().remove('advanced/data path') log.info( 'Database location has been reset to the default settings.') return False
def add_from_command(self, path, file_name, image, display_title=None, notes=None): """ Add a slide from a command. :param path: The title of the slide in the service item. :param file_name: The title of the slide in the service item. :param image: The command of/for the slide. :param display_title: Title to show in gui/webinterface, optional. :param notes: Notes to show in the webinteface, optional. """ self.service_item_type = ServiceItemType.Command # If the item should have a display title but this frame doesn't have one, we make one up if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title: display_title = translate('OpenLP.ServiceItem', '[slide {frame:d}]').format(frame=len(self.slides) + 1) # Update image path to match servicemanager location if file was loaded from service if image and not self.has_original_files and self.name == 'presentations': file_location = os.path.join(path, file_name) file_location_hash = md5_hash(file_location.encode('utf-8')) image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails', file_location_hash, ntpath.basename(image)) # TODO: Pathlib self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title, 'notes': notes, 'thumbnail': image}) # if self.is_capable(ItemCapabilities.HasThumbnails): # self.image_manager.add_image(image, ImageSource.CommandPlugins, '#000000') self._new_item()
def on_data_directory_path_edit_path_changed(self, new_path): """ Handle the `editPathChanged` signal of the data_directory_path_edit :param pathlib.Path new_path: The new path :rtype: None """ # Make sure they want to change the data. answer = QtWidgets.QMessageBox.question( self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'), translate( 'OpenLP.AdvancedTab', 'Are you sure you want to change the ' 'location of the OpenLP data directory to:\n\n{path}' '\n\nThe data directory will be changed when OpenLP is ' 'closed.').format(path=new_path), defaultButton=QtWidgets.QMessageBox.No) if answer != QtWidgets.QMessageBox.Yes: self.data_directory_path_edit.path = AppLocation.get_data_path() return # Check if data already exists here. self.check_data_overwrite(new_path) # Save the new location. self.main_window.new_data_path = new_path self.data_directory_cancel_button.show()
def load_themes(self): """ Loads the theme lists and triggers updates across the whole system using direct calls or core functions and events for the plugins. The plugins will call back in to get the real list if they want it. """ self._theme_list.clear() self.theme_list_widget.clear() files = AppLocation.get_files(self.settings_section, '/*.json') # Sort the themes by its name considering language specific files.sort(key=lambda file_name: get_locale_key(str(file_name))) # now process the file list of png files for file in files: # check to see file is in theme root directory theme_path = self.theme_path / file if theme_path.exists(): text_name = theme_path.stem if text_name == self.global_theme: name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name) else: name = text_name thumb_path = self.thumb_path / '{name}.png'.format( name=text_name) item_name = QtWidgets.QListWidgetItem(name) if validate_thumb(theme_path, thumb_path): icon = build_icon(thumb_path) else: icon = create_thumb(theme_path, thumb_path) item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, text_name) self.theme_list_widget.addItem(item_name) self._theme_list[text_name] = self._get_theme_data(text_name) self._push_themes()
def get_db_path(plugin_name, db_file_name=None): """ Create a path to a database from the plugin name and database name :param plugin_name: Name of plugin :param pathlib.Path | str | None db_file_name: File name of database :return: The path to the database :rtype: str """ if db_file_name is None: return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name), plugin=plugin_name) elif os.path.isabs(db_file_name): return 'sqlite:///{db_file_name}'.format(db_file_name=db_file_name) else: return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name)
def extension_loader(glob_pattern, excluded_files=None): """ A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and importers. :param str glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the application directory. i.e. plugins/*/*plugin.py :param list[str] | None excluded_files: A list of file names to exclude that the glob pattern may find. :rtype: None """ from openlp.core.common.applocation import AppLocation app_dir = AppLocation.get_directory(AppLocation.AppDir) for extension_path in app_dir.glob(glob_pattern): extension_path = extension_path.relative_to(app_dir) if extension_path.name in (excluded_files or []): continue log.debug('Attempting to import %s', extension_path) module_name = path_to_module(extension_path) try: importlib.import_module(module_name) except (ImportError, OSError): # On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X) log.exception( 'Failed to import {module_name} on path {extension_path}'. format(module_name=module_name, extension_path=extension_path))
def set_up_fault_handling(): """ Set up the Python fault handler """ global error_log_file # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file try: create_paths(AppLocation.get_directory(AppLocation.CacheDir)) error_log_file = (AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb') atexit.register(tear_down_fault_handling) faulthandler.enable(error_log_file) except OSError: log.exception('An exception occurred when enabling the fault handler') atexit.unregister(tear_down_fault_handling) if error_log_file: error_log_file.close()
def build_theme_path(self): """ Set up the theme path variables :rtype: None """ self.theme_path = AppLocation.get_section_data_path(self.settings_section) self.thumb_path = self.theme_path / 'thumbnails' create_paths(self.theme_path, self.thumb_path)
def initialise(self): """ Initialize media item. """ self.list_view.clear() self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails' create_paths(self.service_path) self.load_list([path_to_str(file) for file in Settings().value(self.settings_section + '/media files')]) self.rebuild_players()
def load_first_time_themes(self): """ Imports any themes on start up and makes sure there is at least one theme """ self.application.set_busy_cursor() theme_paths = AppLocation.get_files(self.settings_section, '.otz') for theme_path in theme_paths: theme_path = self.theme_path / theme_path self.unzip_theme(theme_path, self.theme_path) delete_file(theme_path) theme_paths = AppLocation.get_files(self.settings_section, '.png') # No themes have been found so create one if not theme_paths: theme = Theme() theme.theme_name = UiStrings().Default self._write_theme(theme) Settings().setValue(self.settings_section + '/global theme', theme.theme_name) self.application.set_normal_cursor()
def add_static_route(self, route, static_dir): """ Add a static directory as a route """ if route not in self.static_routes: static_path = AppLocation.get_section_data_path('remotes') / static_dir if not static_path.exists(): log.error('Static path "%s" does not exist. Skipping creating static route/', static_path) return self.static_routes[route] = DirectoryApp(str(static_path.resolve()))
def __init__(self): """ Initialise the theme object. """ # basic theme object with defaults json_path = AppLocation.get_directory( AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json' jsn = get_text_file_string(json_path) self.load_theme(jsn) self.background_filename = None
def process_bind_param(self, value, dialect): """ Convert the Path object to a JSON representation :param pathlib.Path value: The value to convert :param dialect: Not used :return str: The Path object as a JSON string """ data_path = AppLocation.get_data_path() return json.dumps(value, cls=OpenLPJSONEncoder, base_path=data_path)
def get_cursor(): """ Return the cursor object. Instantiate one if it doesn't exist yet. """ if BiblesResourcesDB.cursor is None: file_path = \ AppLocation.get_directory(AppLocation.PluginsDir) / 'bibles' / 'resources' / 'bibles_resources.sqlite' conn = sqlite3.connect(str(file_path)) BiblesResourcesDB.cursor = conn.cursor() return BiblesResourcesDB.cursor
def on_data_directory_cancel_button_clicked(self): """ Cancel the data directory location change """ self.data_directory_path_edit.path = AppLocation.get_data_path() self.data_directory_copy_check_box.setChecked(False) self.main_window.new_data_path = None self.main_window.set_copy_data(False) self.data_directory_copy_check_box.hide() self.data_directory_cancel_button.hide() self.new_data_directory_has_files_label.hide()
def process_result_value(self, value, dialect): """ Convert the JSON representation back :param types.UnicodeText value: The value to convert :param dialect: Not used :return: The JSON object converted Python object (in this case it should be a Path object) :rtype: pathlib.Path """ data_path = AppLocation.get_data_path() return json.loads(value, cls=OpenLPJSONDecoder, base_path=data_path)
def __init__(self, parent=None): """ The constructor for the plugin manager. Passes the controllers on to the plugins for them to interact with via their ServiceItems. """ super(PluginManager, self).__init__(parent) self.log_info('Plugin manager Initialising') self.log_debug('Base path {path}'.format( path=AppLocation.get_directory(AppLocation.PluginsDir))) self.plugins = [] self.log_info('Plugin manager Initialised')