func (d *Daemon) saveMessage(message *proto.Message) error { // generate metadata file metadata := proto.ConversationMetadata{ Participants: message.Participants, Subject: message.Subject, } // generate conversation name convName := persistence.ConversationName(&metadata) messageName := persistence.MessageName(time.Unix(0, message.Date), string(message.Dename)) // create conversation directory if it doesn't already exist convDir := filepath.Join(d.ConversationDir(), convName) outboxDir := filepath.Join(d.OutboxDir(), convName) _, err := os.Stat(convDir) if err != nil && !os.IsNotExist(err) { return err } else if err != nil && os.IsNotExist(err) { // new message in existing conversation if err := d.conversationToConversations(&metadata); err != nil { return err } } err = d.AtomicWriteFile(filepath.Join(convDir, messageName), message.Contents, 0600) if err != nil { return err } // to outbox tdir, err := d.MkdirInTemp() if err != nil { return err } defer shred.RemoveAll(tdir) err = d.MarshalToFile(filepath.Join(tdir, persistence.MetadataFileName), &metadata) if err != nil { return err } if err = os.Rename(filepath.Join(tdir), outboxDir); err != nil && !os.IsExist(err) && !strings.Contains(err.Error(), "directory not empty") { return err } return nil }
func (d *Daemon) processOutboxDir(dirname string) error { // TODO: refactor: separate message assembly and filesystem access? // parse metadata metadataFile := filepath.Join(dirname, persistence.MetadataFileName) if _, err := os.Stat(metadataFile); err != nil { return nil // no metadata --> not an outgoing message } metadata := proto.ConversationMetadata{} err := persistence.UnmarshalFromFile(metadataFile, &metadata) if err != nil { return err } metadata.Participants = append(metadata.Participants, d.Dename) undupStrings(metadata.Participants) sort.Strings(metadata.Participants) convName := persistence.ConversationName(&metadata) // load messages potentialMessages, err := ioutil.ReadDir(dirname) if err != nil { return err } messages := make([][]byte, 0, len(potentialMessages)) for _, finfo := range potentialMessages { if !finfo.IsDir() && finfo.Name() != persistence.MetadataFileName { msg, err := ioutil.ReadFile(filepath.Join(dirname, finfo.Name())) if err != nil { return err } // make protobuf for message; append it d.ourDenameLookupMu.Lock() payload := proto.Message{ Dename: d.Dename, DenameLookup: d.ourDenameLookup, Contents: msg, Subject: metadata.Subject, Participants: metadata.Participants, Date: finfo.ModTime().UnixNano(), } d.ourDenameLookupMu.Unlock() payloadBytes, err := payload.Marshal() if err != nil { return err } messages = append(messages, payloadBytes) } } if len(messages) == 0 { return nil // no messages to send, just the metadata file } if err := d.conversationToConversations(&metadata); err != nil && !os.IsExist(err) && !strings.Contains(fmt.Sprint(err), "directory not empty") { log.Fatal(err) } for _, recipient := range metadata.Participants { if recipient == d.Dename { continue } for _, msg := range messages { if err != nil { return err } if msgRatch, err := LoadRatchet(d, recipient, d.fillAuth, d.checkAuth); err != nil { //First message to this recipien if err := d.sendFirstMessage(msg, recipient); err != nil { return err } } else { if err := d.sendMessage(msg, recipient, msgRatch); err != nil { return err } } } } // move the sent messages to the conversation folder for _, finfo := range potentialMessages { if !finfo.IsDir() && finfo.Name() != persistence.MetadataFileName { if err = os.Rename(filepath.Join(dirname, finfo.Name()), filepath.Join(d.ConversationDir(), persistence.ConversationName(&metadata), persistence.MessageName(finfo.ModTime(), string(d.Dename)))); err != nil { log.Fatal(err) } } } // canonicalize the outbox folder name if dirname != filepath.Join(d.OutboxDir(), convName) { if err := os.Rename(dirname, filepath.Join(d.OutboxDir(), convName)); err != nil { shred.RemoveAll(dirname) } } return nil }