]> git.cworth.org Git - sup/blob - lib/sup.rb
fixes for ruby1.9
[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 Redwood::SENT_FN
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   end
119
120   def finish
121     Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
122     Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
123     Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
124   end
125
126   ## not really a good place for this, so I'll just dump it here.
127   ##
128   ## a source error is either a FatalSourceError or an OutOfSyncSourceError.
129   ## the superclass SourceError is just a generic.
130   def report_broken_sources opts={}
131     return unless BufferManager.instantiated?
132
133     broken_sources = Index.sources.select { |s| s.error.is_a? FatalSourceError }
134     unless broken_sources.empty?
135       BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
136         TextMode.new(<<EOM)
137 Source error notification
138 -------------------------
139
140 Hi there. It looks like one or more message sources is reporting
141 errors. Until this is corrected, messages from these sources cannot
142 be viewed, and new messages will not be detected.
143
144 #{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join("\n\n")}
145 EOM
146 #' stupid ruby-mode
147       end
148     end
149
150     desynced_sources = Index.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
151     unless desynced_sources.empty?
152       BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
153         TextMode.new(<<EOM)
154 Out-of-sync source notification
155 -------------------------------
156
157 Hi there. It looks like one or more sources has fallen out of sync
158 with my index. This can happen when you modify these sources with
159 other email clients. (Sorry, I don't play well with others.)
160
161 Until this is corrected, messages from these sources cannot be viewed,
162 and new messages will not be detected. Luckily, this is easy to correct!
163
164 #{desynced_sources.map do |s|
165   "Source: " + s.to_s + 
166    "\n Error: " + s.error.message.wrap(70).join("\n        ") + 
167    "\n   Fix: sup-sync --changed #{s.to_s}"
168   end}
169 EOM
170 #' stupid ruby-mode
171       end
172     end
173   end
174
175   module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
176                   :report_broken_sources
177 end
178
179 ## set up default configuration file
180 if File.exists? Redwood::CONFIG_FN
181   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
182 else
183   require 'etc'
184   require 'socket'
185   name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first rescue nil
186   name ||= ENV["USER"]
187   email = ENV["USER"] + "@" + 
188     begin
189       Socket.gethostbyname(Socket.gethostname).first
190     rescue SocketError
191       Socket.gethostname
192     end
193
194   $config = {
195     :accounts => {
196       :default => {
197         :name => name,
198         :email => email,
199         :alternates => [],
200         :sendmail => "/usr/sbin/sendmail -oem -ti",
201         :signature => File.join(ENV["HOME"], ".signature")
202       }
203     },
204     :editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
205     :thread_by_subject => false,
206     :edit_signature => false,
207     :ask_for_cc => true,
208     :ask_for_bcc => false,
209     :ask_for_subject => true,
210     :confirm_no_attachments => true,
211     :confirm_top_posting => true,
212     :discard_snippets_from_encrypted_messages => false,
213     :default_attachment_save_dir => "",
214   }
215   begin
216     FileUtils.mkdir_p Redwood::BASE_DIR
217     Redwood::save_yaml_obj $config, Redwood::CONFIG_FN
218   rescue StandardError => e
219     $stderr.puts "warning: #{e.message}"
220   end
221 end
222
223 require "sup/util"
224 require "sup/hook"
225
226 ## we have to initialize this guy first, because other classes must
227 ## reference it in order to register hooks, and they do that at parse
228 ## time.
229 Redwood::HookManager.new Redwood::HOOK_DIR
230
231 ## everything we need to get logging working
232 require "sup/buffer"
233 require "sup/keymap"
234 require "sup/mode"
235 require "sup/modes/scroll-mode"
236 require "sup/modes/text-mode"
237 require "sup/modes/log-mode"
238 require "sup/logger"
239 module Redwood
240   def log s; Logger.log s; end
241   module_function :log
242 end
243
244 ## determine encoding and character set
245   $encoding = Locale.current.charset
246   if $encoding
247     Redwood::log "using character set encoding #{$encoding.inspect}"
248   else
249     Redwood::log "warning: can't find character set by using locale, defaulting to utf-8"
250     $encoding = "UTF-8"
251   end
252
253 ## now everything else (which can feel free to call Redwood::log at load time)
254 require "sup/update"
255 require "sup/suicide"
256 require "sup/message-chunks"
257 require "sup/message"
258 require "sup/source"
259 require "sup/mbox"
260 require "sup/maildir"
261 require "sup/imap"
262 require "sup/person"
263 require "sup/account"
264 require "sup/thread"
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/sent"
296
297 $:.each do |base|
298   d = File.join base, "sup/share/modes/"
299   Redwood::Mode.load_all_modes d if File.directory? d
300 end