]> git.cworth.org Git - sup/blobdiff - lib/sup.rb
Merge branch 'various-api-refactors'
[sup] / lib / sup.rb
index 11567f7a4e4ed662b66b6f4d6f7c9b81a05dcbd7..43daa7e7be19b0ba12c83bec1d67c5f50d0b6d94 100644 (file)
@@ -3,6 +3,12 @@ require 'yaml'
 require 'zlib'
 require 'thread'
 require 'fileutils'
 require 'zlib'
 require 'thread'
 require 'fileutils'
+require 'gettext'
+require 'curses'
+begin
+  require 'fastthread'
+rescue LoadError
+end
 
 class Object
   ## this is for debugging purposes because i keep calling #id on the
 
 class Object
   ## this is for debugging purposes because i keep calling #id on the
@@ -12,24 +18,57 @@ class Object
   end
 end
 
   end
 end
 
+class Module
+  def yaml_properties *props
+    props = props.map { |p| p.to_s }
+    vars = props.map { |p| "@#{p}" }
+    klass = self
+    path = klass.name.gsub(/::/, "/")
+
+    klass.instance_eval do
+      define_method(:to_yaml_properties) { vars }
+      define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
+    end
+
+    YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
+      klass.new(*props.map { |p| val[p] })
+    end
+  end
+end
+
 module Redwood
 module Redwood
-  VERSION = "0.0.8"
+  VERSION = "git"
 
   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
 
   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
+  COLOR_FN   = File.join(BASE_DIR, "colors.yaml")
   SOURCE_FN  = File.join(BASE_DIR, "sources.yaml")
   LABEL_FN   = File.join(BASE_DIR, "labels.txt")
   SOURCE_FN  = File.join(BASE_DIR, "sources.yaml")
   LABEL_FN   = File.join(BASE_DIR, "labels.txt")
-  PERSON_FN  = File.join(BASE_DIR, "people.txt")
   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
+  LOCK_FN    = File.join(BASE_DIR, "lock")
+  SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
+  HOOK_DIR   = File.join(BASE_DIR, "hooks")
 
   YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
 
 
   YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
 
-## record exceptions thrown in threads nicely
-  $exception = nil
-  def reporting_thread
+  DEFAULT_INDEX = 'ferret'
+
+  ## record exceptions thrown in threads nicely
+  @exceptions = []
+  @exception_mutex = Mutex.new
+
+  attr_reader :exceptions
+  def record_exception e, name
+    @exception_mutex.synchronize do
+      @exceptions ||= []
+      @exceptions << [e, name]
+    end
+  end
+
+  def reporting_thread name
     if $opts[:no_threads]
       yield
     else
     if $opts[:no_threads]
       yield
     else
@@ -37,76 +76,79 @@ module Redwood
         begin
           yield
         rescue Exception => e
         begin
           yield
         rescue Exception => e
-          File.open("sup-exception-log.txt", "w") do |f|
-            f.puts "--- #{e.class.name} at #{Time.now}"
-            f.puts e.message, e.backtrace
-          end
-          $exception ||= e
-          raise
+          record_exception e, name
         end
       end
     end
   end
         end
       end
     end
   end
-  module_function :reporting_thread
 
 
-## one-stop shop for yamliciousness
-  def register_yaml klass, props
-    vars = props.map { |p| "@#{p}" }
-    path = klass.name.gsub(/::/, "/")
-    
-    klass.instance_eval do
-      define_method(:to_yaml_properties) { vars }
-      define_method(:to_yaml_type) { "!#{YAML_DOMAIN},#{YAML_DATE}/#{path}" }
-    end
+  module_function :reporting_thread, :record_exception, :exceptions
 
 
-    YAML.add_domain_type("#{YAML_DOMAIN},#{YAML_DATE}", path) do |type, val|
-      klass.new(*props.map { |p| val[p] })
+## one-stop shop for yamliciousness
+  def save_yaml_obj o, fn, safe=false
+    o = if o.is_a?(Array)
+      o.map { |x| (x.respond_to?(:before_marshal) && x.before_marshal) || x }
+    else
+      o.respond_to?(:before_marshal) && o.before_marshal
     end
     end
-  end
 
 
-  def save_yaml_obj object, fn, compress=false
-    if compress
-      Zlib::GzipWriter.open(fn) { |f| f.puts object.to_yaml }
+    if safe
+      safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
+      mode = File.stat(fn).mode if File.exists? fn
+      File.open(safe_fn, "w", mode) { |f| f.puts o.to_yaml }
+      FileUtils.mv safe_fn, fn
     else
     else
-      File.open(fn, "w") { |f| f.puts object.to_yaml }
+      File.open(fn, "w") { |f| f.puts o.to_yaml }
     end
   end
 
   def load_yaml_obj fn, compress=false
     end
   end
 
   def load_yaml_obj fn, compress=false
-    if File.exists? fn
+    o = if File.exists? fn
       if compress
         Zlib::GzipReader.open(fn) { |f| YAML::load f }
       else
         YAML::load_file fn
       end
     end
       if compress
         Zlib::GzipReader.open(fn) { |f| YAML::load f }
       else
         YAML::load_file fn
       end
     end
+    if o.is_a?(Array)
+      o.each { |x| x.after_unmarshal! if x.respond_to?(:after_unmarshal!) }
+    else
+      o.after_unmarshal! if o.respond_to?(:after_unmarshal!)
+    end
+    o
   end
 
   def start
   end
 
   def start
-    Redwood::PersonManager.new Redwood::PERSON_FN
-    Redwood::SentManager.new Redwood::SENT_FN
-    Redwood::ContactManager.new Redwood::CONTACT_FN
-    Redwood::LabelManager.new Redwood::LABEL_FN
-    Redwood::AccountManager.new $config[:accounts]
-    Redwood::DraftManager.new Redwood::DRAFT_DIR
-    Redwood::UpdateManager.new
-    Redwood::PollManager.new
+    Redwood::SentManager.init $config[:sent_source] || 'sup://sent'
+    Redwood::ContactManager.init Redwood::CONTACT_FN
+    Redwood::LabelManager.init Redwood::LABEL_FN
+    Redwood::AccountManager.init $config[:accounts]
+    Redwood::DraftManager.init Redwood::DRAFT_DIR
+    Redwood::UpdateManager.init
+    Redwood::PollManager.init
+    Redwood::SuicideManager.init Redwood::SUICIDE_FN
+    Redwood::CryptoManager.init
+    Redwood::UndoManager.init
+    Redwood::SourceManager.init
   end
 
   def finish
   end
 
   def finish
-    Redwood::LabelManager.save
-    Redwood::ContactManager.save
-    Redwood::PersonManager.save
-    Redwood::BufferManager.deinstantiate!
+    Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
+    Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
+    Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
   end
 
   ## not really a good place for this, so I'll just dump it here.
   end
 
   ## not really a good place for this, so I'll just dump it here.
+  ##
+  ## a source error is either a FatalSourceError or an OutOfSyncSourceError.
+  ## the superclass SourceError is just a generic.
   def report_broken_sources opts={}
     return unless BufferManager.instantiated?
 
   def report_broken_sources opts={}
     return unless BufferManager.instantiated?
 
-    broken_sources = Index.usual_sources.select { |s| s.error.is_a? FatalSourceError }
+    broken_sources = SourceManager.sources.select { |s| s.error.is_a? FatalSourceError }
     unless broken_sources.empty?
     unless broken_sources.empty?
-      BufferManager.spawn "Broken source notification", TextMode.new(<<EOM), opts
+      BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
+        TextMode.new(<<EOM)
 Source error notification
 -------------------------
 
 Source error notification
 -------------------------
 
@@ -114,14 +156,16 @@ Hi there. It looks like one or more message sources is reporting
 errors. Until this is corrected, messages from these sources cannot
 be viewed, and new messages will not be detected.
 
 errors. Until this is corrected, messages from these sources cannot
 be viewed, and new messages will not be detected.
 
-#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join('\n\n')}
+#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join("\n\n")}
 EOM
 #' stupid ruby-mode
 EOM
 #' stupid ruby-mode
+      end
     end
 
     end
 
-    desynced_sources = Index.usual_sources.select { |s| s.error.is_a? OutOfSyncSourceError }
+    desynced_sources = SourceManager.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
     unless desynced_sources.empty?
     unless desynced_sources.empty?
-      BufferManager.spawn "Out-of-sync source notification", TextMode.new(<<EOM), opts
+      BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
+        TextMode.new(<<EOM)
 Out-of-sync source notification
 -------------------------------
 
 Out-of-sync source notification
 -------------------------------
 
@@ -139,28 +183,50 @@ and new messages will not be detected. Luckily, this is easy to correct!
   end}
 EOM
 #' stupid ruby-mode
   end}
 EOM
 #' stupid ruby-mode
+      end
     end
   end
 
     end
   end
 
-  module_function :register_yaml, :save_yaml_obj, :load_yaml_obj, :start, :finish, :report_broken_sources
+  module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
+                  :report_broken_sources
 end
 
 ## set up default configuration file
 if File.exists? Redwood::CONFIG_FN
   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
 else
 end
 
 ## set up default configuration file
 if File.exists? Redwood::CONFIG_FN
   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
 else
+  require 'etc'
+  require 'socket'
+  name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first rescue nil
+  name ||= ENV["USER"]
+  email = ENV["USER"] + "@" + 
+    begin
+      Socket.gethostbyname(Socket.gethostname).first
+    rescue SocketError
+      Socket.gethostname
+    end
+
   $config = {
     :accounts => {
       :default => {
   $config = {
     :accounts => {
       :default => {
-        :name => "Sup Rocks",
-        :email => "sup-rocks@reading-my-emails",
+        :name => name,
+        :email => email,
         :alternates => [],
         :sendmail => "/usr/sbin/sendmail -oem -ti",
         :signature => File.join(ENV["HOME"], ".signature")
       }
     },
         :alternates => [],
         :sendmail => "/usr/sbin/sendmail -oem -ti",
         :signature => File.join(ENV["HOME"], ".signature")
       }
     },
-    :editor => ENV["EDITOR"] || "/usr/bin/vi",
+    :editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
     :thread_by_subject => false,
     :thread_by_subject => false,
+    :edit_signature => false,
+    :ask_for_cc => true,
+    :ask_for_bcc => false,
+    :ask_for_subject => true,
+    :confirm_no_attachments => true,
+    :confirm_top_posting => true,
+    :discard_snippets_from_encrypted_messages => false,
+    :default_attachment_save_dir => "",
+    :sent_source => "sup://sent"
   }
   begin
     FileUtils.mkdir_p Redwood::BASE_DIR
   }
   begin
     FileUtils.mkdir_p Redwood::BASE_DIR
@@ -171,7 +237,36 @@ else
 end
 
 require "sup/util"
 end
 
 require "sup/util"
+require "sup/hook"
+
+## we have to initialize this guy first, because other classes must
+## reference it in order to register hooks, and they do that at parse
+## time.
+Redwood::HookManager.init Redwood::HOOK_DIR
+
+## everything we need to get logging working
+require "sup/logger"
+Redwood::Logger.init.add_sink $stderr
+include Redwood::LogsStuff
+
+## determine encoding and character set
+  $encoding = Locale.current.charset
+  if $encoding
+    debug "using character set encoding #{$encoding.inspect}"
+  else
+    warn "can't find character set by using locale, defaulting to utf-8"
+    $encoding = "UTF-8"
+  end
+
+require "sup/buffer"
+require "sup/keymap"
+require "sup/mode"
+require "sup/modes/scroll-mode"
+require "sup/modes/text-mode"
+require "sup/modes/log-mode"
 require "sup/update"
 require "sup/update"
+require "sup/suicide"
+require "sup/message-chunks"
 require "sup/message"
 require "sup/source"
 require "sup/mbox"
 require "sup/message"
 require "sup/source"
 require "sup/mbox"
@@ -182,17 +277,15 @@ require "sup/account"
 require "sup/thread"
 require "sup/index"
 require "sup/textfield"
 require "sup/thread"
 require "sup/index"
 require "sup/textfield"
-require "sup/buffer"
-require "sup/keymap"
-require "sup/mode"
 require "sup/colormap"
 require "sup/label"
 require "sup/contact"
 require "sup/tagger"
 require "sup/draft"
 require "sup/poll"
 require "sup/colormap"
 require "sup/label"
 require "sup/contact"
 require "sup/tagger"
 require "sup/draft"
 require "sup/poll"
-require "sup/modes/scroll-mode"
-require "sup/modes/text-mode"
+require "sup/crypto"
+require "sup/undo"
+require "sup/horizontal-selector"
 require "sup/modes/line-cursor-mode"
 require "sup/modes/help-mode"
 require "sup/modes/edit-message-mode"
 require "sup/modes/line-cursor-mode"
 require "sup/modes/help-mode"
 require "sup/modes/edit-message-mode"
@@ -209,16 +302,11 @@ require "sup/modes/search-results-mode"
 require "sup/modes/person-search-results-mode"
 require "sup/modes/inbox-mode"
 require "sup/modes/buffer-list-mode"
 require "sup/modes/person-search-results-mode"
 require "sup/modes/inbox-mode"
 require "sup/modes/buffer-list-mode"
-require "sup/modes/log-mode"
 require "sup/modes/poll-mode"
 require "sup/modes/poll-mode"
-require "sup/logger"
+require "sup/modes/file-browser-mode"
+require "sup/modes/completion-mode"
 require "sup/sent"
 
 require "sup/sent"
 
-module Redwood
-  def log s; Logger.log s; end
-  module_function :log
-end
-
 $:.each do |base|
   d = File.join base, "sup/share/modes/"
   Redwood::Mode.load_all_modes d if File.directory? d
 $:.each do |base|
   d = File.join base, "sup/share/modes/"
   Redwood::Mode.load_all_modes d if File.directory? d