sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 4c2ad353fb5f79f2c48a26980400be34a5b9e7f8
parent e69c32cf9167851555361c01476e4181814d1bb3
Author: Eric Weikl <eric.weikl@gmx.net>
Date:   Sun,  1 Sep 2013 15:16:21 +0200

Synchronize maildir syncback (#137)

It is possible for maildir sync back to occur during polling. To prevent this,
syncback now locks the source before writing back labels.

poll_lock is now a monitor instead of a mutex, so reentrant locking is
possible. Sources can be locked directly using Source#try_lock, Source#unlock
and Source#synchronize.

Moved some syncback logic from Message to Location. The configuration check
for syncback is now also checked there, so we can add source-specific syncback
per source (see #141).

Diffstat:
M lib/sup/maildir.rb | 7 +++++--
M lib/sup/message.rb | 20 +++++++++++++-------
M lib/sup/poll.rb | 7 +++----
M lib/sup/sent.rb | 2 +-
M lib/sup/source.rb | 24 ++++++++++++++++++++++--
5 files changed, 44 insertions(+), 16 deletions(-)
diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb
@@ -77,8 +77,11 @@ class Maildir < Source
   end
 
   def sync_back id, labels
-    flags = maildir_reconcile_flags id, labels
-    maildir_mark_file id, flags
+    synchronize do
+      debug "syncing back maildir message #{id} with flags #{labels.to_a}"
+      flags = maildir_reconcile_flags id, labels
+      maildir_mark_file id, flags
+    end
   end
 
   def raw_header id
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
@@ -292,10 +292,7 @@ EOS
   end
 
   def sync_back
-    if @locations.map { |l|
-      l.sync_back @labels if l.valid? and $config[:sync_back_to_maildir] and l.source.is_a? Maildir
-    }.any?
-      Index.sync_message self, true
+    @locations.map { |l| l.sync_back @labels, self }.any? do
       UpdateManager.relay self, :updated, self
     end
   end
@@ -733,9 +730,18 @@ class Location
     source.raw_message info
   end
 
-  def sync_back labels
-    new_info = source.sync_back(@info, labels) if source.respond_to? :sync_back
-    @info = new_info if new_info
+  def sync_back labels, message
+    synced = false
+    return synced unless $config[:sync_back_to_maildir] and valid? and source.respond_to? :sync_back
+    source.synchronize do
+      new_info = source.sync_back(@info, labels)
+      if new_info
+        @info = new_info
+        Index.sync_message message, true
+        synced = true
+      end
+    end
+    synced
   end
 
   ## much faster than raw_message
diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb
@@ -195,9 +195,8 @@ EOS
   ## labels and locations set correctly. The Messages are saved to or removed
   ## from the index after being yielded.
   def poll_from source, opts={}
-    debug "trying to acquire poll lock for: #{source}.."
-    if source.poll_lock.try_lock
-      debug "lock acquired for: #{source}."
+    debug "trying to acquire poll lock for: #{source}..."
+    if source.try_lock
       begin
         source.poll do |sym, args|
           case sym
@@ -258,7 +257,7 @@ EOS
 
       ensure
         source.go_idle
-        source.poll_lock.unlock
+        source.unlock
       end
     else
       debug "source #{source} is already being polled."
diff --git a/lib/sup/sent.rb b/lib/sup/sent.rb
@@ -27,7 +27,7 @@ class SentManager
   def write_sent_message date, from_email, &block
     ::Thread.new do
       debug "store the sent message (locking sent source..)"
-      @source.poll_lock.synchronize do
+      @source.synchronize do
         @source.store_message date, from_email, &block
       end
       PollManager.poll_from @source
diff --git a/lib/sup/source.rb b/lib/sup/source.rb
@@ -1,4 +1,5 @@
 require "sup/rfc2047"
+require "monitor"
 
 module Redwood
 
@@ -54,7 +55,7 @@ class Source
 
   bool_accessor :usual, :archived
   attr_reader :uri
-  attr_accessor :id, :poll_lock
+  attr_accessor :id
 
   def initialize uri, usual=true, archived=false, id=nil
     raise ArgumentError, "id must be an integer: #{id.inspect}" unless id.is_a? Fixnum if id
@@ -64,7 +65,7 @@ class Source
     @archived = archived
     @id = id
 
-    @poll_lock = Mutex.new
+    @poll_lock = Monitor.new
   end
 
   ## overwrite me if you have a disk incarnation (currently used only for sup-sync-back)
@@ -100,6 +101,25 @@ class Source
     true
   end
 
+  def synchronize &block
+    @poll_lock.synchronize &block
+  end
+
+  def try_lock
+    acquired = @poll_lock.try_enter
+    if acquired
+      debug "lock acquired for: #{self}"
+    else
+      debug "could not acquire lock for: #{self}"
+    end
+    acquired
+  end
+
+  def unlock
+    @poll_lock.exit
+    debug "lock released for: #{self}"
+  end
+
   ## utility method to read a raw email header from an IO stream and turn it
   ## into a hash of key-value pairs. minor special semantics for certain headers.
   ##