~emersion/public-inbox

go-maildir: Implement Copy function v1 PROPOSED

I've been wondering whether this should be part of go-maildir.
Probably, because we don't want the destination to see the copy as a
new message.

On Wednesday, July 24, 2019 3:49 PM, Ben Burwell <ben@benburwell.com> wrote:
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/public-inbox/%3C20190724124912.29177-1-ben%40benburwell.com%3E/mbox | git am -3
Learn more about email & git

[PATCH go-maildir] Implement Copy function Export this patch

---
 maildir.go      | 39 ++++++++++++++++++++++++++++++++++++
 maildir_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 92 insertions(+)

diff --git a/maildir.go b/maildir.go
index 8cab16d..c56d624 100644
--- a/maildir.go
+++ b/maildir.go
@@ -384,6 +384,45 @@ func (d Dir) Move(target Dir, key string) error {
 	return os.Rename(path, filepath.Join(string(target), "cur", filepath.Base(path)))
 }
 
+// Copy copies a message from this Maildir to another, preserving its flags.
+// The newly generate key in the target maildir is returned, along with an
+// error.
+func (d Dir) Copy(target Dir, key string) (string, error) {
+	flags, err := d.Flags(key)
+	if err != nil {
+		return "", err
+	}
+	rc, err := d.Open(key)
+	if err != nil {
+		return "", err
+	}
+	defer rc.Close()
+	key2, err := newKey()
+	if err != nil {
+		return "", err
+	}
+	tmpfile := filepath.Join(string(target), "tmp", key2)
+	wc, err := os.OpenFile(tmpfile, os.O_CREATE|os.O_WRONLY, 0600)
+	if err != nil {
+		return "", err
+	}
+	defer wc.Close()
+	if _, err = io.Copy(wc, rc); err != nil {
+		return "", err
+	}
+	rc.Close()
+	wc.Close()
+	suffix := string(separator) + "2,"
+	curfile := filepath.Join(string(target), "cur", key2+suffix)
+	if err = os.Rename(tmpfile, curfile); err != nil {
+		return "", err
+	}
+	if err = target.SetFlags(key2, flags); err != nil {
+		return "", err
+	}
+	return key2, nil
+}
+
 // Remove removes the actual file behind this message.
 func (d Dir) Remove(key string) error {
 	f, err := d.Filename(key)
diff --git a/maildir_test.go b/maildir_test.go
index 3164df3..ee79f2f 100644
--- a/maildir_test.go
+++ b/maildir_test.go
@@ -203,6 +203,59 @@ func TestMove(t *testing.T) {
 
 }
 
+func TestCopy(t *testing.T) {
+	t.Parallel()
+	var d1 Dir = "test_copy1"
+	err := d1.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer cleanup(t.Error, d1)
+	var d2 Dir = "test_copy2"
+	err = d2.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer cleanup(t.Error, d2)
+	const msg = "a moving message"
+	makeDelivery(t.Fatal, d1, msg)
+	keys, err := d1.Unseen()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = d1.SetFlags(keys[0], []Flag{FlagSeen}); err != nil {
+		t.Fatal(err)
+	}
+	key2, err := d1.Copy(d2, keys[0])
+	if err != nil {
+		t.Fatal(err)
+	}
+	path, err := d1.Filename(keys[0])
+	if err != nil {
+		t.Fatal(err)
+	}
+	if cat(t, path) != msg {
+		t.Error("original content has changed")
+	}
+	path, err = d2.Filename(key2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if cat(t, path) != msg {
+		t.Error("target content doesn't match source")
+	}
+	flags, err := d2.Flags(key2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(flags) != 1 {
+		t.Fatal("no flags on target")
+	}
+	if flags[0] != FlagSeen {
+		t.Error("seen flag not present on target")
+	}
+}
+
 func BenchmarkFilename(b *testing.B) {
 	// set up test maildir
 	d := Dir("benchmark_filename")
-- 
2.22.0
I've been wondering whether this should be part of go-maildir.
Probably, because we don't want the destination to see the copy as a
new message.

On Wednesday, July 24, 2019 3:49 PM, Ben Burwell <ben@benburwell.com> wrote:
View this thread in the archives