aboutsummaryrefslogtreecommitdiff
path: root/internal/imap
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--internal/imap/cmds.go16
-rw-r--r--internal/imap/connection.go109
2 files changed, 121 insertions, 4 deletions
diff --git a/internal/imap/cmds.go b/internal/imap/cmds.go
index d978d80..7c99fc3 100644
--- a/internal/imap/cmds.go
+++ b/internal/imap/cmds.go
@@ -24,3 +24,19 @@ func (cmd addCommando) execute(conn *connection) error {
func (client *Client) PutMessages(folder Folder, messages []string) error {
return client.commander.execute(addCommando{folder, messages})
}
+
+type replaceCommando struct {
+ folder Folder
+ header string
+ value string
+ newContent string
+ force bool
+}
+
+func (cmd replaceCommando) execute(conn *connection) error {
+ return conn.replace(cmd.folder, cmd.header, cmd.value, cmd.newContent, cmd.force)
+}
+
+func (client *Client) Replace(folder Folder, header, value, newContent string, force bool) error {
+ return client.commander.execute(replaceCommando{folder, header, value, newContent, force})
+}
diff --git a/internal/imap/connection.go b/internal/imap/connection.go
index 5f62586..68b7e6b 100644
--- a/internal/imap/connection.go
+++ b/internal/imap/connection.go
@@ -128,16 +128,117 @@ func (conn *connection) ensureFolder(folder Folder) error {
}
}
+func (conn *connection) delete(uids []uint32) error {
+ storeItem := imap.FormatFlagsOp(imap.AddFlags, true)
+ seqSet := new(imap.SeqSet)
+ seqSet.AddNum(uids...)
+
+ if err := conn.c.UidStore(seqSet, storeItem, imap.DeletedFlag, nil); err != nil {
+ return fmt.Errorf("marking as deleted: %w", err)
+ }
+
+ if err := conn.c.Expunge(nil); err != nil {
+ return fmt.Errorf("expunging: %w", err)
+ }
+
+ return nil
+}
+
+func (conn *connection) fetchFlags(uid uint32) ([]string, error) {
+ fetchItem := []imap.FetchItem{imap.FetchFlags}
+
+ seqSet := new(imap.SeqSet)
+ seqSet.AddNum(uid)
+
+ messages := make(chan *imap.Message, 1)
+ done := make(chan error, 1)
+ go func() {
+ done <- conn.c.UidFetch(seqSet, fetchItem, messages)
+ }()
+
+ msg := <-messages
+ err := <-done
+
+ if err != nil {
+ return nil, fmt.Errorf("fetching flags: %w", err)
+ }
+ return msg.Flags, nil
+}
+
+func (conn *connection) replace(folder Folder, header, value, newContent string, force bool) error {
+ var err error
+ var msgIds []uint32
+
+ if err = conn.selectFolder(folder); err != nil {
+ return err
+ }
+
+ if msgIds, err = conn.searchHeader(header, value); err != nil {
+ return err
+ }
+
+ if len(msgIds) == 0 {
+ if force {
+ return conn.append(folder, nil, newContent)
+ }
+ return nil // nothing to do
+ }
+
+ var flags []string
+ if flags, err = conn.fetchFlags(msgIds[0]); err != nil {
+ return err
+ }
+
+ if err = conn.delete(msgIds); err != nil {
+ return err
+ }
+
+ if err = conn.append(folder, flags, newContent); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (conn *connection) searchHeader(header, value string) ([]uint32, error) {
+ criteria := imap.NewSearchCriteria()
+ criteria.Header.Set(header, value)
+ ids, err := conn.search(criteria)
+ if err != nil {
+ return nil, fmt.Errorf("searching for header %q=%q: %w", header, value, err)
+ }
+ return ids, nil
+}
+
+func (conn *connection) search(criteria *imap.SearchCriteria) ([]uint32, error) {
+ return conn.c.UidSearch(criteria)
+}
+
+func (conn *connection) selectFolder(folder Folder) error {
+ if _, err := conn.c.Select(folder.str, false); err != nil {
+ return fmt.Errorf("selecting folder %s: %w", folder, err)
+ }
+
+ return nil
+}
+
+func (conn *connection) append(folder Folder, flags []string, msg string) error {
+ reader := strings.NewReader(msg)
+ if err := conn.c.Append(folder.str, flags, time.Now(), reader); err != nil {
+ return fmt.Errorf("uploading message to %s: %w", folder, err)
+ }
+
+ return nil
+}
+
func (conn *connection) putMessages(folder Folder, messages []string) error {
if len(messages) == 0 {
return nil
}
- now := time.Now()
for _, msg := range messages {
- reader := strings.NewReader(msg)
- if err := conn.c.Append(folder.str, nil, now, reader); err != nil {
- return fmt.Errorf("uploading message to %s: %w", folder, err)
+ if err := conn.append(folder, nil, msg); err != nil {
+ return err
}
}
/cgit.git/commit/shared.c?h=v0.8.3.1&id=d6f6072560c963065b13c704fa1fa6f8950e4bac&follow=1'>Add generic filter/plugin infrastructureLars Hjemli3-0/+62 2009-07-25Add support for mime type registration and lookupLars Hjemli4-5/+45 2009-07-25cgit.h: keep config flags sortedLars Hjemli1-2/+2 2009-07-25cgitrc.5.txt: document 'embedded' and 'noheader'Lars Hjemli1-0/+9 2009-07-25Add support for 'noheader' optionLars Hjemli3-7/+16 2009-07-25cgitrc.5.txt: document 'head-include'Lars Hjemli1-0/+4 2009-07-25ui-blob: return 'application/octet-stream' for binary blobsLars Hjemli1-1/+7 2009-07-25ui-plain: Return 'application/octet-stream' for binary files.Remko Tronçon1-1/+4 2009-06-11use cgit_httpscheme() for atom feedDiego Ongaro2-3/+6 2009-06-11add cgit_httpscheme() -> http:// or https://Diego Ongaro2-0/+12 2009-06-07Return http statuscode 404 on unknown branchLars Hjemli3-0/+6 2009-06-07Add head-include configuration option.Mark Lodato3-1/+6 2009-03-15CGIT 0.8.2.1v0.8.2.1Lars Hjemli1-1/+1 2009-03-15Fix doc-related glitches in Makefile and .gitignoreLars Hjemli2-1/+6 2009-03-15ui-snapshot: avoid segfault when no filename is specifiedLars Hjemli1-6/+17 2009-03-15fix segfault when displaying empty blobsEric Wong1-5/+8 2009-02-19Add support for HEAD requestsLars Hjemli2-0/+7 2009-02-19Add support for ETag in 'plain' viewLars Hjemli4-0/+5 2009-02-12ui-tree: escape ascii-text properly in hexdump viewLars Hjemli1-4/+9 2009-02-12Makefile: add doc-related targetsLars Hjemli1-2/+17