From: wmorgan Date: Sun, 3 Jun 2007 22:00:24 +0000 (+0000) Subject: labels now fully determined by sources.yaml, and lots of improvements to sup-config X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=df62e1a6e52f6b5c0dd542e79307aa7cdcc0a88d;p=sup labels now fully determined by sources.yaml, and lots of improvements to sup-config git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@427 5c8cc53c-5e98-4d25-b20a-d8db53a31250 --- diff --git a/bin/sup-add b/bin/sup-add index 97a579b..f0def12 100644 --- a/bin/sup-add +++ b/bin/sup-add @@ -37,6 +37,7 @@ Options are: EOS opt :archive, "Automatically archive all new messages from these sources." opt :unusual, "Do not automatically poll these sources for new messages." + opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String opt :force_new, "Create a new account for this source, even if one already exists." end @@ -80,6 +81,8 @@ index = Redwood::Index.new index.load ARGV.each do |uri| + labels = $opts[:labels] ? $opts[:labels].split(/\s*,\s*/).uniq : [] + if !$opts[:force_new] && index.source_for(uri) say "Already know about #{uri}; skipping." next @@ -94,14 +97,14 @@ ARGV.each do |uri| say "For SSH connections, if you will use public key authentication, you may leave the username and password blank." say "" username, password = get_login_info uri, index.sources - Redwood::MBox::SSHLoader.new uri, username, password, nil, !$opts[:unusual], $opts[:archive] + Redwood::MBox::SSHLoader.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, labels when "imap", "imaps" username, password = get_login_info uri, index.sources - Redwood::IMAP.new uri, username, password, nil, !$opts[:unusual], $opts[:archive] + Redwood::IMAP.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, labels when "maildir" - Redwood::Maildir.new uri, nil, !$opts[:unusual], $opts[:archive] + Redwood::Maildir.new uri, nil, !$opts[:unusual], $opts[:archive], nil, labels when "mbox" - Redwood::MBox::Loader.new uri, nil, !$opts[:unusual], $opts[:archive] + Redwood::MBox::Loader.new uri, nil, !$opts[:unusual], $opts[:archive], nil, labels else Trollop::die "Unknown source type #{parsed_uri.scheme.inspect}" end diff --git a/bin/sup-config b/bin/sup-config index 5b75b2b..1cd244a 100644 --- a/bin/sup-config +++ b/bin/sup-config @@ -21,11 +21,11 @@ end #' stupid ruby-mode def axe q, default=nil ans = - if default && !default.empty? - ask "#{q} (enter for \"#{default}\"): " - else - ask "#{q}: " - end + if default && !default.empty? + ask "#{q} (enter for \"#{default}\"): " + else + ask "#{q}: " + end ans.empty? ? default : ans end @@ -42,7 +42,7 @@ def add_source say "Ok, adding a new source." choose do |menu| - menu.prompt = "What type of mail source is it?" + menu.prompt = "What type of mail source is it? " menu.choice("mbox file") { type = :mbox } menu.choice("maildir directory") { type = :maildir } menu.choice("remote mbox file (accessible via ssh)") { type = :mboxssh } @@ -52,40 +52,57 @@ def add_source end while true do - say "Now for the details." + say "Ok, now for the details." - components = + default_labels, components = case type when :mbox - fn = axe "What's the full path to the mbox file?", ENV["MAIL"] #"srm + $last_fn ||= ENV["MAIL"] + fn = axe "What's the full path to the mbox file?", $last_fn #"srm return if fn.nil? || fn.empty? - { :scheme => "mbox", :path => fn } + + $last_fn = fn + [Redwood::MBox::Loader.suggest_labels_for(fn), + { :scheme => "mbox", :path => fn }] when :maildir - fn = axe "What's the full path to the maildir directory?", ENV["MAIL"] #"srm + $last_fn ||= ENV["MAIL"] + fn = axe "What's the full path to the maildir directory?", $last_fn #"srm return if fn.nil? || fn.empty? - { :scheme => "maildir", :path => fn } + + $last_fn = fn + [Redwood::Maildir.suggest_labels_for(fn), + { :scheme => "maildir", :path => fn }] when :mboxssh + $last_server ||= "localhost" srv = axe "What machine is the mbox file located on?", $last_server return if srv.nil? || srv.empty? $last_server = srv - fn = axe "What's the full path to the mbox file?", ENV["MAIL"] #"srm + + fn = axe "What's the full path to the mbox file?", $last_fn #" stupid ruby-mode return if fn.nil? || fn.empty? + $last_fn = fn fn = "/#{fn}" # lame - { :scheme => "mbox+ssh", :host => srv, :path => fn } + [Redwood::MBox::SSHLoader.suggest_labels_for(fn), + { :scheme => "mbox+ssh", :host => srv, :path => fn }] when :imap, :imaps + $last_server ||= "localhost" srv = axe "What is the IMAP server (host, or host:port notation)?", $last_server return if srv.nil? || srv.empty? $last_server = srv - fn = axe "What's the folder path?", "INBOX" #"srm + + $last_folder ||= "INBOX" + fn = axe "What's the folder path?", $last_folder #"srm return if fn.nil? || fn.empty? - fn = "/#{fn}" # lame + $last_folder = fn + fn = "/#{fn}" # lame if srv =~ /^(\w+):(\d+)$/ host, port = $1, $2.to_i else host, port = srv, nil end - { :scheme => type.to_s, :host => host, :port => port, :path => fn } + [Redwood::IMAP.suggest_labels_for(fn), + { :scheme => type.to_s, :host => host, :port => port, :path => fn }] end uri = @@ -96,17 +113,27 @@ def add_source if axe_yes("Try again?") then next else return end end - say "I'm going to add this source: #{uri}." + say "I'm going to add this source: #{uri}" unless axe("Does that look right?", "y") =~ /^y|yes$/i if axe_yes("Try again?") then next else return end end usual = axe_yes "Does this source ever receive new messages?", "y" - archive = usual ? axe_yes("Should those new messages be automatically archived?") : false + archive = usual ? axe_yes("Should new messages be automatically archived? (I.e. not appear in your inbox, though still be accessible via search.)") : false + + labels_str = axe("Enter any labels to be automatically added to all messages from this source, separated by spaces (or 'none')", default_labels.join(",")) + + labels = + if labels_str =~ /^\s*none\s*$/i + nil + else + labels_str.split(/\s+/) + end cmd = build_cmd "sup-add" cmd += " --unusual" unless usual cmd += " --archive" if archive + cmd += " --labels=#{labels.join(',')}" if labels cmd += " #{uri}" puts "Ok, trying to run \"#{cmd}\"..." @@ -134,8 +161,7 @@ program. Get ready to be the envy of everyone in your internets with your amazing keyboarding skills! Jump from email to email with nary a click of the mouse! -Just answer these simple questions and you'll be on your way! Press -enter at any point to accept the default answer. +Just answer these simple questions and you'll be on your way. EOS #' stupid ruby-mode @@ -143,7 +169,7 @@ EOS account = $config[:accounts][:default] name = axe "What's your name?", account[:name] -email = axe "What's your email address?", account[:email] #'srm +email = axe "What's your (primary) email address?", account[:email] #'srm say "Ok, your header will look like this:" say " From: #{name} <#{email}>" @@ -178,7 +204,7 @@ until done say "\n" choose do |menu| - menu.prompt = "Your wish?" + menu.prompt = "Your wish? " menu.choice("Add a new source.") { add_source } menu.choice("Done adding sources!") { done = true } end @@ -190,12 +216,6 @@ Ok. The final step is to import all your messages into the Sup index. Depending on how many messages are in the sources, this could take quite a while. -IMPORTANT NOTE: this import will archive messages if the source is -marked archival, and won't otherwise. It will preserve read/unread -status as given by the source, and it will automatically add one label -per source. All of this behavior can be controlled on per-source -basis by running sup-sync manually. - EOS #' if axe_yes "Run sup-sync to import all messages now?" @@ -222,7 +242,7 @@ like you're ready to jack in to cyberspace there, cowboy. Just one last command: - sup + #{build_cmd "sup"} Have fun! EOS diff --git a/bin/sup-sync b/bin/sup-sync index c7e826b..6c28a76 100644 --- a/bin/sup-sync +++ b/bin/sup-sync @@ -176,7 +176,7 @@ begin elapsed = last_info_time - start_time pctdone = source.respond_to?(:pct_done) ? source.pct_done : 100.0 * (source.cur_offset.to_f - source.start_offset).to_f / (source.end_offset - source.start_offset).to_f remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone) - $stderr.puts "## #{num_added + num_updated} (#{pctdone}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining (for this source)" + $stderr.puts "## #{num_added + num_updated} (#{pctdone}%) read; #{elapsed.to_time_s} elapsed; #{remaining.to_time_s} remaining" end if index_state.nil? diff --git a/doc/TODO b/doc/TODO index 72ff675..1ca6d43 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,18 +1,20 @@ for 0.0.9 --------- -_ add arbitrary labels to sources +_ bugfix: when one new message comes into an imap folder, we don't + catch it until a reload (sometimes?) + message indicating they're loaded to inbox (imap only?) +_ rss feed reading _ detect other sup instances and do something intelligent (because ferret crashes violently with more than one index writer open) _ bugfix: need a way to force an address to a particular name, for things like evite addresses _ bugfix: read before thread-index has finished loading then hides the thread?!? wtf. (on jamie) _ bugfix: ferret flakiness: just added message but can't find it. -_ bugfix: when one new message comes into an imap folder, we don't - catch it until a reload (sometimes?) _ bugfix: add new message counts until keypress -_ bugfix: deadlock -_ split out threading & message chunk parsing to a separate library +_ bugfix: deadlock (on rubyforge) _ decode RFC 2047 ("encoded word") headers - see: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/101949, http://dev.rubyonrails.org/ticket/6807 _ refactor all the *-search-results-mode classes into one. +x add arbitrary labels to sources +x improve sup-config x autoload more threads when you go down x add a sync-back tool that at least works for mboxes x thread by subject configurable in config.yaml @@ -55,6 +57,10 @@ toggle wrapping maybe: de-archived messages auto-added to inbox prune old entries from contacts.txt so that it doesn't arbitrarily +maybe +_ split out threading & message chunk parsing to a separate library + + done ---- x nice little startup config program diff --git a/lib/sup.rb b/lib/sup.rb index 45f43c0..5311a44 100644 --- a/lib/sup.rb +++ b/lib/sup.rb @@ -153,11 +153,21 @@ end 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 + email = ENV["USER"] + "@" + + begin + Socket.gethostbyname(Socket.gethostname).first + rescue SocketError + Socket.gethostname + end + $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") diff --git a/lib/sup/imap.rb b/lib/sup/imap.rb index 7370b91..c1ba2bf 100644 --- a/lib/sup/imap.rb +++ b/lib/sup/imap.rb @@ -50,9 +50,9 @@ class IMAP < Source attr_accessor :username, :password yaml_properties :uri, :username, :password, :cur_offset, :usual, - :archived, :id + :archived, :id, :labels - def initialize uri, username, password, last_idate=nil, usual=true, archived=false, id=nil + def initialize uri, username, password, last_idate=nil, usual=true, archived=false, id=nil, labels=[] raise ArgumentError, "username and password must be specified" unless username && password raise ArgumentError, "not an imap uri" unless uri =~ %r!imaps?://! @@ -65,11 +65,19 @@ class IMAP < Source @imap_ids = {} @ids = [] @last_scan = nil - @labels = [:unread] - @labels << mailbox.intern unless mailbox =~ /inbox/i + @labels = (labels || []).freeze + @say_id = nil @mutex = Mutex.new end + def self.suggest_labels_for path + if path =~ /inbox/i + [path.intern] + else + [] + end + end + def host; @parsed_uri.host; end def port; @parsed_uri.port || (ssl? ? 993 : 143); end def mailbox @@ -102,6 +110,7 @@ class IMAP < Source def raw_header id unsynchronized_scan_mailbox header, flags = get_imap_fields id, 'RFC822.HEADER', 'FLAGS' + ## very bad. this is very very bad. very bad bad bad. header = header + "Status: RO\n" if flags.include? :Seen # fake an mbox-style read header # TODO: improve source-marked-as-read reporting system header.gsub(/\r\n/, "\n") end @@ -151,7 +160,7 @@ class IMAP < Source start.upto(ids.length - 1) do |i| id = ids[i] self.cur_offset = id - yield id, @labels.clone + yield id, @labels end end diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb index c17e8f3..c439fca 100644 --- a/lib/sup/maildir.rb +++ b/lib/sup/maildir.rb @@ -11,21 +11,24 @@ module Redwood class Maildir < Source SCAN_INTERVAL = 30 # seconds - yaml_properties :uri, :cur_offset, :usual, :archived, :id - def initialize uri, last_date=nil, usual=true, archived=false, id=nil - super + yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels + def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[] + super uri, last_date, usual, archived, id uri = URI(uri) raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir" raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host @dir = uri.path + @labels = (labels || []).freeze @ids = [] @ids_to_fns = {} @last_scan = nil @mutex = Mutex.new end + def self.suggest_labels_for path; [] end + def check scan_mailbox start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email @@ -90,7 +93,7 @@ class Maildir < Source start.upto(@ids.length - 1) do |i| id = @ids[i] self.cur_offset = id - yield id, (@ids_to_fns[id] =~ /,.*R.*$/ ? [] : [:unread]) + yield id, @labels + (@ids_to_fns[id] =~ /,.*R.*$/ ? [] : [:unread]) end end diff --git a/lib/sup/mbox/loader.rb b/lib/sup/mbox/loader.rb index 05ddbab..49e9703 100644 --- a/lib/sup/mbox/loader.rb +++ b/lib/sup/mbox/loader.rb @@ -5,27 +5,34 @@ module Redwood module MBox class Loader < Source - yaml_properties :uri, :cur_offset, :usual, :archived, :id - def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil - super + yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels + def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, labels=[] + super uri_or_fp, start_offset, usual, archived, id @mutex = Mutex.new - @labels = [:unread] + @labels = (labels || []).freeze case uri_or_fp when String uri = URI(uri_or_fp) raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox" raise ArgumentError, "mbox uri ('#{uri}') cannot have a host: #{uri.host}" if uri.host - ## heuristic: use the filename as a label, unless the file - ## has a path that probably represents an inbox. - @labels << File.basename(uri.path).intern unless File.dirname(uri.path) =~ /\b(var|usr|spool)\b/ @f = File.open uri.path else @f = uri_or_fp end end + def self.suggest_labels_for path + ## heuristic: use the filename as a label, unless the file + ## has a path that probably represents an inbox. + if File.dirname(path) =~ /\b(var|usr|spool)\b/ + [] + else + [File.basename(path).intern] + end + end + def check if (cur_offset ||= start_offset) > end_offset raise OutOfSyncSourceError, "mbox file is smaller than last recorded message offset. Messages have probably been deleted by another client." @@ -128,7 +135,7 @@ class Loader < Source end self.cur_offset = next_offset - [returned_offset, @labels.clone] + [returned_offset, @labels] end end diff --git a/lib/sup/mbox/ssh-loader.rb b/lib/sup/mbox/ssh-loader.rb index 42a2b98..bea585e 100644 --- a/lib/sup/mbox/ssh-loader.rb +++ b/lib/sup/mbox/ssh-loader.rb @@ -7,9 +7,9 @@ class SSHLoader < Source attr_accessor :username, :password yaml_properties :uri, :username, :password, :cur_offset, :usual, - :archived, :id + :archived, :id, :labels - def initialize uri, username=nil, password=nil, start_offset=nil, usual=true, archived=false, id=nil + def initialize uri, username=nil, password=nil, start_offset=nil, usual=true, archived=false, id=nil, labels=[] raise ArgumentError, "not an mbox+ssh uri: #{uri.inspect}" unless uri =~ %r!^mbox\+ssh://! super uri, start_offset, usual, archived, id @@ -19,6 +19,7 @@ class SSHLoader < Source @password = password @uri = uri @cur_offset = start_offset + @labels = (labels || []).freeze opts = {} opts[:username] = @username if @username @@ -29,10 +30,10 @@ class SSHLoader < Source ## heuristic: use the filename as a label, unless the file ## has a path that probably represents an inbox. - @labels = [:unread] - @labels << File.basename(filename).intern unless File.dirname(filename) =~ /\b(var|usr|spool)\b/ end + def self.suggest_labels_for path; Loader.suggest_labels_for(path) end + def connect; safely { @f.connect }; end def host; @parsed_uri.host; end def filename; @parsed_uri.path[1..-1] end diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb index 690caa2..37e8c2e 100644 --- a/lib/sup/poll.rb +++ b/lib/sup/poll.rb @@ -106,6 +106,9 @@ class PollManager if m.source_marked_read? m.remove_label :unread labels.delete :unread + else + m.add_label :unread + labels << :unread end docid, entry = Index.load_entry_for_id m.id diff --git a/lib/sup/source.rb b/lib/sup/source.rb index 00f2d07..bcdc6e4 100644 --- a/lib/sup/source.rb +++ b/lib/sup/source.rb @@ -61,6 +61,8 @@ class Source attr_accessor :id def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil + raise ArgumentError, "id must be an integer: #{id.inspect}" unless id.is_a? Fixnum if id + @uri = uri @cur_offset = initial_offset @usual = usual