]> git.cworth.org Git - sup/blob - lib/sup.rb
Merge branch 'console-mode' into next
[sup] / lib / sup.rb
1 require 'rubygems'
2 require 'yaml'
3 require 'zlib'
4 require 'thread'
5 require 'fileutils'
6 require 'gettext'
7 require 'curses'
8 begin
9   require 'fastthread'
10 rescue LoadError
11 end
12
13 class Object
14   ## this is for debugging purposes because i keep calling #id on the
15   ## wrong object and i want it to throw an exception
16   def id
17     raise "wrong id called on #{self.inspect}"
18   end
19 end
20
21 class Module
22   def yaml_properties *props
23     props = props.map { |p| p.to_s }
24     vars = props.map { |p| "@#{p}" }
25     klass = self
26     path = klass.name.gsub(/::/, "/")
27
28     klass.instance_eval do
29       define_method(:to_yaml_properties) { vars }
30       define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
31     end
32
33     YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
34       klass.new(*props.map { |p| val[p] })
35     end
36   end
37 end
38
39 module Redwood
40   VERSION = "git"
41
42   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
43   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
44   COLOR_FN   = File.join(BASE_DIR, "colors.yaml")
45   SOURCE_FN  = File.join(BASE_DIR, "sources.yaml")
46   LABEL_FN   = File.join(BASE_DIR, "labels.txt")
47   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
48   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
49   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
50   LOCK_FN    = File.join(BASE_DIR, "lock")
51   SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
52   HOOK_DIR   = File.join(BASE_DIR, "hooks")
53
54   YAML_DOMAIN = "masanjin.net"
55   YAML_DATE = "2006-10-01"
56
57   DEFAULT_INDEX = 'ferret'
58
59   ## record exceptions thrown in threads nicely
60   @exceptions = []
61   @exception_mutex = Mutex.new
62
63   attr_reader :exceptions
64   def record_exception e, name
65     @exception_mutex.synchronize do
66       @exceptions ||= []
67       @exceptions << [e, name]
68     end
69   end
70
71   def reporting_thread name
72     if $opts[:no_threads]
73       yield
74     else
75       ::Thread.new do
76         begin
77           yield
78         rescue Exception => e
79           record_exception e, name
80         end
81       end
82     end
83   end
84
85   module_function :reporting_thread, :record_exception, :exceptions
86
87 ## one-stop shop for yamliciousness
88   def save_yaml_obj object, fn, safe=false
89     if safe
90       safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
91       mode = File.stat(fn).mode if File.exists? fn
92       File.open(safe_fn, "w", mode) { |f| f.puts object.to_yaml }
93       FileUtils.mv safe_fn, fn
94     else
95       File.open(fn, "w") { |f| f.puts object.to_yaml }
96     end
97   end
98
99   def load_yaml_obj fn, compress=false
100     if File.exists? fn
101       if compress
102         Zlib::GzipReader.open(fn) { |f| YAML::load f }
103       else
104         YAML::load_file fn
105       end
106     end
107   end
108
109   def start
110     Redwood::SentManager.init $config[:sent_source] || 'sup://sent'
111     Redwood::ContactManager.init Redwood::CONTACT_FN
112     Redwood::LabelManager.init Redwood::LABEL_FN
113     Redwood::AccountManager.init $config[:accounts]
114     Redwood::DraftManager.init Redwood::DRAFT_DIR
115     Redwood::UpdateManager.init
116     Redwood::PollManager.init
117     Redwood::CryptoManager.init
118     Redwood::UndoManager.init
119     Redwood::SourceManager.init
120   end
121
122   def finish
123     Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
124     Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
125     Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
126   end
127
128   ## not really a good place for this, so I'll just dump it here.
129   ##
130   ## a source error is either a FatalSourceError or an OutOfSyncSourceError.
131   ## the superclass SourceError is just a generic.
132   def report_broken_sources opts={}
133     return unless BufferManager.instantiated?
134
135     broken_sources = SourceManager.sources.select { |s| s.error.is_a? FatalSourceError }
136     unless broken_sources.empty?
137       BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
138         TextMode.new(<<EOM)
139 Source error notification
140 -------------------------
141
142 Hi there. It looks like one or more message sources is reporting
143 errors. Until this is corrected, messages from these sources cannot
144 be viewed, and new messages will not be detected.
145
146 #{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join("\n\n")}
147 EOM
148 #' stupid ruby-mode
149       end
150     end
151
152     desynced_sources = SourceManager.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
153     unless desynced_sources.empty?
154       BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
155         TextMode.new(<<EOM)
156 Out-of-sync source notification
157 -------------------------------
158
159 Hi there. It looks like one or more sources has fallen out of sync
160 with my index. This can happen when you modify these sources with
161 other email clients. (Sorry, I don't play well with others.)
162
163 Until this is corrected, messages from these sources cannot be viewed,
164 and new messages will not be detected. Luckily, this is easy to correct!
165
166 #{desynced_sources.map do |s|
167   "Source: " + s.to_s + 
168    "\n Error: " + s.error.message.wrap(70).join("\n        ") + 
169    "\n   Fix: sup-sync --changed #{s.to_s}"
170   end}
171 EOM
172 #' stupid ruby-mode
173       end
174     end
175   end
176
177   module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
178                   :report_broken_sources
179 end
180
181 ## set up default configuration file
182 if File.exists? Redwood::CONFIG_FN
183   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
184 else
185   require 'etc'
186   require 'socket'
187   name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first rescue nil
188   name ||= ENV["USER"]
189   email = ENV["USER"] + "@" + 
190     begin
191       Socket.gethostbyname(Socket.gethostname).first
192     rescue SocketError
193       Socket.gethostname
194     end
195
196   $config = {
197     :accounts => {
198       :default => {
199         :name => name,
200         :email => email,
201         :alternates => [],
202         :sendmail => "/usr/sbin/sendmail -oem -ti",
203         :signature => File.join(ENV["HOME"], ".signature")
204       }
205     },
206     :editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
207     :thread_by_subject => false,
208     :edit_signature => false,
209     :ask_for_cc => true,
210     :ask_for_bcc => false,
211     :ask_for_subject => true,
212     :confirm_no_attachments => true,
213     :confirm_top_posting => true,
214     :discard_snippets_from_encrypted_messages => false,
215     :default_attachment_save_dir => "",
216     :sent_source => "sup://sent"
217   }
218   begin
219     FileUtils.mkdir_p Redwood::BASE_DIR
220     Redwood::save_yaml_obj $config, Redwood::CONFIG_FN
221   rescue StandardError => e
222     $stderr.puts "warning: #{e.message}"
223   end
224 end
225
226 require "sup/util"
227 require "sup/hook"
228
229 ## we have to initialize this guy first, because other classes must
230 ## reference it in order to register hooks, and they do that at parse
231 ## time.
232 Redwood::HookManager.init Redwood::HOOK_DIR
233
234 ## everything we need to get logging working
235 require "sup/logger"
236 Redwood::Logger.init.add_sink $stderr
237 include Redwood::LogsStuff
238
239 ## determine encoding and character set
240   $encoding = Locale.current.charset
241   if $encoding
242     debug "using character set encoding #{$encoding.inspect}"
243   else
244     warn "can't find character set by using locale, defaulting to utf-8"
245     $encoding = "UTF-8"
246   end
247
248 require "sup/buffer"
249 require "sup/keymap"
250 require "sup/mode"
251 require "sup/modes/scroll-mode"
252 require "sup/modes/text-mode"
253 require "sup/modes/log-mode"
254 require "sup/update"
255 require "sup/message-chunks"
256 require "sup/message"
257 require "sup/source"
258 require "sup/mbox"
259 require "sup/maildir"
260 require "sup/imap"
261 require "sup/person"
262 require "sup/account"
263 require "sup/thread"
264 require "sup/interactive-lock"
265 require "sup/index"
266 require "sup/textfield"
267 require "sup/colormap"
268 require "sup/label"
269 require "sup/contact"
270 require "sup/tagger"
271 require "sup/draft"
272 require "sup/poll"
273 require "sup/crypto"
274 require "sup/undo"
275 require "sup/horizontal-selector"
276 require "sup/modes/line-cursor-mode"
277 require "sup/modes/help-mode"
278 require "sup/modes/edit-message-mode"
279 require "sup/modes/compose-mode"
280 require "sup/modes/resume-mode"
281 require "sup/modes/forward-mode"
282 require "sup/modes/reply-mode"
283 require "sup/modes/label-list-mode"
284 require "sup/modes/contact-list-mode"
285 require "sup/modes/thread-view-mode"
286 require "sup/modes/thread-index-mode"
287 require "sup/modes/label-search-results-mode"
288 require "sup/modes/search-results-mode"
289 require "sup/modes/person-search-results-mode"
290 require "sup/modes/inbox-mode"
291 require "sup/modes/buffer-list-mode"
292 require "sup/modes/poll-mode"
293 require "sup/modes/file-browser-mode"
294 require "sup/modes/completion-mode"
295 require "sup/modes/console-mode"
296 require "sup/sent"
297
298 $:.each do |base|
299   d = File.join base, "sup/share/modes/"
300   Redwood::Mode.load_all_modes d if File.directory? d
301 end