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 _write_theme(self, theme, image_source_path=None, image_destination_path=None): """ Writes the theme to the disk and handles the background image if necessary :param Theme theme: The theme data object. :param openlp.core.common.path.Path image_source_path: Where the theme image is currently located. :param openlp.core.common.path.Path image_destination_path: Where the Theme Image is to be saved to :rtype: None """ name = theme.theme_name theme_pretty = theme.export_theme(self.theme_path) theme_dir = self.theme_path / name create_paths(theme_dir) theme_path = theme_dir / '{file_name}.json'.format(file_name=name) try: theme_path.write_text(theme_pretty) except OSError: self.log_exception('Saving theme to file failed') if image_source_path and image_destination_path: if self.old_background_image_path and image_destination_path != self.old_background_image_path: delete_file(self.old_background_image_path) if image_source_path != image_destination_path: try: copyfile(image_source_path, image_destination_path) except OSError: self.log_exception('Failed to save theme image') self.generate_and_save_image(name, theme)
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 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 set_defaults(self): """ Set up display at start of theme edit. """ self.restart() self.web = 'https://get.openlp.org/ftw/' self.currentIdChanged.connect(self.on_current_id_changed) Registry().register_function('config_screen_changed', self.screen_selection_widget.load) # Check if this is a re-run of the wizard. self.has_run_wizard = Settings().value('core/has run wizard') create_paths(Path(gettempdir(), 'openlp')) self.theme_combo_box.clear() self.button(QtWidgets.QWizard.CustomButton1).setVisible(False) if self.has_run_wizard: self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active()) self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active()) self.presentation_check_box.setChecked( self.plugin_manager.get_plugin_by_name('presentations').is_active()) self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active()) self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active()) self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active()) self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active()) self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active()) # Add any existing themes to list. self.theme_combo_box.insertSeparator(0) self.theme_combo_box.addItems(sorted(self.theme_manager.get_theme_names())) default_theme = Settings().value('themes/global theme') # Pre-select the current default theme. index = self.theme_combo_box.findText(default_theme) self.theme_combo_box.setCurrentIndex(index)
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(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 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 get_section_data_path(section): """ Return the path a particular module stores its data under. :param str section: :rtype: openlp.core.common.path.Path """ path = AppLocation.get_data_path() / section create_paths(path) return path
def _setup(self, document_path): """ Run some initial setup. This method is separate from __init__ in order to mock it out in tests. :param openlp.core.common.path.Path document_path: Path to the document to load. :rtype: None """ self.slide_number = 0 self.file_path = document_path create_paths(self.get_thumbnail_folder())
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 initialise(self): log.debug('initialise') self.list_view.clear() self.list_view.setIconSize(QtCore.QSize(88, 50)) self.list_view.setIndentation(self.list_view.default_indentation) self.list_view.allow_internal_dnd = True self.service_path = AppLocation.get_section_data_path( self.settings_section) / 'thumbnails' create_paths(self.service_path) # Load images from the database self.load_full_list(self.manager.get_all_objects( ImageFilenames, order_by_ref=ImageFilenames.file_path), initial_load=True)
def __init__(self, parent, songs, save_path): """ Initialise the export. :param pathlib.Path save_path: The directory to save the exported songs in :rtype: None """ log.debug('initialise OpenLyricsExport') self.parent = parent self.manager = parent.plugin.manager self.songs = songs self.save_path = save_path create_paths(self.save_path)
def test_create_paths_dir_io_error(self, mocked_logger): """ Test the create_paths() when an OSError is raised """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() mocked_path.exists.side_effect = OSError('Cannot make directory') # WHEN: An OSError is raised when checking the if the path exists. create_paths(mocked_path) # THEN: The Error should have been logged mocked_logger.exception.assert_called_once_with( 'failed to check if directory exists or create directory')
def test_create_paths_dir_doesnt_exists(self): """ Test the create_paths() function when the path does not already exist """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() mocked_path.exists.return_value = False # WHEN: `create_paths` is called and the path does not exist create_paths(mocked_path) # THEN: The directory should have been created mocked_path.exists.assert_called_once_with() mocked_path.mkdir.assert_called_once_with(parents=True)
def test_create_paths_dir_exists(self): """ Test the create_paths() function when the path already exists """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() mocked_path.exists.return_value = True # WHEN: `create_paths` is called and the path exists create_paths(mocked_path) # THEN: The function should not attempt to create the directory mocked_path.exists.assert_called_once_with() assert mocked_path.mkdir.call_count == 0, 'mkdir should not have been called'
def get_data_path(): """ Return the path OpenLP stores all its data under. :return: The data path to use. :rtype: openlp.core.common.path.Path """ # Check if we have a different data location. if Settings().contains('advanced/data path'): path = Path(Settings().value('advanced/data path')) else: path = AppLocation.get_directory(AppLocation.DataDir) create_paths(path) return path
def test_create_paths_dir_value_error(self): """ Test the create_paths() when an error other than OSError is raised """ # GIVEN: A `Path` to check with patched out mkdir and exists methods mocked_path = MagicMock() mocked_path.exists.side_effect = ValueError('Some other error') # WHEN: Some other exception is raised try: create_paths(mocked_path) assert False, 'create_paths should have thrown an exception' except: # THEN: `create_paths` raises an exception pass
def set_up_logging(log_path): """ Setup our logging using log_path :param openlp.core.common.path.Path log_path: The file to save the log to. :rtype: None """ create_paths(log_path, do_not_log=True) file_path = log_path / 'openlp.log' # TODO: FileHandler accepts a Path object in Py3.6 logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8') logfile.setFormatter( logging.Formatter( '%(asctime)s %(name)-55s %(levelname)-8s %(message)s')) log.addHandler(logfile) if log.isEnabledFor(logging.DEBUG): print('Logging to: {name}'.format(name=file_path))
def accept(self): """ Ok was triggered so lets save the data and run the report """ log.debug('accept') path = self.report_path_edit.path if not path: self.main_window.error_message( translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'), translate('SongUsagePlugin.SongUsageDetailForm', 'You have not set a valid output location for your' ' song usage report.\nPlease select an existing path on your computer.') ) return create_paths(path) file_name = translate('SongUsagePlugin.SongUsageDetailForm', 'usage_detail_{old}_{new}.txt' ).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'), new=self.to_date_calendar.selectedDate().toString('ddMMyyyy')) self.settings.setValue(self.plugin.settings_section + '/from date', self.from_date_calendar.selectedDate()) self.settings.setValue(self.plugin.settings_section + '/to date', self.to_date_calendar.selectedDate()) usage = self.plugin.manager.get_all_objects( SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(), SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()), [SongUsageItem.usagedate, SongUsageItem.usagetime]) report_file_name = path / file_name try: with report_file_name.open('wb') as file_handle: for instance in usage: record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",' '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime, title=instance.title, copyright=instance.copyright, ccli=instance.ccl_number, authors=instance.authors, name=instance.plugin_name, source=instance.source) file_handle.write(record.encode('utf-8')) self.main_window.information_message( translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'), translate('SongUsagePlugin.SongUsageDetailForm', 'Report\n{name}\nhas been successfully created.').format(name=report_file_name) ) except OSError as ose: log.exception('Failed to write out song usage records') critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'), translate('SongUsagePlugin.SongUsageDetailForm', 'An error occurred while creating the report: {error}' ).format(error=ose.strerror)) self.close()
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 copy_media_file(self, song_id, file_path): """ This method copies the media file to the correct location and returns the new file location. :param song_id: :param openlp.core.common.path.Path file_path: The file to copy. :return: The new location of the file :rtype: openlp.core.common.path.Path """ if not hasattr(self, 'save_path'): self.save_path = AppLocation.get_section_data_path( self.import_wizard.plugin.name) / 'audio' / str(song_id) create_paths(self.save_path) if self.save_path not in file_path.parents: old_path, file_path = file_path, self.save_path / file_path.name copyfile(old_path, file_path) return file_path
def set_defaults(self): """ Set up display at start of theme edit. """ self.restart() self.web = 'http://openlp.org/files/frw/' self.cancel_button.clicked.connect(self.on_cancel_button_clicked) self.no_internet_finish_button.clicked.connect( self.on_no_internet_finish_button_clicked) self.no_internet_cancel_button.clicked.connect( self.on_no_internet_cancel_button_clicked) self.currentIdChanged.connect(self.on_current_id_changed) Registry().register_function('config_screen_changed', self.update_screen_list_combo) self.no_internet_finish_button.setVisible(False) self.no_internet_cancel_button.setVisible(False) # Check if this is a re-run of the wizard. self.has_run_wizard = Settings().value('core/has run wizard') create_paths(Path(gettempdir(), 'openlp'))
def save_theme(self, theme, image=None, background_override=None): """ Writes the theme to the disk and including the background image and thumbnail if necessary :param Theme theme: The theme data object. :param image: The theme thumbnail. Optionally. :param background_override: Background to use rather than background_source. Optionally. :rtype: None """ name = theme.theme_name theme_pretty = theme.export_theme(self.theme_path) theme_dir = self.theme_path / name create_paths(theme_dir) theme_path = theme_dir / '{file_name}.json'.format(file_name=name) try: theme_path.write_text(theme_pretty) except OSError: self.log_exception('Saving theme to file failed') if theme.background_source and theme.background_filename: background_file = background_override # Use theme source image if override doesn't exist if not background_file or not background_file.exists(): background_file = theme.background_source if self.old_background_image_path and theme.background_filename != self.old_background_image_path: delete_file(self.old_background_image_path) if not background_file.exists(): self.log_warning( 'Background does not exist, retaining cached background') elif background_file != theme.background_filename: try: shutil.copyfile(background_file, theme.background_filename) except OSError: self.log_exception('Failed to save theme image') if image: sample_path_name = self.theme_path / '{file_name}.png'.format( file_name=name) if sample_path_name.exists(): sample_path_name.unlink() image.save(str(sample_path_name), 'png') thumb_path = self.thumb_path / '{name}.png'.format(name=name) create_thumb(sample_path_name, thumb_path, False) else: self.update_preview_images([name])
def unzip_theme(self, file_path, directory_path): """ Unzip the theme, remove the preview file if stored. Generate a new preview file. Check the XML theme version and upgrade if necessary. :param Path file_path: :param Path directory_path: """ self.log_debug('Unzipping theme {name}'.format(name=file_path)) file_xml = None abort_import = True json_theme = False theme_name = "" try: with zipfile.ZipFile(file_path) as theme_zip: json_file = [ name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json' ] if len(json_file) != 1: # TODO: remove XML handling after once the upgrade path from 2.4 is no longer required xml_file = [ name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml' ] if len(xml_file) != 1: self.log_error( 'Theme contains "{val:d}" theme files'.format( val=len(xml_file))) raise ValidationError xml_tree = ElementTree( element=XML(theme_zip.read(xml_file[0]))).getroot() theme_version = xml_tree.get('version', default=None) if not theme_version or float(theme_version) < 2.0: self.log_error('Theme version is less than 2.0') raise ValidationError theme_name = xml_tree.find('name').text.strip() else: new_theme = Theme() new_theme.load_theme( theme_zip.read(json_file[0]).decode("utf-8")) theme_name = new_theme.theme_name json_theme = True theme_folder = directory_path / theme_name if theme_folder.exists( ) and not self.over_write_message_box(theme_name): abort_import = True return else: abort_import = False for zipped_file in theme_zip.namelist(): zipped_file_rel_path = Path(zipped_file) split_name = zipped_file_rel_path.parts if split_name[-1] == '' or len(split_name) == 1: # is directory or preview file continue full_name = directory_path / zipped_file_rel_path create_paths(full_name.parent) if zipped_file_rel_path.suffix.lower( ) == '.xml' or zipped_file_rel_path.suffix.lower( ) == '.json': file_xml = str(theme_zip.read(zipped_file), 'utf-8') with full_name.open('w', encoding='utf-8') as out_file: out_file.write(file_xml) else: with full_name.open('wb') as out_file: out_file.write(theme_zip.read(zipped_file)) except (OSError, ValidationError, zipfile.BadZipFile): self.log_exception('Importing theme from zip failed {name}'.format( name=file_path)) critical_error_message_box( translate('OpenLP.ThemeManager', 'Import Error'), translate( 'OpenLP.ThemeManager', 'There was a problem importing {file_name}.\n\nIt is corrupt, ' 'inaccessible or not a valid theme.').format( file_name=file_path)) finally: if not abort_import: # As all files are closed, we can create the Theme. if file_xml: if json_theme: self._create_theme_from_json(file_xml, self.theme_path) else: self._create_theme_from_xml(file_xml, self.theme_path) return theme_name else: return None