From 730c17c981a8bf620590a3ba76372f7513e6a253 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 19 Nov 2022 20:04:01 +0100 Subject: [PATCH] Implemented paper message handling --- nomadnet/Conversation.py | 29 ++++++ nomadnet/NomadNetworkApp.py | 35 ++++++- nomadnet/ui/TextUI.py | 4 +- nomadnet/ui/textui/Conversations.py | 145 +++++++++++++++++++++++++++- 4 files changed, 207 insertions(+), 6 deletions(-) diff --git a/nomadnet/Conversation.py b/nomadnet/Conversation.py index d36f2eb..35b8cf5 100644 --- a/nomadnet/Conversation.py +++ b/nomadnet/Conversation.py @@ -227,6 +227,35 @@ class Conversation: RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE) return False + def paper_output(self, content="", title=""): + if self.send_destination: + try: + dest = self.send_destination + source = self.app.lxmf_destination + desired_method = LXMF.LXMessage.PAPER + + lxm = LXMF.LXMessage(dest, source, content, title=title, desired_method=desired_method) + qr_code = lxm.as_qr() + qr_tmp_path = self.app.tmpfilespath+"/"+str(RNS.hexrep(lxm.hash, delimit=False)) + qr_code.save(qr_tmp_path) + + print_result = self.app.print_file(qr_tmp_path) + os.unlink(qr_tmp_path) + + if print_result: + message_path = Conversation.ingest(lxm, self.app, originator=True) + self.messages.append(ConversationMessage(message_path)) + + return print_result + + except Exception as e: + RNS.log("An error occurred while generating paper message, the contained exception was: "+str(e), RNS.LOG_ERROR) + return False + + else: + RNS.log("Destination is not known, cannot create LXMF Message.", RNS.LOG_VERBOSE) + return False + def message_notification(self, message): if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail: RNS.log("Direct delivery of "+str(message)+" failed. Retrying as propagated message.", RNS.LOG_VERBOSE) diff --git a/nomadnet/NomadNetworkApp.py b/nomadnet/NomadNetworkApp.py index d0ac8a5..2dea9ad 100644 --- a/nomadnet/NomadNetworkApp.py +++ b/nomadnet/NomadNetworkApp.py @@ -105,6 +105,7 @@ class NomadNetworkApp: self.conversationpath = self.configdir+"/storage/conversations" self.directorypath = self.configdir+"/storage/directory" self.peersettingspath = self.configdir+"/storage/peersettings" + self.tmpfilespath = self.configdir+"/storage/tmp" self.pagespath = self.configdir+"/storage/pages" self.filespath = self.configdir+"/storage/files" @@ -145,6 +146,11 @@ class NomadNetworkApp: if not os.path.isdir(self.cachepath): os.makedirs(self.cachepath) + if not os.path.isdir(self.tmpfilespath): + os.makedirs(self.tmpfilespath) + else: + self.clear_tmp_dir() + if os.path.isfile(self.configpath): try: self.config = ConfigObj(self.configpath) @@ -514,6 +520,26 @@ class NomadNetworkApp: return False + def print_file(self, filename): + print_command = self.print_command+" "+filename + + try: + return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + except Exception as e: + RNS.log("An error occurred while executing print command: "+str(print_command), RNS.LOG_ERROR) + RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) + return False + + if return_code == 0: + RNS.log("Successfully printed "+str(filename)+" using print command: "+print_command, RNS.LOG_DEBUG) + return True + + else: + RNS.log("Printing "+str(filename)+" failed using print command: "+print_command, RNS.LOG_DEBUG) + return False + + def print_message(self, message, received = None): try: template = self.printing_template_msg @@ -547,8 +573,7 @@ class NomadNetworkApp: f.write(output.encode("utf-8")) f.close() - print_command = "lp -d thermal -o cpi=16 -o lpi=8 "+filename - return_code = subprocess.call(shlex.split(print_command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + self.print_file(filename) os.unlink(filename) @@ -576,6 +601,12 @@ class NomadNetworkApp: if os.path.isfile(self.conversationpath + "/" + source_hash + "/unread"): os.unlink(self.conversationpath + "/" + source_hash + "/unread") + def clear_tmp_dir(self): + if os.path.isdir(self.tmpfilespath): + for file in os.listdir(self.tmpfilespath): + fpath = self.tmpfilespath+"/"+file + os.unlink(fpath) + def createDefaultConfig(self): self.config = ConfigObj(__default_nomadnet_config__) self.config.filename = self.configpath diff --git a/nomadnet/ui/TextUI.py b/nomadnet/ui/TextUI.py index ae3e17d..6389aac 100644 --- a/nomadnet/ui/TextUI.py +++ b/nomadnet/ui/TextUI.py @@ -121,7 +121,9 @@ GLYPHS = { ("decoration_menu", " +", " +", " \uf93a"), ("unread_menu", " !", " \u2709", urm_char), ("globe", "", "", "\uf484"), - ("sent", "/\\", "\u2191", "\ufbf4") + ("sent", "/\\", "\u2191", "\ufbf4"), + ("papermsg", "P", "\u25a4", "\uf719"), + ("qrcode", "QR", "\u25a4", "\uf029"), } class TextUI: diff --git a/nomadnet/ui/textui/Conversations.py b/nomadnet/ui/textui/Conversations.py index cac9b89..8de7065 100644 --- a/nomadnet/ui/textui/Conversations.py +++ b/nomadnet/ui/textui/Conversations.py @@ -14,13 +14,13 @@ class ConversationListDisplayShortcuts(): def __init__(self, app): self.app = app - self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info [C-x] Delete [C-r] Sync [C-n] New [C-g] Fullscreen"), "shortcutbar") + self.widget = urwid.AttrMap(urwid.Text("[C-e] Peer Info [C-x] Delete [C-r] Sync [C-n] New [C-u] Ingest URL [C-g] Fullscreen"), "shortcutbar") class ConversationDisplayShortcuts(): def __init__(self, app): self.app = app - self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-k] Clear [C-w] Close [C-t] Title [C-p] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar") + self.widget = urwid.AttrMap(urwid.Text("[C-d] Send [C-p] Paper Msg [C-t] Title [C-k] Clear [C-w] Close [C-u] Purge [C-x] Clear History [C-o] Sort"), "shortcutbar") class ConversationsArea(urwid.LineBox): def keypress(self, size, key): @@ -30,6 +30,8 @@ class ConversationsArea(urwid.LineBox): self.delegate.delete_selected_conversation() elif key == "ctrl n": self.delegate.new_conversation() + elif key == "ctrl u": + self.delegate.ingest_lxm_url() elif key == "ctrl r": self.delegate.sync_conversations() elif key == "ctrl g": @@ -330,6 +332,110 @@ class ConversationsDisplay(): options = self.columns_widget.options("given", ConversationsDisplay.given_list_width) self.columns_widget.contents[0] = (overlay, options) + def ingest_lxm_url(self): + self.dialog_open = True + lxm_url = "" + e_url = urwid.Edit(caption="URL : ",edit_text=lxm_url) + + def dismiss_dialog(sender): + self.update_conversation_list() + self.dialog_open = False + + def confirmed(sender): + try: + local_delivery_signal = "local_delivery_occurred" + duplicate_signal = "duplicate_lxm" + lxm_url = e_url.get_edit_text() + + ingest_result = self.app.message_router.ingest_lxm_url( + lxm_url, + signal_local_delivery=local_delivery_signal, + signal_duplicate=duplicate_signal + ) + + if ingest_result == False: + raise ValueError("The URL contained no decodable messages") + + elif ingest_result == local_delivery_signal: + rdialog_pile = urwid.Pile([ + urwid.Text("Message was decoded, decrypted successfully, and added to your conversation list."), + urwid.Text(""), + urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))]) + ]) + rdialog_pile.error_display = False + + rdialog = DialogLineBox(rdialog_pile, title="Ingest message URL") + rdialog.delegate = self + bottom = self.listbox + + roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2) + + options = self.columns_widget.options("given", ConversationsDisplay.given_list_width) + self.columns_widget.contents[0] = (roverlay, options) + + elif ingest_result == duplicate_signal: + rdialog_pile = urwid.Pile([ + urwid.Text("The decoded message has already been processed by the LXMF Router, and will not be ingested again."), + urwid.Text(""), + urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))]) + ]) + rdialog_pile.error_display = False + + rdialog = DialogLineBox(rdialog_pile, title="Ingest message URL") + rdialog.delegate = self + bottom = self.listbox + + roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2) + + options = self.columns_widget.options("given", ConversationsDisplay.given_list_width) + self.columns_widget.contents[0] = (roverlay, options) + + else: + if self.app.enable_node: + propagation_text = "The decoded message was not addressed to this LXMF address, but has been added to the propagation node queues, and will be distributed on the propagation network." + else: + propagation_text = "The decoded message was not addressed to this LXMF address, and has been discarded." + + rdialog_pile = urwid.Pile([ + urwid.Text(propagation_text), + urwid.Text(""), + urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))]) + ]) + rdialog_pile.error_display = False + + rdialog = DialogLineBox(rdialog_pile, title="Ingest message URL") + rdialog.delegate = self + bottom = self.listbox + + roverlay = urwid.Overlay(rdialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2) + + options = self.columns_widget.options("given", ConversationsDisplay.given_list_width) + self.columns_widget.contents[0] = (roverlay, options) + + except Exception as e: + RNS.log("Could not ingest LXM URL. The contained exception was: "+str(e), RNS.LOG_VERBOSE) + if not dialog_pile.error_display: + dialog_pile.error_display = True + options = dialog_pile.options(height_type="pack") + dialog_pile.contents.append((urwid.Text(""), options)) + dialog_pile.contents.append((urwid.Text(("error_text", "Could ingest LXM from URL data. Check your input."), align="center"), options)) + + dialog_pile = urwid.Pile([ + e_url, + urwid.Text(""), + urwid.Columns([("weight", 0.45, urwid.Button("Ingest", on_press=confirmed)), ("weight", 0.1, urwid.Text("")), ("weight", 0.45, urwid.Button("Back", on_press=dismiss_dialog))]) + ]) + dialog_pile.error_display = False + + dialog = DialogLineBox(dialog_pile, title="Ingest message URL") + dialog.delegate = self + bottom = self.listbox + + overlay = urwid.Overlay(dialog, bottom, align="center", width=("relative", 100), valign="middle", height="pack", left=2, right=2) + + options = self.columns_widget.options("given", ConversationsDisplay.given_list_width) + self.columns_widget.contents[0] = (overlay, options) + def delete_conversation(self, source_hash): if source_hash in ConversationsDisplay.cached_conversation_widgets: conversation = ConversationsDisplay.cached_conversation_widgets[source_hash] @@ -636,6 +742,8 @@ class MessageEdit(urwid.Edit): def keypress(self, size, key): if key == "ctrl d": self.delegate.send_message() + elif key == "ctrl p": + self.delegate.paper_message() elif key == "ctrl k": self.delegate.clear_editor() elif key == "up": @@ -800,7 +908,7 @@ class ConversationWidget(urwid.WidgetWrap): self.toggle_focus_area() elif key == "ctrl w": self.close() - elif key == "ctrl p": + elif key == "ctrl u": self.conversation.purge_failed() self.conversation_changed(None) elif key == "ctrl t": @@ -860,6 +968,34 @@ class ConversationWidget(urwid.WidgetWrap): else: pass + def paper_message(self): + content = self.content_editor.get_edit_text() + title = self.title_editor.get_edit_text() + if not content == "": + if self.conversation.paper_output(content, title): + self.clear_editor() + else: + self.paper_message_failed() + + def paper_message_failed(self): + def dismiss_dialog(sender): + self.dialog_open = False + self.conversation_changed(None) + + dialog = DialogLineBox( + urwid.Pile([ + urwid.Text("Could not output paper message,\ncheck your settings. See the log\nfile for any error messages.\n", align="center"), + urwid.Columns([("weight", 0.6, urwid.Text("")), ("weight", 0.4, urwid.Button("OK", on_press=dismiss_dialog))]) + ]), title="!" + ) + dialog.delegate = self + bottom = self.messagelist + + overlay = urwid.Overlay(dialog, bottom, align="center", width=34, valign="middle", height="pack", left=2, right=2) + + self.frame.contents["body"] = (overlay, self.frame.options()) + self.frame.set_focus("body") + def close(self): self.delegate.close_conversation(self) @@ -890,6 +1026,9 @@ class LXMessageWidget(urwid.WidgetWrap): elif message.lxm.method == LXMF.LXMessage.PROPAGATED and message.lxm.state == LXMF.LXMessage.SENT: header_style = "msg_header_propagated" title_string = g["sent"]+" "+title_string + elif message.lxm.method == LXMF.LXMessage.PAPER and message.lxm.state == LXMF.LXMessage.PAPER: + header_style = "msg_header_propagated" + title_string = g["papermsg"]+" "+title_string elif message.lxm.state == LXMF.LXMessage.SENT: header_style = "msg_header_sent" title_string = g["sent"]+" "+title_string