def delete_selected_files(self): button = QMessageBox.question( get_main_window(), 'Delete Files', 'Irreversibly deleting selected files and directories.', QMessageBox.Ok, QMessageBox.Cancel) if not self.is_alive() or button != QMessageBox.Ok: return selected_name_items = self.get_directly_selected_name_items() if len(selected_name_items) == 0: return script_instance_ref = [None] def progress_canceled(): script_instance = script_instance_ref[0] if script_instance == None: return self.script_manager.abort_script(script_instance) progress = ExpandingProgressDialog(self) progress.set_progress_text_visible(False) progress.setModal(True) progress.setWindowTitle('Delete Files') progress.setLabelText('Collecting files and directories to delete') progress.setRange(0, 0) progress.canceled.connect(progress_canceled) progress.show() files_to_delete = [] dirs_to_delete = [] all_done = False while not all_done: all_done = True for selected_name_item in list(selected_name_items): item_done = False parent = selected_name_item.parent() while not item_done and parent != None: if parent in selected_name_items: selected_name_items.remove(selected_name_item) item_done = True else: parent = parent.parent() if item_done: all_done = False break for selected_name_item in selected_name_items: path = get_full_item_path(selected_name_item) item_type = selected_name_item.data(USER_ROLE_ITEM_TYPE) if item_type == ITEM_TYPE_DIRECTORY: dirs_to_delete.append(posixpath.join(self.bin_directory, path)) else: files_to_delete.append(posixpath.join(self.bin_directory, path)) message = 'Deleting ' if len(files_to_delete) == 1: message += '1 file ' elif len(files_to_delete) > 1: message += '{0} files '.format(len(files_to_delete)) if len(dirs_to_delete) == 1: if len(files_to_delete) > 0: message += 'and ' message += '1 directory' elif len(dirs_to_delete) > 1: if len(files_to_delete) > 0: message += 'and ' message += '{0} directories'.format(len(dirs_to_delete)) progress.setLabelText(message) def cb_delete(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None progress.cancel() self.refresh_files() if aborted: QMessageBox.information(get_main_window(), 'Delete Files', 'Delete operation was aborted.') return report_script_result( result, 'Delete Files Error', 'Could not delete selected files/directories:') script_instance_ref[0] = self.script_manager.execute_script( 'delete', cb_delete, [json.dumps(files_to_delete), json.dumps(dirs_to_delete)], execute_as_user=True)
class REDTabImportExportImport(QWidget, Ui_REDTabImportExportImport): def __init__(self): QWidget.__init__(self) self.setupUi(self) self.session = None # Set from REDTabImportExport self.script_manager = None # Set from REDTabImportExport self.image_version = None # Set from REDTabImportExport self.refresh_in_progress = False self.progress = None self.chunked_uploader = None self.button_browse_archive.clicked.connect(self.browse_archive) self.edit_archive.textChanged.connect(self.update_ui_state) self.tree_programs.setColumnWidth(0, 150) self.tree_programs.setColumnWidth(1, 150) self.tree_programs.setColumnWidth(2, 150) self.tree_programs.itemSelectionChanged.connect(self.update_ui_state) self.button_refresh_programs.clicked.connect(self.refresh_program_list) self.button_import.clicked.connect(self.import_archive) self.update_ui_state() def tab_on_focus(self): QTimer.singleShot(1, self.refresh_program_list) def tab_off_focus(self): pass def tab_destroy(self): pass def update_ui_state(self): if self.refresh_in_progress: self.edit_archive.setEnabled(False) self.button_browse_archive.setEnabled(False) self.progress_refresh_programs.setVisible(True) self.button_refresh_programs.setText('Refreshing...') self.button_refresh_programs.setEnabled(False) self.button_import.setEnabled(False) else: self.edit_archive.setEnabled(True) self.button_browse_archive.setEnabled(True) self.progress_refresh_programs.setVisible(False) self.button_refresh_programs.setText('Refresh') self.button_refresh_programs.setEnabled(len(self.edit_archive.text()) > 0) self.button_import.setEnabled(len(self.tree_programs.selectedItems()) > 0) def browse_archive(self): if len(self.edit_archive.text()) > 0: last_directory = os.path.dirname(os.path.realpath(self.edit_archive.text())) else: last_directory = get_home_path() filename = get_open_file_name(get_main_window(), 'Open Archive', last_directory, '*.tfrba') if len(filename) > 0: self.edit_archive.setText(filename) self.refresh_program_list() def refresh_program_list(self): filename = self.edit_archive.text() if len(filename) == 0: return def refresh_async(): try: a = tarfile.open(filename, 'r:gz') except Exception as e: return [], 'Could not open archive:\n\n{0}'.format(e) with contextlib.closing(a): try: v = a.extractfile('tfrba-version') except Exception as e: return [], 'Could not extract tfrba-version:\n\n{0}'.format(e) version = v.read() v.close() if version != b'1': return [], 'Unknown tfrba-version {0}'.format(version) programs = {} for member in a.getnames(): if member.startswith('programs/') and member.endswith('/program.conf'): try: c = a.extractfile(member) except Exception as e: return [], 'Could not extract {0}:\n\n{1}'.format(member, e) conf = list(map(lambda line: line.decode('utf-8'), c.readlines())) c.close() name = '<unknown>' identifier = member.split('/')[1] language = '<unknown>' first_upload = 0 for line in conf: if line.startswith('custom.name ='): try: name = line[len('custom.name ='):].strip().encode('ascii').decode('unicode_escape') except: pass elif line.startswith('custom.language ='): try: language = Constants.get_language_display_name(line[len('custom.language ='):].strip().encode('ascii').decode('unicode_escape')) except: pass elif line.startswith('custom.first_upload ='): try: first_upload = int(line[len('custom.first_upload ='):].strip()) except: pass programs[identifier] = [name, identifier, language, 'New', first_upload] try: existing_programs = get_lite_programs(self.session) except Exception as e: return [], 'Could not get existing program list:\n\n{0}'.format(e) for existing_program in existing_programs: identifier = existing_program.identifier if identifier in programs: programs[identifier][3] = 'Existing' return programs.values(), None def cb_success(result): programs, message = result if message != None: QMessageBox.critical(get_main_window(), 'Import Error', message) else: sorted_programs = {} for program in programs: first_upload = program[4] if first_upload in sorted_programs: sorted_programs[first_upload][program[1]] = program else: sorted_programs[first_upload] = {program[1]: program} for first_upload in sorted(sorted_programs.keys()): for identifier in sorted(sorted_programs[first_upload].keys()): program = sorted_programs[first_upload][identifier] item = QTreeWidgetItem(program[0:4]) self.tree_programs.addTopLevelItem(item) item.setSelected(True) self.refresh_in_progress = False self.update_ui_state() self.tree_programs.setFocus() def cb_error(error): pass # FIXME: report error self.refresh_in_progress = True self.update_ui_state() self.tree_programs.invisibleRootItem().takeChildren() async_call(refresh_async, None, cb_success, cb_error, pass_exception_to_error_callback=True) def import_archive(self): source_path = self.edit_archive.text() if len(source_path) == 0: return script_instance_ref = [None] def progress_canceled(): chunked_uploader = self.chunked_uploader if chunked_uploader != None: chunked_uploader.canceled = True script_instance = script_instance_ref[0] if script_instance != None: self.script_manager.abort_script(script_instance) self.progress = ExpandingProgressDialog(self) self.progress.set_progress_text_visible(False) self.progress.setModal(True) self.progress.setWindowTitle('Import Archive') self.progress.setLabelText('Step 1 of 4: Creating import directory') self.progress.setRange(0, 0) self.progress.setAutoClose(False) self.progress.canceled.connect(progress_canceled) self.progress.show() selected_identifiers = [] for selected_item in self.tree_programs.selectedItems(): selected_identifiers.append(selected_item.text(1)) import_directory_ref = [None] def extract_archive(): def cb_import_extract(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None if aborted: return if not report_script_result(result, 'Import Error', 'Could not extract archive', before_message_box=self.progress.close): return def cb_restart_reboot_shutdown(result): self.progress.close() report_script_result(result, 'Import Error', 'Could not reboot RED Brick to finish program import') # step 4/4: reboot self.progress.setLabelText('Step 4 of 4: Rebooting RED Brick') self.progress.setRange(0, 0) self.script_manager.execute_script('restart_reboot_shutdown_systemd', cb_restart_reboot_shutdown, ['1']) def close_progress(): # use a closure to capture self and ansure that it's safe # to call this even if the tab was official destroyed already self.progress.close() QTimer.singleShot(1500, close_progress) # step 3/4: extract uploaded archive self.progress.setLabelText('Step 3 of 4: Extracting archive') self.progress.setRange(0, 0) script_instance_ref[0] = self.script_manager.execute_script('import_extract', cb_import_extract, [import_directory_ref[0]] + selected_identifiers) def cb_import_directory(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None if aborted: return if not report_script_result(result, 'Import Error', 'Could not create import directory', before_message_box=self.progress.close): return # step 2/4: upload archive to temporary import directory import_directory_ref[0] = result.stdout.strip() target_path = posixpath.join(import_directory_ref[0], 'archive.tfrba') self.chunked_uploader = ChunkedUploader(self, extract_archive) if not self.chunked_uploader.prepare(source_path): return try: target_file = REDFile(self.session).open(target_path, REDFile.FLAG_WRITE_ONLY | REDFile.FLAG_CREATE | REDFile.FLAG_NON_BLOCKING | REDFile.FLAG_EXCLUSIVE, 0o644, 1000, 1000) # FIXME: async_call except (Error, REDError) as e: QMessageBox.information(get_main_window(), 'Import Error', 'Could not open target file {0}: {1}'.format(target_path, e)) return self.progress.setLabelText('Step 2 of 4: Uploading archive') self.progress.set_progress_text_visible(True) self.chunked_uploader.start(target_path, target_file) # step 1/4: create temporary import directory script_instance_ref[0] = self.script_manager.execute_script('import_directory', cb_import_directory, execute_as_user=True)
def delete_selected_files(self): button = QMessageBox.question(get_main_window(), 'Delete Files', 'Irreversibly deleting selected files and directories.', QMessageBox.Ok, QMessageBox.Cancel) if not self.is_alive() or button != QMessageBox.Ok: return selected_name_items = self.get_directly_selected_name_items() if len(selected_name_items) == 0: return script_instance_ref = [None] def progress_canceled(): script_instance = script_instance_ref[0] if script_instance == None: return self.script_manager.abort_script(script_instance) progress = ExpandingProgressDialog(self) progress.set_progress_text_visible(False) progress.setModal(True) progress.setWindowTitle('Delete Files') progress.setLabelText('Collecting files and directories to delete') progress.setRange(0, 0) progress.canceled.connect(progress_canceled) progress.show() files_to_delete = [] dirs_to_delete = [] all_done = False while not all_done: all_done = True for selected_name_item in list(selected_name_items): item_done = False parent = selected_name_item.parent() while not item_done and parent != None: if parent in selected_name_items: selected_name_items.remove(selected_name_item) item_done = True else: parent = parent.parent() if item_done: all_done = False break for selected_name_item in selected_name_items: path = get_full_item_path(selected_name_item) item_type = selected_name_item.data(USER_ROLE_ITEM_TYPE) if item_type == ITEM_TYPE_DIRECTORY: dirs_to_delete.append(posixpath.join(self.bin_directory, path)) else: files_to_delete.append(posixpath.join(self.bin_directory, path)) message = 'Deleting ' if len(files_to_delete) == 1: message += '1 file ' elif len(files_to_delete) > 1: message += '{0} files '.format(len(files_to_delete)) if len(dirs_to_delete) == 1: if len(files_to_delete) > 0: message += 'and ' message += '1 directory' elif len(dirs_to_delete) > 1: if len(files_to_delete) > 0: message += 'and ' message += '{0} directories'.format(len(dirs_to_delete)) progress.setLabelText(message) def cb_delete(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None progress.cancel() self.refresh_files() if aborted: QMessageBox.information(get_main_window(), 'Delete Files', 'Delete operation was aborted.') return report_script_result(result, 'Delete Files Error', 'Could not delete selected files/directories:') script_instance_ref[0] = self.script_manager.execute_script('delete', cb_delete, [json.dumps(files_to_delete), json.dumps(dirs_to_delete)], execute_as_user=True)
def get_main_classes(): script_instance_ref = [None] def progress_canceled(): script_instance = script_instance_ref[0] if script_instance == None: return self.wizard().script_manager.abort_script(script_instance) progress = ExpandingProgressDialog(self.wizard()) progress.set_progress_text_visible(False) progress.setWindowTitle('Edit Program') progress.setLabelText('Collecting Java main classes') progress.setModal(True) progress.setRange(0, 0) progress.canceled.connect(progress_canceled) progress.show() def cb_java_main_classes(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None def done(): progress.cancel() self.combo_main_class.setEnabled(True) self.completeChanged.emit() if aborted: done() return okay, message = check_script_result(result, decode_stderr=True) if not okay: self.label_main_class_error.setText( '<b>Error:</b> ' + html.escape(message)) self.label_main_class_error.setVisible(True) done() return def expand_async(data): try: main_classes = json.loads( zlib.decompress( memoryview(data)).decode('utf-8')) if not isinstance(main_classes, dict): main_classes = {} except: main_classes = {} return main_classes def cb_expand_success(main_classes): self.combo_main_class.clear() for cls in sorted(main_classes.keys()): self.combo_main_class.addItem( cls, main_classes[cls]) self.combo_main_class_checker.set_current_text( program.cast_custom_option_value( 'java.main_class', str, '')) done() def cb_expand_error(): self.label_main_class_error.setText( '<b>Error:</b> Internal async error') self.label_main_class_error.setVisible(True) done() async_call(expand_async, result.stdout, cb_expand_success, cb_expand_error) script_instance_ref[0] = self.wizard( ).script_manager.execute_script('java_main_classes', cb_java_main_classes, [self.bin_directory], max_length=1024 * 1024, decode_output_as_utf8=False)
def initializePage(self): self.set_formatted_sub_title( 'Specify how the {language} program [{name}] should be executed.') self.update_combo_version('java', self.combo_version) self.combo_start_mode.setCurrentIndex( Constants.DEFAULT_JAVA_START_MODE) self.combo_jar_file_selector.reset() self.class_path_list_editor.reset() self.check_show_class_path.setChecked(False) self.check_show_advanced_options.setChecked(False) self.combo_working_directory_selector.reset() self.option_list_editor.reset() # if a program exists then this page is used in an edit wizard program = self.wizard().program if program != None: self.bin_directory = posixpath.join(program.root_directory, 'bin') else: identifier = self.get_field('identifier') self.bin_directory = posixpath.join('/', 'home', 'tf', 'programs', identifier, 'bin') # collect class path entries self.class_path_candidates = ['.'] for filename in sorted(self.wizard().available_files): directroy = posixpath.split(filename)[0] if len(directroy ) > 0 and directroy not in self.class_path_candidates: self.class_path_candidates.append(directroy) if filename.endswith('.class') or filename.endswith('.properties'): if program == None: self.class_path_list_editor.add_item(directroy) elif filename.endswith('.jar'): self.class_path_candidates.append(filename) if program == None: self.class_path_list_editor.add_item(filename) self.class_path_list_editor.add_item( '/usr/tinkerforge/bindings/java/Tinkerforge.jar') self.class_path_candidates.append( '/usr/tinkerforge/bindings/java/Tinkerforge.jar') self.combo_main_class.clear() self.combo_main_class.clearEditText() # collect main classes if program != None: self.combo_main_class.setEnabled(False) def get_main_classes(): script_instance_ref = [None] def progress_canceled(): script_instance = script_instance_ref[0] if script_instance == None: return self.wizard().script_manager.abort_script(script_instance) progress = ExpandingProgressDialog(self.wizard()) progress.set_progress_text_visible(False) progress.setWindowTitle('Edit Program') progress.setLabelText('Collecting Java main classes') progress.setModal(True) progress.setRange(0, 0) progress.canceled.connect(progress_canceled) progress.show() def cb_java_main_classes(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None def done(): progress.cancel() self.combo_main_class.setEnabled(True) self.completeChanged.emit() if aborted: done() return okay, message = check_script_result(result, decode_stderr=True) if not okay: self.label_main_class_error.setText( '<b>Error:</b> ' + html.escape(message)) self.label_main_class_error.setVisible(True) done() return def expand_async(data): try: main_classes = json.loads( zlib.decompress( memoryview(data)).decode('utf-8')) if not isinstance(main_classes, dict): main_classes = {} except: main_classes = {} return main_classes def cb_expand_success(main_classes): self.combo_main_class.clear() for cls in sorted(main_classes.keys()): self.combo_main_class.addItem( cls, main_classes[cls]) self.combo_main_class_checker.set_current_text( program.cast_custom_option_value( 'java.main_class', str, '')) done() def cb_expand_error(): self.label_main_class_error.setText( '<b>Error:</b> Internal async error') self.label_main_class_error.setVisible(True) done() async_call(expand_async, result.stdout, cb_expand_success, cb_expand_error) script_instance_ref[0] = self.wizard( ).script_manager.execute_script('java_main_classes', cb_java_main_classes, [self.bin_directory], max_length=1024 * 1024, decode_output_as_utf8=False) # need to decouple this with a timer, otherwise it's executed at # a time where the progress bar cannot properly enter model state # to block the parent widget QTimer.singleShot(0, get_main_classes) elif self.wizard().hasVisitedPage(Constants.PAGE_FILES): uploads = self.wizard().page(Constants.PAGE_FILES).get_uploads() if len(uploads) > 0: abort_ref = [False] def progress_canceled(): abort_ref[0] = True progress = ExpandingProgressDialog(self) progress.set_progress_text_visible(False) progress.setWindowTitle('New Program') progress.setLabelText('Collecting Java main classes') progress.setModal(True) progress.setRange(0, 0) progress.canceled.connect(progress_canceled) progress.show() def cb_main_classes(main_classes): for main_class in main_classes: self.combo_main_class.addItem(main_class) if self.combo_main_class.count() > 1: self.combo_main_class.clearEditText() progress.cancel() self.combo_main_class.setEnabled(True) self.completeChanged.emit() def cb_main_classes_error(): self.label_main_class_error.setText( '<b>Error:</b> Internal async error') self.label_main_class_error.setVisible(True) progress.cancel() self.combo_main_class.clearEditText() self.combo_main_class.setEnabled(True) self.completeChanged.emit() def get_main_classes_async(uploads): return sorted( get_main_classes_from_class_or_jar(uploads, abort_ref)) self.combo_main_class.setEnabled(False) async_call(get_main_classes_async, uploads, cb_main_classes, cb_main_classes_error) # if a program exists then this page is used in an edit wizard if program != None: # start mode start_mode_api_name = program.cast_custom_option_value( 'java.start_mode', str, '<unknown>') start_mode = Constants.get_java_start_mode(start_mode_api_name) self.combo_start_mode.setCurrentIndex(start_mode) # main class self.combo_main_class_checker.set_current_text( program.cast_custom_option_value('java.main_class', str, '')) # jar file self.combo_jar_file_selector.set_current_text( program.cast_custom_option_value('java.jar_file', str, '')) # class path self.class_path_list_editor.clear() for class_path_entry in program.cast_custom_option_value_list( 'java.class_path', str, []): self.class_path_list_editor.add_item(class_path_entry) # working directory self.combo_working_directory_selector.set_current_text( program.working_directory) # options self.option_list_editor.clear() for option in program.cast_custom_option_value_list( 'java.options', str, []): self.option_list_editor.add_item(option) self.update_ui_state()
class REDTabImportExportImport(QWidget, Ui_REDTabImportExportImport): def __init__(self): QWidget.__init__(self) self.setupUi(self) self.session = None # Set from REDTabImportExport self.script_manager = None # Set from REDTabImportExport self.image_version = None # Set from REDTabImportExport self.refresh_in_progress = False self.progress = None self.chunked_uploader = None self.button_browse_archive.clicked.connect(self.browse_archive) self.edit_archive.textChanged.connect(self.update_ui_state) self.tree_programs.setColumnWidth(0, 150) self.tree_programs.setColumnWidth(1, 150) self.tree_programs.setColumnWidth(2, 150) self.tree_programs.itemSelectionChanged.connect(self.update_ui_state) self.button_refresh_programs.clicked.connect(self.refresh_program_list) self.button_import.clicked.connect(self.import_archive) self.update_ui_state() def tab_on_focus(self): QTimer.singleShot(1, self.refresh_program_list) def tab_off_focus(self): pass def tab_destroy(self): pass def update_ui_state(self): if self.refresh_in_progress: self.edit_archive.setEnabled(False) self.button_browse_archive.setEnabled(False) self.progress_refresh_programs.setVisible(True) self.button_refresh_programs.setText('Refreshing...') self.button_refresh_programs.setEnabled(False) self.button_import.setEnabled(False) else: self.edit_archive.setEnabled(True) self.button_browse_archive.setEnabled(True) self.progress_refresh_programs.setVisible(False) self.button_refresh_programs.setText('Refresh') self.button_refresh_programs.setEnabled( len(self.edit_archive.text()) > 0) self.button_import.setEnabled( len(self.tree_programs.selectedItems()) > 0) def browse_archive(self): if len(self.edit_archive.text()) > 0: last_directory = os.path.dirname( os.path.realpath(self.edit_archive.text())) else: last_directory = get_home_path() filename = get_open_file_name( get_main_window(), 'Open Archive', last_directory, 'Tinkerforge RED-Brick Archives(*.tfrba);;All Files(*)') if len(filename) > 0: self.edit_archive.setText(filename) self.refresh_program_list() def refresh_program_list(self): filename = self.edit_archive.text() if len(filename) == 0: return def refresh_async(): try: a = tarfile.open(filename, 'r:gz') except Exception as e: return [], 'Could not open archive:\n\n{0}'.format(e) with contextlib.closing(a): try: v = a.extractfile('tfrba-version') except Exception as e: return [], 'Could not extract tfrba-version:\n\n{0}'.format( e) version = v.read() v.close() if version != b'1': return [], 'Unknown tfrba-version {0}'.format(version) programs = {} for member in a.getnames(): if member.startswith('programs/') and member.endswith( '/program.conf'): try: c = a.extractfile(member) except Exception as e: return [], 'Could not extract {0}:\n\n{1}'.format( member, e) conf = list( map(lambda line: line.decode('utf-8'), c.readlines())) c.close() name = '<unknown>' identifier = member.split('/')[1] language = '<unknown>' first_upload = 0 for line in conf: if line.startswith('custom.name ='): try: name = line[len('custom.name ='):].strip( ).encode('ascii').decode('unicode_escape') except: pass elif line.startswith('custom.language ='): try: language = Constants.get_language_display_name( line[len('custom.language ='):].strip( ).encode('ascii').decode( 'unicode_escape')) except: pass elif line.startswith('custom.first_upload ='): try: first_upload = int( line[len('custom.first_upload =' ):].strip()) except: pass programs[identifier] = [ name, identifier, language, 'New', first_upload ] try: existing_programs = get_lite_programs(self.session) except Exception as e: return [], 'Could not get existing program list:\n\n{0}'.format( e) for existing_program in existing_programs: identifier = existing_program.identifier if identifier in programs: programs[identifier][3] = 'Existing' return programs.values(), None def cb_success(result): programs, message = result if message != None: QMessageBox.critical(get_main_window(), 'Import Error', message) else: sorted_programs = {} for program in programs: first_upload = program[4] if first_upload in sorted_programs: sorted_programs[first_upload][program[1]] = program else: sorted_programs[first_upload] = {program[1]: program} for first_upload in sorted(sorted_programs.keys()): for identifier in sorted( sorted_programs[first_upload].keys()): program = sorted_programs[first_upload][identifier] item = QTreeWidgetItem(program[0:4]) self.tree_programs.addTopLevelItem(item) item.setSelected(True) self.refresh_in_progress = False self.update_ui_state() self.tree_programs.setFocus() def cb_error(error): pass # FIXME: report error self.refresh_in_progress = True self.update_ui_state() self.tree_programs.invisibleRootItem().takeChildren() async_call(refresh_async, None, cb_success, cb_error, pass_exception_to_error_callback=True) def import_archive(self): source_path = self.edit_archive.text() if len(source_path) == 0: return script_instance_ref = [None] def progress_canceled(): chunked_uploader = self.chunked_uploader if chunked_uploader != None: chunked_uploader.canceled = True script_instance = script_instance_ref[0] if script_instance != None: self.script_manager.abort_script(script_instance) self.progress = ExpandingProgressDialog(self) self.progress.set_progress_text_visible(False) self.progress.setModal(True) self.progress.setWindowTitle('Import Archive') self.progress.setLabelText('Step 1 of 4: Creating import directory') self.progress.setRange(0, 0) self.progress.setAutoClose(False) self.progress.canceled.connect(progress_canceled) self.progress.show() selected_identifiers = [] for selected_item in self.tree_programs.selectedItems(): selected_identifiers.append(selected_item.text(1)) import_directory_ref = [None] def extract_archive(): def cb_import_extract(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None if aborted: return if not report_script_result( result, 'Import Error', 'Could not extract archive', before_message_box=self.progress.close): return def cb_restart_reboot_shutdown(result): self.progress.close() report_script_result( result, 'Import Error', 'Could not reboot RED Brick to finish program import') # step 4/4: reboot self.progress.setLabelText('Step 4 of 4: Rebooting RED Brick') self.progress.setRange(0, 0) self.script_manager.execute_script( 'restart_reboot_shutdown_systemd', cb_restart_reboot_shutdown, ['1']) def close_progress(): # use a closure to capture self and ansure that it's safe # to call this even if the tab was official destroyed already self.progress.close() QTimer.singleShot(1500, close_progress) # step 3/4: extract uploaded archive self.progress.setLabelText('Step 3 of 4: Extracting archive') self.progress.setRange(0, 0) script_instance_ref[0] = self.script_manager.execute_script( 'import_extract', cb_import_extract, [import_directory_ref[0]] + selected_identifiers) def cb_import_directory(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None if aborted: return if not report_script_result( result, 'Import Error', 'Could not create import directory', before_message_box=self.progress.close): return # step 2/4: upload archive to temporary import directory import_directory_ref[0] = result.stdout.strip() target_path = posixpath.join(import_directory_ref[0], 'archive.tfrba') self.chunked_uploader = ChunkedUploader(self, extract_archive) if not self.chunked_uploader.prepare(source_path): return try: target_file = REDFile(self.session).open( target_path, REDFile.FLAG_WRITE_ONLY | REDFile.FLAG_CREATE | REDFile.FLAG_NON_BLOCKING | REDFile.FLAG_EXCLUSIVE, 0o644, 1000, 1000) # FIXME: async_call except (Error, REDError) as e: QMessageBox.information( get_main_window(), 'Import Error', 'Could not open target file {0}: {1}'.format( target_path, e)) return self.progress.setLabelText('Step 2 of 4: Uploading archive') self.progress.set_progress_text_visible(True) self.chunked_uploader.start(target_path, target_file) # step 1/4: create temporary import directory script_instance_ref[0] = self.script_manager.execute_script( 'import_directory', cb_import_directory, execute_as_user=True)
class REDTabImportExportExport(QWidget, Ui_REDTabImportExportExport): def __init__(self): QWidget.__init__(self) self.setupUi(self) self.session = None # Set from REDTabImportExport self.script_manager = None # Set from REDTabImportExport self.image_version = None # Set from REDTabImportExport self.first_tab_on_focus = True self.refresh_in_progress = False self.last_directory = get_home_path() self.progress = None self.chunked_downloader = None self.tree_programs.setColumnWidth(0, 150) self.tree_programs.setColumnWidth(1, 150) self.tree_programs.itemSelectionChanged.connect(self.update_ui_state) self.button_refresh_programs.clicked.connect(self.refresh_program_list) self.button_export.clicked.connect(self.export_archive) self.update_ui_state() def tab_on_focus(self): QTimer.singleShot(1, self.refresh_program_list) def tab_off_focus(self): pass def tab_destroy(self): pass def update_ui_state(self): if self.refresh_in_progress: self.progress_refresh_programs.setVisible(True) self.button_refresh_programs.setText('Refreshing...') self.button_refresh_programs.setEnabled(False) self.button_export.setEnabled(False) else: self.progress_refresh_programs.setVisible(False) self.button_refresh_programs.setText('Refresh') self.button_refresh_programs.setEnabled(True) self.button_export.setEnabled(len(self.tree_programs.selectedItems()) > 0) def refresh_program_list(self): def refresh_async(): return get_lite_programs(self.session) def cb_success(programs): sorted_programs = {} for program in programs: first_upload = program.cast_custom_option_value('first_upload', int, 0) if first_upload in sorted_programs: sorted_programs[first_upload][program.identifier] = program else: sorted_programs[first_upload] = {program.identifier: program} for first_upload in sorted(sorted_programs.keys()): for identifier in sorted(sorted_programs[first_upload].keys()): program = sorted_programs[first_upload][identifier] language = program.cast_custom_option_value('language', str, '<unknown>') try: language = Constants.get_language_display_name(language) except: pass item = QTreeWidgetItem([program.cast_custom_option_value('name', str, '<unknown>'), identifier, language]) self.tree_programs.addTopLevelItem(item) item.setSelected(True) self.refresh_in_progress = False self.update_ui_state() self.tree_programs.setFocus() def cb_error(error): pass # FIXME: report error self.refresh_in_progress = True self.update_ui_state() self.tree_programs.invisibleRootItem().takeChildren() async_call(refresh_async, None, cb_success, cb_error, pass_exception_to_error_callback=True) def export_archive(self): #FIXME: fromTime_t is obsolete: https://doc.qt.io/qt-5/qdatetime-obsolete.html#toTime_t timestamp = QDateTime.fromTime_t(int(time.time())).toString('yyyyMMdd-HHmmss') target_path = os.path.join(self.last_directory, 'red-brick-export-{0}.tfrba'.format(timestamp)) target_path = get_save_file_name(get_main_window(), 'Save Archive', target_path, 'Tinkerforge RED-Brick Archive(*.tfrba)') if len(target_path) == 0: return self.last_directory = os.path.split(target_path)[0] script_instance_ref = [None] def progress_canceled(): script_instance = script_instance_ref[0] if script_instance != None: self.script_manager.abort_script(script_instance) chunked_downloader = self.chunked_downloader if chunked_downloader != None: chunked_downloader.canceled = True self.progress = ExpandingProgressDialog(self) self.progress.set_progress_text_visible(False) self.progress.setModal(True) self.progress.setWindowTitle('Export Archive') self.progress.setLabelText('Step 1 of 2: Archiving selected programs') self.progress.setRange(0, 0) self.progress.setAutoClose(False) self.progress.canceled.connect(progress_canceled) self.progress.show() selected_identifiers = [] for selected_item in self.tree_programs.selectedItems(): selected_identifiers.append(selected_item.text(1)) def cb_export(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None if aborted: return if not report_script_result(result, 'Export Error', 'Could not archive selected programs', before_message_box=self.progress.close): return # step 2/2: download created archive source_path = posixpath.join(result.stdout.strip(), 'archive.tfrba') self.chunked_downloader = ChunkedDownloader(self) if not self.chunked_downloader.prepare(source_path): return self.progress.setLabelText('Step 2 of 2: Downloading archive') self.progress.set_progress_text_visible(True) self.chunked_downloader.start(target_path) # step 1/2: run export script to create archive script_instance_ref[0] = self.script_manager.execute_script('export', cb_export, selected_identifiers)
class REDTabImportExportExport(QWidget, Ui_REDTabImportExportExport): def __init__(self): QWidget.__init__(self) self.setupUi(self) self.session = None # Set from REDTabImportExport self.script_manager = None # Set from REDTabImportExport self.image_version = None # Set from REDTabImportExport self.first_tab_on_focus = True self.refresh_in_progress = False self.last_directory = get_home_path() self.progress = None self.chunked_downloader = None self.tree_programs.setColumnWidth(0, 150) self.tree_programs.setColumnWidth(1, 150) self.tree_programs.itemSelectionChanged.connect(self.update_ui_state) self.button_refresh_programs.clicked.connect(self.refresh_program_list) self.button_export.clicked.connect(self.export_archive) self.update_ui_state() def tab_on_focus(self): QTimer.singleShot(1, self.refresh_program_list) def tab_off_focus(self): pass def tab_destroy(self): pass def update_ui_state(self): if self.refresh_in_progress: self.progress_refresh_programs.setVisible(True) self.button_refresh_programs.setText('Refreshing...') self.button_refresh_programs.setEnabled(False) self.button_export.setEnabled(False) else: self.progress_refresh_programs.setVisible(False) self.button_refresh_programs.setText('Refresh') self.button_refresh_programs.setEnabled(True) self.button_export.setEnabled(len(self.tree_programs.selectedItems()) > 0) def refresh_program_list(self): def refresh_async(): return get_lite_programs(self.session) def cb_success(programs): sorted_programs = {} for program in programs: first_upload = program.cast_custom_option_value('first_upload', int, 0) if first_upload in sorted_programs: sorted_programs[first_upload][program.identifier] = program else: sorted_programs[first_upload] = {program.identifier: program} for first_upload in sorted(sorted_programs.keys()): for identifier in sorted(sorted_programs[first_upload].keys()): program = sorted_programs[first_upload][identifier] language = program.cast_custom_option_value('language', str, '<unknown>') try: language = Constants.get_language_display_name(language) except: pass item = QTreeWidgetItem([program.cast_custom_option_value('name', str, '<unknown>'), identifier, language]) self.tree_programs.addTopLevelItem(item) item.setSelected(True) self.refresh_in_progress = False self.update_ui_state() self.tree_programs.setFocus() def cb_error(error): pass # FIXME: report error self.refresh_in_progress = True self.update_ui_state() self.tree_programs.invisibleRootItem().takeChildren() async_call(refresh_async, None, cb_success, cb_error, pass_exception_to_error_callback=True) def export_archive(self): #FIXME: fromTime_t is obsolete: https://doc.qt.io/qt-5/qdatetime-obsolete.html#toTime_t timestamp = QDateTime.fromTime_t(int(time.time())).toString('yyyyMMdd-HHmmss') target_path = os.path.join(self.last_directory, 'red-brick-export-{0}.tfrba'.format(timestamp)) target_path = get_save_file_name(get_main_window(), 'Save Archive', target_path, '*.tfrba') if len(target_path) == 0: return self.last_directory = os.path.split(target_path)[0] script_instance_ref = [None] def progress_canceled(): script_instance = script_instance_ref[0] if script_instance != None: self.script_manager.abort_script(script_instance) chunked_downloader = self.chunked_downloader if chunked_downloader != None: chunked_downloader.canceled = True self.progress = ExpandingProgressDialog(self) self.progress.set_progress_text_visible(False) self.progress.setModal(True) self.progress.setWindowTitle('Export Archive') self.progress.setLabelText('Step 1 of 2: Archiving selected programs') self.progress.setRange(0, 0) self.progress.setAutoClose(False) self.progress.canceled.connect(progress_canceled) self.progress.show() selected_identifiers = [] for selected_item in self.tree_programs.selectedItems(): selected_identifiers.append(selected_item.text(1)) def cb_export(result): script_instance = script_instance_ref[0] if script_instance != None: aborted = script_instance.abort else: aborted = False script_instance_ref[0] = None if aborted: return if not report_script_result(result, 'Export Error', 'Could not archive selected programs', before_message_box=self.progress.close): return # step 2/2: download created archive source_path = posixpath.join(result.stdout.strip(), 'archive.tfrba') self.chunked_downloader = ChunkedDownloader(self) if not self.chunked_downloader.prepare(source_path): return self.progress.setLabelText('Step 2 of 2: Downloading archive') self.progress.set_progress_text_visible(True) self.chunked_downloader.start(target_path) # step 1/2: run export script to create archive script_instance_ref[0] = self.script_manager.execute_script('export', cb_export, selected_identifiers)