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