]> git.openstreetmap.org Git - rails.git/blob - app/controllers/trace_controller.rb
Give the API error handler a generic rescue that returns 500 with
[rails.git] / app / controllers / trace_controller.rb
1 class TraceController < ApplicationController
2   layout 'site'
3
4   before_filter :authorize_web
5   before_filter :require_user, :only => [:mine, :create, :edit, :delete, :make_public]
6   before_filter :authorize, :only => [:api_details, :api_data, :api_create]
7   before_filter :check_database_readable, :except => [:api_details, :api_data, :api_create]
8   before_filter :check_database_writable, :only => [:create, :edit, :delete, :make_public]
9   before_filter :check_api_readable, :only => [:api_details, :api_data]
10   before_filter :check_api_writable, :only => [:api_create]
11  
12   # Counts and selects pages of GPX traces for various criteria (by user, tags, public etc.).
13   #  target_user - if set, specifies the user to fetch traces for.  if not set will fetch all traces
14   def list(target_user = nil, action = "list")
15     # from display name, pick up user id if one user's traces only
16     display_name = params[:display_name]
17     if target_user.nil? and !display_name.blank?
18       target_user = User.find(:first, :conditions => [ "visible = ? and display_name = ?", true, display_name])
19       if target_user.nil?
20         @not_found_user = display_name
21         render :action => 'no_such_user', :status => :not_found
22         return
23       end
24     end
25
26     # set title
27     if target_user.nil?
28       @title = "Public GPS traces"
29     elsif @user and @user == target_user
30       @title = "Your GPS traces"
31     else
32       @title = "Public GPS traces from #{target_user.display_name}"
33     end
34
35     @title += " tagged with #{params[:tag]}" if params[:tag]
36
37     # four main cases:
38     # 1 - all traces, logged in = all public traces + all user's (i.e + all mine)
39     # 2 - all traces, not logged in = all public traces
40     # 3 - user's traces, logged in as same user = all user's traces 
41     # 4 - user's traces, not logged in as that user = all user's public traces
42     if target_user.nil? # all traces
43       if @user
44         conditions = ["(gpx_files.public = ? OR gpx_files.user_id = ?)", true, @user.id] #1
45       else
46         conditions  = ["gpx_files.public = ?", true] #2
47       end
48     else
49       if @user and @user == target_user
50         conditions = ["gpx_files.user_id = ?", @user.id] #3 (check vs user id, so no join + can't pick up non-public traces by changing name)
51       else
52         conditions = ["gpx_files.public = ? AND gpx_files.user_id = ?", true, target_user.id] #4
53       end
54     end
55     
56     if params[:tag]
57       @tag = params[:tag]
58
59       files = Tracetag.find_all_by_tag(params[:tag]).collect { |tt| tt.gpx_id }
60
61       if files.length > 0
62         conditions[0] += " AND gpx_files.id IN (#{files.join(',')})"
63       else
64         conditions[0] += " AND 0 = 1"
65       end
66     end
67     
68     conditions[0] += " AND gpx_files.visible = ?"
69     conditions << true
70
71     @trace_pages, @traces = paginate(:traces,
72                                      :include => [:user, :tags],
73                                      :conditions => conditions,
74                                      :order => "gpx_files.timestamp DESC",
75                                      :per_page => 20)
76
77     # put together SET of tags across traces, for related links
78     tagset = Hash.new
79     if @traces
80       @traces.each do |trace|
81         trace.tags.reload if params[:tag] # if searched by tag, ActiveRecord won't bring back other tags, so do explicitly here
82         trace.tags.each do |tag|
83           tagset[tag.tag] = tag.tag
84         end
85       end
86     end
87     
88     # final helper vars for view
89     @action = action
90     @display_name = target_user.display_name if target_user
91     @all_tags = tagset.values
92   end
93
94   def mine
95     # Load the preference of whether the user set the trace public the last time
96     @trace = Trace.new
97     if @user.preferences.find(:first, :conditions => {:k => "gps.trace.public", :v => "default"}).nil?
98       @trace.public = false
99     else 
100       @trace.public = true
101     end
102     list(@user, "mine")
103   end
104
105   def view
106     @trace = Trace.find(params[:id])
107
108     if @trace and @trace.visible? and
109        (@trace.public? or @trace.user == @user)
110       @title = "Viewing trace #{@trace.name}"
111     else
112       flash[:notice] = "Trace not found!"
113       redirect_to :controller => 'trace', :action => 'list'
114     end
115   rescue ActiveRecord::RecordNotFound
116     flash[:notice] = "Trace not found!"
117     redirect_to :controller => 'trace', :action => 'list'
118   end
119
120   def create
121     if params[:trace]
122       logger.info(params[:trace][:gpx_file].class.name)
123       if params[:trace][:gpx_file].respond_to?(:read)
124         do_create(params[:trace][:gpx_file], params[:trace][:tagstring],
125                   params[:trace][:description], params[:trace][:public])
126
127         if @trace.id
128           logger.info("id is #{@trace.id}")
129           flash[:notice] = "Your GPX file has been uploaded and is awaiting insertion in to the database. This will usually happen within half an hour, and an email will be sent to you on completion."
130
131           redirect_to :action => 'mine'
132         end
133       else
134         @trace = Trace.new({:name => "Dummy",
135                             :tagstring => params[:trace][:tagstring],
136                             :description => params[:trace][:description],
137                             :public => params[:trace][:public],
138                             :inserted => false, :user => @user,
139                             :timestamp => Time.now.getutc})
140         @trace.valid?
141         @trace.errors.add(:gpx_file, "can't be blank")
142       end
143     end
144   end
145
146   def data
147     trace = Trace.find(params[:id])
148
149     if trace.visible? and (trace.public? or (@user and @user == trace.user))
150       if request.format == Mime::XML
151         send_file(trace.xml_file, :filename => "#{trace.id}.xml", :type => Mime::XML.to_s, :disposition => 'attachment')
152       else
153         send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
154       end
155     else
156       render :nothing => true, :status => :not_found
157     end
158   rescue ActiveRecord::RecordNotFound
159     render :nothing => true, :status => :not_found
160   end
161
162   def edit
163     @trace = Trace.find(params[:id])
164
165     if @user and @trace.user == @user
166       if params[:trace]
167         @trace.description = params[:trace][:description]
168         @trace.tagstring = params[:trace][:tagstring]
169         if @trace.save
170           redirect_to :action => 'view'
171         end        
172       end
173     else
174       render :nothing => true, :status => :forbidden
175     end
176   rescue ActiveRecord::RecordNotFound
177     render :nothing => true, :status => :not_found
178   end
179
180   def delete
181     trace = Trace.find(params[:id])
182
183     if @user and trace.user == @user
184       if request.post? and trace.visible?
185         trace.visible = false
186         trace.save
187         flash[:notice] = 'Track scheduled for deletion'
188         redirect_to :controller => 'traces', :action => 'mine'
189       else
190         render :nothing => true, :status => :bad_request
191       end
192     else
193       render :nothing => true, :status => :forbidden
194     end
195   rescue ActiveRecord::RecordNotFound
196     render :nothing => true, :status => :not_found
197   end
198
199   def make_public
200     trace = Trace.find(params[:id])
201
202     if @user and trace.user == @user
203       if request.post? and !trace.public?
204         trace.public = true
205         trace.save
206         flash[:notice] = 'Track made public'
207         redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
208       else
209         render :nothing => true, :status => :bad_request
210       end
211     else
212       render :nothing => true, :status => :forbidden
213     end
214   rescue ActiveRecord::RecordNotFound
215     render :nothing => true, :status => :not_found
216   end
217
218   def georss
219     conditions = ["gpx_files.public = ?", true]
220
221     if params[:display_name]
222       conditions[0] += " AND users.display_name = ?"
223       conditions << params[:display_name]
224     end
225
226     if params[:tag]
227       conditions[0] += " AND EXISTS (SELECT * FROM gpx_file_tags AS gft WHERE gft.gpx_id = gpx_files.id AND gft.tag = ?)"
228       conditions << params[:tag]
229     end
230
231     traces = Trace.find(:all, :include => :user, :conditions => conditions, 
232                         :order => "timestamp DESC", :limit => 20)
233
234     rss = OSM::GeoRSS.new
235
236     traces.each do |trace|
237       rss.add(trace.latitude, trace.longitude, trace.name, trace.user.display_name, url_for({:controller => 'trace', :action => 'view', :id => trace.id, :display_name => trace.user.display_name}), "<img src='#{url_for({:controller => 'trace', :action => 'icon', :id => trace.id, :user_login => trace.user.display_name})}'> GPX file with #{trace.size} points from #{trace.user.display_name}", trace.timestamp)
238     end
239
240     render :text => rss.to_s, :content_type => "application/rss+xml"
241   end
242
243   def picture
244     trace = Trace.find(params[:id])
245
246     if trace.inserted?
247       if trace.public? or (@user and @user == trace.user)
248         expires_in 7.days, :private => !trace.public, :public => trace.public
249         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
250       else
251         render :nothing => true, :status => :forbidden
252       end
253     else
254       render :nothing => true, :status => :not_found
255     end
256   rescue ActiveRecord::RecordNotFound
257     render :nothing => true, :status => :not_found
258   end
259
260   def icon
261     trace = Trace.find(params[:id])
262
263     if trace.inserted?
264       if trace.public? or (@user and @user == trace.user)
265         expires_in 7.days, :private => !trace.public, :public => trace.public
266         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
267       else
268         render :nothing => true, :status => :forbidden
269       end
270     else
271       render :nothing => true, :status => :not_found
272     end
273   rescue ActiveRecord::RecordNotFound
274     render :nothing => true, :status => :not_found
275   end
276
277   def api_details
278     trace = Trace.find(params[:id])
279
280     if trace.public? or trace.user == @user
281       render :text => trace.to_xml.to_s, :content_type => "text/xml"
282     else
283       render :nothing => true, :status => :forbidden
284     end
285   rescue ActiveRecord::RecordNotFound
286     render :nothing => true, :status => :not_found
287   end
288
289   def api_data
290     trace = Trace.find(params[:id])
291
292     if trace.public? or trace.user == @user
293       send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
294     else
295       render :nothing => true, :status => :forbidden
296     end
297   rescue ActiveRecord::RecordNotFound
298     render :nothing => true, :status => :not_found
299   end
300
301   def api_create
302     if request.post?
303       tags = params[:tags] || ""
304       description = params[:description] || ""
305       pub = params[:public] || false
306       
307       if params[:file].respond_to?(:read)
308         do_create(params[:file], tags, description, pub)
309
310         if @trace.id
311           render :text => @trace.id.to_s, :content_type => "text/plain"
312         elsif @trace.valid?
313           render :nothing => true, :status => :internal_server_error
314         else
315           render :nothing => true, :status => :bad_request
316         end
317       else
318         render :nothing => true, :status => :bad_request
319       end
320     else
321       render :nothing => true, :status => :method_not_allowed
322     end
323   end
324
325 private
326
327   def do_create(file, tags, description, public)
328     # Sanitise the user's filename
329     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
330
331     # Get a temporary filename...
332     filename = "/tmp/#{rand}"
333
334     # ...and save the uploaded file to that location
335     File.open(filename, "w") { |f| f.write(file.read) }
336
337     # Create the trace object, falsely marked as already
338     # inserted to stop the import daemon trying to load it
339     @trace = Trace.new({
340       :name => name,
341       :tagstring => tags,
342       :description => description,
343       :public => public,
344       :inserted => true,
345       :user => @user,
346       :timestamp => Time.now.getutc
347     })
348
349     # Save the trace object
350     if @trace.save
351       # Rename the temporary file to the final name
352       FileUtils.mv(filename, @trace.trace_name)
353
354       # Clear the inserted flag to make the import daemon load the trace
355       @trace.inserted = false
356       @trace.save!
357     else
358       # Remove the file as we have failed to update the database
359       FileUtils.rm_f(filename)
360     end
361     
362     # Finally save whether the user marked the trace as being public
363     if @trace.public?
364       if @user.trace_public_default.nil?
365         @user.preferences.create(:k => "gps.trace.public", :v => "default")
366       end
367     else
368       pref = @user.trace_public_default
369       pref.destroy unless pref.nil?
370     end
371     
372   end
373
374 end