def view_log(self, item): file_name = posixpath.join(self.log_directory, item.data(USER_ROLE_FILE_NAME)) self.set_program_callbacks_enabled(False) self.view_dialog = ProgramInfoLogsView(self, self.session, file_name) self.view_dialog.exec_() self.view_dialog = None if self.is_alive(): self.set_program_callbacks_enabled(True)
class ProgramInfoLogs(QWidget, Ui_ProgramInfoLogs): def __init__(self, context, update_main_ui_state, set_widget_enabled, is_alive, show_download_wizard, set_program_callbacks_enabled): QWidget.__init__(self) self.setupUi(self) self.session = context.session self.script_manager = context.script_manager self.program = context.program self.update_main_ui_state = update_main_ui_state self.set_widget_enabled = set_widget_enabled self.is_alive = is_alive self.show_download_wizard = show_download_wizard self.set_program_callbacks_enabled = set_program_callbacks_enabled self.log_directory = posixpath.join(self.program.root_directory, 'log') self.refresh_in_progress = False self.any_refresh_in_progress = False # set from ProgramInfoMain.update_ui_state self.view_dialog = None self.file_icon = QIcon(load_pixmap('file-icon.png')) self.tree_logs_model = QStandardItemModel(self) self.tree_logs_model_header = ['Date/Time', 'Size'] self.tree_logs_proxy_model = LogsProxyModel(self) self.last_download_directory = get_home_path() self.tree_logs_model.setHorizontalHeaderLabels(self.tree_logs_model_header) self.tree_logs_proxy_model.setSourceModel(self.tree_logs_model) self.tree_logs.setModel(self.tree_logs_proxy_model) self.tree_logs.setColumnWidth(0, 250) self.tree_logs.selectionModel().selectionChanged.connect(self.update_ui_state) self.tree_logs.activated.connect(self.view_activated_log) self.button_download_logs.clicked.connect(self.download_selected_logs) self.button_view_log.clicked.connect(self.view_selected_log) self.button_delete_logs.clicked.connect(self.delete_selected_logs) self.label_error.setVisible(False) def update_ui_state(self): selection_count = len(self.tree_logs.selectionModel().selectedRows()) self.set_widget_enabled(self.button_download_logs, not self.any_refresh_in_progress and selection_count > 0) self.set_widget_enabled(self.button_view_log, not self.any_refresh_in_progress and selection_count == 1 and len(self.get_directly_selected_log_items()) == 1) self.set_widget_enabled(self.button_delete_logs, not self.any_refresh_in_progress and selection_count > 0) def close_all_dialogs(self): if self.view_dialog != None: self.view_dialog.close() def refresh_logs_done(self): self.refresh_in_progress = False self.update_main_ui_state() def refresh_logs(self): def cb_program_logs_list(result): okay, message = check_script_result(result, decode_stderr=True) if not okay: self.label_error.setText('<b>Error:</b> ' + html.escape(message)) self.label_error.setVisible(True) self.refresh_logs_done() return try: # FIXME: do decompress in an async_call program_logs_list = json.loads(zlib.decompress(memoryview(result.stdout)).decode('utf-8')) except: program_logs_list = None if program_logs_list == None or not isinstance(program_logs_list, dict): self.label_error.setText('<b>Error:</b> Received invalid data') self.label_error.setVisible(True) self.refresh_logs_done() return self.label_error.setVisible(False) def create_file_size_item(size): item = QStandardItem(get_file_display_size(size)) item.setData(size, USER_ROLE_SIZE) return item def update_file_size_item(item, additional_size): current_size = item.data(USER_ROLE_SIZE) new_size = current_size + additional_size item.setText(get_file_display_size(new_size)) item.setData(new_size, USER_ROLE_SIZE) continuous_row = None date_rows = {} time_rows = {} for file_name, file_size in program_logs_list.items(): QApplication.processEvents() file_name_parts = file_name.split('_') if file_name_parts[0] == "continuous": if len(file_name_parts) != 2: continue if continuous_row == None: continuous_item = QStandardItem("Continuous") continuous_item.setData(ITEM_TYPE_PARENT_CONT, USER_ROLE_ITEM_TYPE) continuous_row = [continuous_item, create_file_size_item(0)] self.tree_logs_model.appendRow(continuous_row) log_item = QStandardItem(file_name_parts[1]) log_item.setData(self.file_icon, Qt.DecorationRole) log_item.setData(file_name, USER_ROLE_FILE_NAME) log_item.setData(ITEM_TYPE_LOG_FILE_CONT, USER_ROLE_ITEM_TYPE) continuous_row[0].appendRow([log_item, create_file_size_item(file_size)]) update_file_size_item(continuous_row[1], file_size) else: if len(file_name_parts) != 3: continue try: timestamp = int(file_name_parts[1].split('+')[0]) // 1000000 except ValueError: continue #FIXME: fromTime_t is obsolete: https://doc.qt.io/qt-5/qdatetime-obsolete.html#toTime_t date = QDateTime.fromTime_t(timestamp).toString('yyyy-MM-dd') time = QDateTime.fromTime_t(timestamp).toString('HH:mm:ss') date_time = date + 'T' + time if date in date_rows: date_row = date_rows[date] else: date_item = QStandardItem(date) date_item.setData(ITEM_TYPE_PARENT_DATE, USER_ROLE_ITEM_TYPE) date_row = [date_item, create_file_size_item(0)] date_rows[date] = date_row self.tree_logs_model.appendRow(date_row) if date_time in time_rows: time_row = time_rows[date_time] else: time_item = QStandardItem(time) time_item.setData(ITEM_TYPE_PARENT_TIME, USER_ROLE_ITEM_TYPE) time_row = [time_item, create_file_size_item(0)] time_rows[date_time] = time_row date_row[0].appendRow(time_row) log_item = QStandardItem(file_name_parts[2]) log_item.setData(self.file_icon, Qt.DecorationRole) log_item.setData(file_name, USER_ROLE_FILE_NAME) log_item.setData(ITEM_TYPE_LOG_FILE, USER_ROLE_ITEM_TYPE) time_row[0].appendRow([log_item, create_file_size_item(file_size)]) update_file_size_item(time_row[1], file_size) update_file_size_item(date_row[1], file_size) self.tree_logs.header().setSortIndicator(0, Qt.DescendingOrder) self.refresh_logs_done() self.refresh_in_progress = True self.update_main_ui_state() width = self.tree_logs.columnWidth(0) self.tree_logs_model.clear() self.tree_logs_model.setHorizontalHeaderLabels(self.tree_logs_model_header) self.tree_logs.setColumnWidth(0, width) self.script_manager.execute_script('program_logs_list', cb_program_logs_list, [self.log_directory], max_length=1024*1024, decode_output_as_utf8=False) def get_directly_selected_log_items(self): selected_indexes = self.tree_logs.selectedIndexes() selected_log_items = [] for selected_index in selected_indexes: if selected_index.column() == 0: mapped_index = self.tree_logs_proxy_model.mapToSource(selected_index) selected_item = self.tree_logs_model.itemFromIndex(mapped_index) item_type = selected_item.data(USER_ROLE_ITEM_TYPE) if item_type in [ITEM_TYPE_LOG_FILE, ITEM_TYPE_LOG_FILE_CONT]: selected_log_items.append(selected_item) return selected_log_items def load_log_files_for_ops(self, index_list): logs_download_dict = {'files': {}, 'total_download_size': 0, 'foobar':[]} def populate_log_download(item_list): item_type = item_list[0].data(USER_ROLE_ITEM_TYPE) if item_type == ITEM_TYPE_PARENT_CONT: for i in range(item_list[0].rowCount()): f_size = item_list[0].child(i, 1).data(USER_ROLE_SIZE) # File size file_name = item_list[0].child(i, 0).data(USER_ROLE_FILE_NAME) f_path = posixpath.join(self.log_directory, file_name) # File path if not f_path in logs_download_dict['files']: logs_download_dict['files'][f_path] = {'size': f_size} logs_download_dict['total_download_size'] += f_size logs_download_dict['foobar'].append(file_name) elif item_type == ITEM_TYPE_PARENT_DATE: for i in range(item_list[0].rowCount()): parent_time = item_list[0].child(i) for j in range(parent_time.rowCount()): f_size = parent_time.child(j, 1).data(USER_ROLE_SIZE) # File size file_name = parent_time.child(j, 0).data(USER_ROLE_FILE_NAME) f_path = posixpath.join(self.log_directory, file_name) # File path if not f_path in logs_download_dict['files']: logs_download_dict['files'][f_path] = {'size': f_size} logs_download_dict['total_download_size'] += f_size logs_download_dict['foobar'].append(file_name) elif item_type == ITEM_TYPE_PARENT_TIME: for i in range(item_list[0].rowCount()): f_size = item_list[0].child(i, 1).data(USER_ROLE_SIZE) # File size file_name = item_list[0].child(i, 0).data(USER_ROLE_FILE_NAME) f_path = posixpath.join(self.log_directory, file_name) # File path if not f_path in logs_download_dict['files']: logs_download_dict['files'][f_path] = {'size': f_size} logs_download_dict['total_download_size'] += f_size logs_download_dict['foobar'].append(file_name) elif item_type in [ITEM_TYPE_LOG_FILE, ITEM_TYPE_LOG_FILE_CONT]: f_size = item_list[1].data(USER_ROLE_SIZE) # File size file_name = item_list[0].data(USER_ROLE_FILE_NAME) f_path = posixpath.join(self.log_directory, file_name) # File path if not f_path in logs_download_dict['files']: logs_download_dict['files'][f_path] = {'size': f_size} logs_download_dict['total_download_size'] += f_size logs_download_dict['foobar'].append(file_name) index_rows = [] for index in index_list: if index.column() == 0: index_rows.append([index, index.sibling(index.row(), 1)]) for index_row in index_rows: item_list = [] for index in index_row: item = self.tree_logs_model.itemFromIndex(self.tree_logs_proxy_model.mapToSource(index)) item_list.append(item) populate_log_download(item_list) return logs_download_dict def download_selected_logs(self): index_list = self.tree_logs.selectedIndexes() if len(index_list) == 0: return log_files_to_download = self.load_log_files_for_ops(index_list) if len(log_files_to_download['foobar']) == 0: return download_directory = get_existing_directory(get_main_window(), 'Download Logs', self.last_download_directory) if len(download_directory) == 0: return self.last_download_directory = download_directory downloads = [] for file_name in log_files_to_download['foobar']: downloads.append(Download(file_name, file_name)) self.show_download_wizard('logs', download_directory, downloads) def view_activated_log(self, index): if index.column() == 0 and not self.any_refresh_in_progress: mapped_index = self.tree_logs_proxy_model.mapToSource(index) item = self.tree_logs_model.itemFromIndex(mapped_index) item_type = item.data(USER_ROLE_ITEM_TYPE) if item_type in [ITEM_TYPE_LOG_FILE, ITEM_TYPE_LOG_FILE_CONT]: self.view_log(item) def view_selected_log(self): selection_count = len(self.tree_logs.selectionModel().selectedRows()) if selection_count != 1: return selected_log_items = self.get_directly_selected_log_items() if len(selected_log_items) != 1: return self.view_log(selected_log_items[0]) def view_log(self, item): file_name = posixpath.join(self.log_directory, item.data(USER_ROLE_FILE_NAME)) self.set_program_callbacks_enabled(False) self.view_dialog = ProgramInfoLogsView(self, self.session, file_name) self.view_dialog.exec_() self.view_dialog = None if self.is_alive(): self.set_program_callbacks_enabled(True) # FIXME: make this work like delete_selected_files def delete_selected_logs(self): button = QMessageBox.question(get_main_window(), 'Delete Logs', 'Irreversibly deleting selected logs.', QMessageBox.Ok, QMessageBox.Cancel) if not self.is_alive() or button != QMessageBox.Ok: return def cb_delete(result): self.refresh_logs() report_script_result(result, 'Delete Logs Error', 'Could not delete selected logs') index_list = self.tree_logs.selectedIndexes() if not index_list: return log_files_to_delete = self.load_log_files_for_ops(index_list) if not log_files_to_delete: return file_list = [] for f_path in log_files_to_delete['files']: file_list.append(f_path) if len(file_list) > 0: self.script_manager.execute_script('delete', cb_delete, [json.dumps(file_list), json.dumps([])], execute_as_user=True)