sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 3068986cc96d0a6f79cba15639f20405d2d82708
parent 8818a4e2e8a4bf95df1d5d7ed5689bb5a34abb48
Author: Whyme Lyu <callme5long@gmail.com>
Date:   Wed, 22 May 2013 06:25:30 -0700

Merge pull request #63 from 5long/batch-label-operation

Fix batch label operation in console
Diffstat:
M lib/sup.rb | 4 +---
M lib/sup/hook.rb | 2 ++
M lib/sup/index.rb | 10 ++++++++++
M lib/sup/logger.rb | 2 +-
A lib/sup/logger/singleton.rb | 10 ++++++++++
M lib/sup/modes/console_mode.rb | 17 ++++++++++++++---
A lib/sup/service/label_service.rb | 45 +++++++++++++++++++++++++++++++++++++++++++++
M sup.gemspec | 1 +
A test/integration/test_label_service.rb | 18 ++++++++++++++++++
M test/test_helper.rb | 5 +++++
A test/unit/service/test_label_service.rb | 19 +++++++++++++++++++
11 files changed, 126 insertions(+), 7 deletions(-)
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -309,9 +309,7 @@ require "sup/hook"
 require "sup/time"
 
 ## everything we need to get logging working
-require "sup/logger"
-Redwood::Logger.init.add_sink $stderr
-include Redwood::LogsStuff
+require "sup/logger/singleton"
 
 ## determine encoding and character set
 $encoding = Locale.current.charset
diff --git a/lib/sup/hook.rb b/lib/sup/hook.rb
@@ -1,3 +1,5 @@
+require "sup/util"
+
 module Redwood
 
 class HookManager
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
@@ -6,6 +6,11 @@ require 'fileutils'
 require 'monitor'
 require 'chronic'
 
+require "sup/interactive_lock"
+require "sup/hook"
+require "sup/logger/singleton"
+
+
 if ([Xapian.major_version, Xapian.minor_version, Xapian.revision] <=> [1,2,1]) < 0
 	fail "Xapian version 1.2.1 or higher required"
 end
@@ -254,6 +259,11 @@ EOS
     end
   end
 
+  # Search messages. Returns an Enumerator.
+  def find_messages query_expr
+    enum_for :each_message, parse_query(query_expr)
+  end
+
   # wrap all future changes inside a transaction so they're done atomically
   def begin_transaction
     synchronize { @xapian.begin_transaction }
diff --git a/lib/sup/logger.rb b/lib/sup/logger.rb
@@ -1,4 +1,4 @@
-require "sup"
+require "sup/util"
 require 'stringio'
 require 'thread'
 
diff --git a/lib/sup/logger/singleton.rb b/lib/sup/logger/singleton.rb
@@ -0,0 +1,10 @@
+# TODO: this is ugly. It's better to have a application singleton passed
+# down to lower level components instead of including logging methods in
+# class `Object'
+#
+# For now this is what we have to do.
+require "sup/logger"
+Redwood::Logger.init.add_sink $stderr
+class Object
+  include Redwood::LogsStuff
+end
diff --git a/lib/sup/modes/console_mode.rb b/lib/sup/modes/console_mode.rb
@@ -1,10 +1,13 @@
 require 'pp'
 
+require "sup/service/label_service"
+
 module Redwood
 
 class Console
   def initialize mode
     @mode = mode
+    @label_service = LabelService.new
   end
 
   def query(query)
@@ -12,19 +15,27 @@ class Console
   end
 
   def add_labels(query, *labels)
-    query(query).each { |m| m.labels += labels; m.save Index }
+    count = @label_service.add_labels(query, *labels)
+    print_buffer_dirty_msg count
   end
 
   def remove_labels(query, *labels)
-    query(query).each { |m| m.labels -= labels; m.save Index }
+    count = @label_service.remove_labels(query, *labels)
+    print_buffer_dirty_msg count
+  end
+
+  def print_buffer_dirty_msg msg_count
+    puts "Scanned #{msg_count} messages."
+    puts "You might want to refresh open buffers with `@` key."
   end
+  private :print_buffer_dirty_msg
 
   def xapian; Index.instance.instance_variable_get :@xapian; end
 
   def loglevel; Redwood::Logger.level; end
   def set_loglevel(level); Redwood::Logger.level = level; end
 
-  def special_methods; methods - Object.methods end
+  def special_methods; public_methods - Object.methods end
 
   def puts x; @mode << "#{x.to_s.rstrip}\n" end
   def p x; puts x.inspect end
diff --git a/lib/sup/service/label_service.rb b/lib/sup/service/label_service.rb
@@ -0,0 +1,45 @@
+require "sup/index"
+
+module Redwood
+  # Provides label tweaking service to the user.
+  # Working as the backend of ConsoleMode.
+  #
+  # Should become the backend of bin/sup-tweak-labels in the future.
+  class LabelService
+    # @param index [Redwood::Index]
+    def initialize index=Index.instance
+      @index = index
+    end
+
+    def add_labels query, *labels
+      run_on_each_message(query) do |m|
+        labels.each {|l| m.add_label l }
+      end
+    end
+
+    def remove_labels query, *labels
+      run_on_each_message(query) do |m|
+        labels.each {|l| m.remove_label l }
+      end
+    end
+
+
+    private
+    def run_on_each_message query, &operation
+      count = 0
+
+      find_messages(query).each do |m|
+        operation.call(m)
+        @index.update_message_state m
+        count += 1
+      end
+
+      @index.save_index
+      count
+    end
+
+    def find_messages query
+      @index.find_messages(query)
+    end
+  end
+end
diff --git a/sup.gemspec b/sup.gemspec
@@ -49,5 +49,6 @@ DESC
     s.add_development_dependency "bundler", "~> 1.3"
     s.add_development_dependency "rake"
     s.add_development_dependency "minitest", "~> 4"
+    s.add_development_dependency "rr", "~> 1.0"
   end
 end
diff --git a/test/integration/test_label_service.rb b/test/integration/test_label_service.rb
@@ -0,0 +1,18 @@
+require "test_helper"
+
+require "sup/service/label_service"
+
+require "tmpdir"
+
+describe Redwood::LabelService do
+  let(:tmpdir) { Dir.mktmpdir }
+  after do
+    require "fileutils"
+    FileUtils.remove_entry_secure @tmpdir unless @tmpdir.nil?
+  end
+
+  describe "#add_labels" do
+    # Integration tests are hard to write at this moment :(
+    it "add labels to all messages matching the query"
+  end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
@@ -1 +1,6 @@
 require 'minitest/autorun'
+require "rr"
+
+class Minitest::Unit::TestCase
+  include ::RR::Adapters::MiniTest
+end
diff --git a/test/unit/service/test_label_service.rb b/test/unit/service/test_label_service.rb
@@ -0,0 +1,19 @@
+require "test_helper"
+
+require "sup/service/label_service"
+
+describe Redwood::LabelService do
+  describe "#add_labels" do
+    it "add labels to all messages matching the query" do
+      q = 'is:starred'
+      label = 'superstarred'
+      message = mock!.add_label(label).subject
+      index = mock!.find_messages(q){ [message] }.subject
+      mock(index).update_message_state(message)
+      mock(index).save_index
+
+      service = Redwood::LabelService.new(index)
+      service.add_labels q, label
+    end
+  end
+end