diff --git a/nomadnet/Conversation.py b/nomadnet/Conversation.py index 20bcdde..868eb1f 100644 --- a/nomadnet/Conversation.py +++ b/nomadnet/Conversation.py @@ -1,48 +1,147 @@ import os import RNS import LXMF +import shutil +from nomadnet.Directory import DirectoryEntry class Conversation: + cached_conversations = {} + created_callback = None + @staticmethod - def ingest(lxmessage, app): - source_hash_path = RNS.hexrep(lxmessage.source_hash, delimit=False) + def ingest(lxmessage, app, originator = False, delegate = None): + if originator: + source_hash = lxmessage.destination_hash + else: + source_hash = lxmessage.source_hash + + source_hash_path = RNS.hexrep(source_hash, delimit=False) + conversation_path = app.conversationpath + "/" + source_hash_path if not os.path.isdir(conversation_path): os.makedirs(conversation_path) + if Conversation.created_callback != None: + Conversation.created_callback() - lxmessage.write_to_directory(conversation_path) + ingested_path = lxmessage.write_to_directory(conversation_path) + + if RNS.hexrep(source_hash, delimit=False) in Conversation.cached_conversations: + conversation = Conversation.cached_conversations[RNS.hexrep(source_hash, delimit=False)] + conversation.scan_storage() + + return ingested_path @staticmethod def conversation_list(app): conversations = [] - for entry in os.listdir(app.conversationpath): - if len(entry) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2 and os.path.isdir(app.conversationpath + "/" + entry): + for dirname in os.listdir(app.conversationpath): + if len(dirname) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2 and os.path.isdir(app.conversationpath + "/" + dirname): try: + source_hash_text = dirname + source_hash = bytes.fromhex(dirname) + app_data = RNS.Identity.recall_app_data(source_hash) + display_name = app.directory.display_name(source_hash) + + if display_name == None and app_data: + display_name = app_data.decode("utf-8") + + trust_level = app.directory.trust_level(source_hash, display_name) + + entry = (source_hash_text, display_name, trust_level) conversations.append(entry) + except Exception as e: - RNS.log("Error while loading conversation "+str(entry)+", skipping it. The contained exception was: "+str(e), RNS.LOG_ERROR) + RNS.log("Error while loading conversation "+str(dirname)+", skipping it. The contained exception was: "+str(e), RNS.LOG_ERROR) + + conversations.sort(key=lambda e: (-e[2], e[1], e[0]), reverse=False) return conversations + @staticmethod + def cache_conversation(conversation): + Conversation.cached_conversations[conversation.source_hash] = conversation + @staticmethod + def delete_conversation(source_hash_path, app): + conversation_path = app.conversationpath + "/" + source_hash_path + + try: + if os.path.isdir(conversation_path): + shutil.rmtree(conversation_path) + except Exception as e: + RNS.log("Could not remove conversation at "+str(conversation_path)+". The contained exception was: "+str(e), RNS.LOG_ERROR) def __init__(self, source_hash, app): + self.app = app self.source_hash = source_hash + self.send_destination = None + self.messages = [] self.messages_path = app.conversationpath + "/" + source_hash self.messages_load_time = None - self.messages = [] self.source_known = False self.source_trusted = False self.source_blocked = False + self.__changed_callback = None + + self.source_identity = RNS.Identity.recall(bytes.fromhex(self.source_hash)) + + if self.source_identity: + self.source_known = True + self.send_destination = RNS.Destination(self.source_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") + + self.scan_storage() + + self.trust_level = app.directory.trust_level(bytes.fromhex(self.source_hash)) + + Conversation.cache_conversation(self) + + def scan_storage(self): + old_len = len(self.messages) + self.messages = [] for filename in os.listdir(self.messages_path): if len(filename) == RNS.Identity.HASHLENGTH//8*2: message_path = self.messages_path + "/" + filename self.messages.append(ConversationMessage(message_path)) + if self.__changed_callback != None: + self.__changed_callback(self) + + def __str__(self): - return self.source_hash + string = self.source_hash + + if self.source_identity: + if self.source_identity.app_data: + # TODO: Sanitise for viewing + string += " | "+self.source.source_identity.app_data.decode("utf-8") + + return string + + def register_changed_callback(self, callback): + self.__changed_callback = callback + + def send(self, content="", title=""): + if self.send_destination: + dest = self.send_destination + source = self.app.lxmf_destination + lxm = LXMF.LXMessage(dest, source, content, desired_method=LXMF.LXMessage.DIRECT) + lxm.register_delivery_callback(self.message_delivered) + self.app.message_router.handle_outbound(lxm) + + message_path = Conversation.ingest(lxm, self.app, originator=True) + self.messages.append(ConversationMessage(message_path)) + + else: + # TODO: Implement + # Alter UI so message cannot be sent until there is a path, or LXMF propagation is implemented + RNS.log("Destination unknown") + + def message_delivered(self, message): + message_path = Conversation.ingest(message, self.app, originator=True) + + class ConversationMessage: def __init__(self, file_path): @@ -63,6 +162,12 @@ class ConversationMessage: self.loaded = False self.lxm = None + def get_timestamp(self): + if not self.loaded: + self.load() + + return self.timestamp + def get_title(self): if not self.loaded: self.load()