Allow before and after_attend events to put themselves in front of the line
[sinatra.git] / lib / sinatra / event.rb
blob5496535b3dd1fda5430345ea1984efd6e215cb61
1 require 'thread'
3 module Sinatra
4   
5   module EventManager # :nodoc:
6     extend self
8     def reset!
9       @events.clear if @events
10     end
12     def events
13       @events || []
14     end
15     
16     def register_event(event)
17       (@events ||= []) << event
18     end
19     
20     def determine_event(verb, path, if_nil = :present_error)
21       event = events.find(method(if_nil)) do |e|
22         e.verb == verb && e.recognize(path)
23       end
24     end
25     
26     def present_error
27       determine_event(:get, '404', :not_found)
28     end
29     
30     def not_found
31       Event.new(:get, 'not_found', false) do
32         status 404
33     
34         if request.path_info == '/' && request.request_method == 'GET'
35           erb :default_index, :views_directory => SINATRA_ROOT + '/files'
36         else
37           erb :not_found, :views_directory => SINATRA_ROOT + '/files'
38         end
39       end
40     end
41     
42   end
43     
44   class Event # :nodoc:
46     cattr_accessor :logger
47     cattr_accessor :after_filters
48     cattr_accessor :before_filters
49     
50     @@mutex = Mutex.new
51     
52     self.before_filters = []
53     self.after_filters = []
54     
55     def self.before_attend(method_name = nil, options ={}, &block)
56       setup_filter(:before_filters, method_name, options, &block)
57     end
59     def self.after_attend(method_name = nil, options = {}, &block)
60       setup_filter(:after_filters, method_name, options, &block)
61     end
62     
63     def self.setup_filter(filter_set_name, method_name, options = {}, &block)
64       raise "Must specify method or block" if method_name.nil? and !block_given?
65       value = if block_given?
66         block
67       else
68         method_name
69       end
70       insert_index = options[:infront] == true ? 0 : -1
71       send(filter_set_name).insert(insert_index, value)
72     end
73       
74     after_attend :log_event
75     
76     attr_reader :path, :verb
77     
78     def initialize(verb, path, register = true, &block)
79       @verb = verb
80       @path = path
81       @route = Route.new(path)
82       @block = block
83       EventManager.register_event(self) if register
84     end
85     
86     def attend(request)
87       request.params.merge!(@route.params)
88       context = EventContext.new(request)
89       run_safely do
90         caught = catch(:halt) do
91           call_filters(before_filters, context)
92         end
93         body = case caught
94           when :filter_chain_completed
95             begin
96               context.instance_eval(&@block) if @block
97             rescue => e
98               context.error e
99             end
100           when Symbol
101             context.send(caught)
102           when String
103             caught
104           when Fixnum
105             context.status caught
106         end
107         context.body context.body || body || ''
108         call_filters(after_filters, context)
109       end
110       context
111     end
112     alias :call :attend
114     def recognize(path)
115       @route.recognize(path)
116     end
118     private
119     
120       def run_safely
121         if Options.use_mutex?
122           @@mutex.synchronize do
123             yield
124           end
125         else
126           yield
127         end
128       end
129       
130       # Adapted from Merb
131       # calls a filter chain according to rules.
132       def call_filters(filter_set, context)
133         filter_set.each do |filter|
134           case filter
135             when Symbol, String
136              context.send(filter)
137             when Proc
138              context.instance_eval(&filter)
139           end
140         end
141         :filter_chain_completed
142       end
143       
144   end
145   
146   class StaticEvent < Event # :nodoc:
147     
148     def initialize(path, root, register = true)
149       @root = root
150       super(:get, path, register)
151     end
153     def recognize(path)
154       filename = physical_path_for(path)
155       File.exists?(filename) && File.file?(filename)
156     end
157     
158     def physical_path_for(path)
159       path.gsub(/^#{@path}/, @root)
160     end
161     
162     def attend(request)
163       @filename = physical_path_for(request.path_info)
164       context = EventContext.new(request)
165       context.body self
166       context.header 'Content-Type' => MIME_TYPES[File.extname(@filename)[1..-1]]
167       context.header 'Content-Length' => File.size(@filename).to_s
168       context
169     end
170     
171     def each
172       File.open(@filename, "rb") do |file|
173         while part = file.read(8192)
174           yield part
175         end
176       end
177     end
178     
179     # :stopdoc:
180     # From WEBrick.
181     MIME_TYPES = {
182       "ai"    => "application/postscript",
183       "asc"   => "text/plain",
184       "avi"   => "video/x-msvideo",
185       "bin"   => "application/octet-stream",
186       "bmp"   => "image/bmp",
187       "class" => "application/octet-stream",
188       "cer"   => "application/pkix-cert",
189       "crl"   => "application/pkix-crl",
190       "crt"   => "application/x-x509-ca-cert",
191      #"crl"   => "application/x-pkcs7-crl",
192       "css"   => "text/css",
193       "dms"   => "application/octet-stream",
194       "doc"   => "application/msword",
195       "dvi"   => "application/x-dvi",
196       "eps"   => "application/postscript",
197       "etx"   => "text/x-setext",
198       "exe"   => "application/octet-stream",
199       "gif"   => "image/gif",
200       "htm"   => "text/html",
201       "html"  => "text/html",
202       "jpe"   => "image/jpeg",
203       "jpeg"  => "image/jpeg",
204       "jpg"   => "image/jpeg",
205       "lha"   => "application/octet-stream",
206       "lzh"   => "application/octet-stream",
207       "mov"   => "video/quicktime",
208       "mpe"   => "video/mpeg",
209       "mpeg"  => "video/mpeg",
210       "mpg"   => "video/mpeg",
211       "pbm"   => "image/x-portable-bitmap",
212       "pdf"   => "application/pdf",
213       "pgm"   => "image/x-portable-graymap",
214       "png"   => "image/png",
215       "pnm"   => "image/x-portable-anymap",
216       "ppm"   => "image/x-portable-pixmap",
217       "ppt"   => "application/vnd.ms-powerpoint",
218       "ps"    => "application/postscript",
219       "qt"    => "video/quicktime",
220       "ras"   => "image/x-cmu-raster",
221       "rb"    => "text/plain",
222       "rd"    => "text/plain",
223       "rtf"   => "application/rtf",
224       "sgm"   => "text/sgml",
225       "sgml"  => "text/sgml",
226       "tif"   => "image/tiff",
227       "tiff"  => "image/tiff",
228       "txt"   => "text/plain",
229       "xbm"   => "image/x-xbitmap",
230       "xls"   => "application/vnd.ms-excel",
231       "xml"   => "text/xml",
232       "xpm"   => "image/x-xpixmap",
233       "xwd"   => "image/x-xwindowdump",
234       "zip"   => "application/zip",
235     }
236     # :startdoc:
237   
238   end  
239