]> git.openstreetmap.org Git - rails.git/blob - app/controllers/trace_controller.rb
Add an expiry header to trace images.
[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       conditions[0] += " AND gpx_files.id IN (#{files.join(',')})"
61     end
62     
63     conditions[0] += " AND gpx_files.visible = ?"
64     conditions << true
65
66     @trace_pages, @traces = paginate(:traces,
67                                      :include => [:user, :tags],
68                                      :conditions => conditions,
69                                      :order => "gpx_files.timestamp DESC",
70                                      :per_page => 20)
71
72     # put together SET of tags across traces, for related links
73     tagset = Hash.new
74     if @traces
75       @traces.each do |trace|
76         trace.tags.reload if params[:tag] # if searched by tag, ActiveRecord won't bring back other tags, so do explicitly here
77         trace.tags.each do |tag|
78           tagset[tag.tag] = tag.tag
79         end
80       end
81     end
82     
83     # final helper vars for view
84     @action = action
85     @display_name = target_user.display_name if target_user
86     @all_tags = tagset.values
87   end
88
89   def mine
90     list(@user, "mine")
91   end
92
93   def view
94     @trace = Trace.find(params[:id])
95
96     if @trace and @trace.visible? and
97        (@trace.public? or @trace.user == @user)
98       @title = "Viewing trace #{@trace.name}"
99     else
100       flash[:notice] = "Trace not found!"
101       redirect_to :controller => 'trace', :action => 'list'
102     end
103   rescue ActiveRecord::RecordNotFound
104     flash[:notice] = "Trace not found!"
105     redirect_to :controller => 'trace', :action => 'list'
106   end
107
108   def create
109     if params[:trace]
110       logger.info(params[:trace][:gpx_file].class.name)
111       if params[:trace][:gpx_file].respond_to?(:read)
112         do_create(params[:trace][:gpx_file], params[:trace][:tagstring],
113                   params[:trace][:description], params[:trace][:public])
114
115         if @trace.id
116           logger.info("id is #{@trace.id}")
117           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."
118
119           redirect_to :action => 'mine'
120         end
121       else
122         @trace = Trace.new({:name => "Dummy",
123                             :tagstring => params[:trace][:tagstring],
124                             :description => params[:trace][:description],
125                             :public => params[:trace][:public],
126                             :inserted => false, :user => @user,
127                             :timestamp => Time.now.getutc})
128         @trace.valid?
129         @trace.errors.add(:gpx_file, "can't be blank")
130       end
131     end
132   end
133
134   def data
135     trace = Trace.find(params[:id])
136
137     if trace.visible? and (trace.public? or (@user and @user == trace.user))
138       if request.format == Mime::XML
139         send_file(trace.xml_file, :filename => "#{trace.id}.xml", :type => Mime::XML.to_s, :disposition => 'attachment')
140       else
141         send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
142       end
143     else
144       render :nothing => true, :status => :not_found
145     end
146   rescue ActiveRecord::RecordNotFound
147     render :nothing => true, :status => :not_found
148   end
149
150   def edit
151     @trace = Trace.find(params[:id])
152
153     if @user and @trace.user == @user
154       if params[:trace]
155         @trace.description = params[:trace][:description]
156         @trace.tagstring = params[:trace][:tagstring]
157         if @trace.save
158           redirect_to :action => 'view'
159         end        
160       end
161     else
162       render :nothing => true, :status => :forbidden
163     end
164   rescue ActiveRecord::RecordNotFound
165     render :nothing => true, :status => :not_found
166   end
167
168   def delete
169     trace = Trace.find(params[:id])
170
171     if @user and trace.user == @user
172       if request.post? and trace.visible?
173         trace.visible = false
174         trace.save
175         flash[:notice] = 'Track scheduled for deletion'
176         redirect_to :controller => 'traces', :action => 'mine'
177       else
178         render :nothing => true, :status => :bad_request
179       end
180     else
181       render :nothing => true, :status => :forbidden
182     end
183   rescue ActiveRecord::RecordNotFound
184     render :nothing => true, :status => :not_found
185   end
186
187   def make_public
188     trace = Trace.find(params[:id])
189
190     if @user and trace.user == @user
191       if request.post? and !trace.public?
192         trace.public = true
193         trace.save
194         flash[:notice] = 'Track made public'
195         redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
196       else
197         render :nothing => true, :status => :bad_request
198       end
199     else
200       render :nothing => true, :status => :forbidden
201     end
202   rescue ActiveRecord::RecordNotFound
203     render :nothing => true, :status => :not_found
204   end
205
206   def georss
207     conditions = ["gpx_files.public = ?", true]
208
209     if params[:display_name]
210       conditions[0] += " AND users.display_name = ?"
211       conditions << params[:display_name]
212     end
213
214     if params[:tag]
215       conditions[0] += " AND EXISTS (SELECT * FROM gpx_file_tags AS gft WHERE gft.gpx_id = gpx_files.id AND gft.tag = ?)"
216       conditions << params[:tag]
217     end
218
219     traces = Trace.find(:all, :include => :user, :conditions => conditions, 
220                         :order => "timestamp DESC", :limit => 20)
221
222     rss = OSM::GeoRSS.new
223
224     traces.each do |trace|
225       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)
226     end
227
228     render :text => rss.to_s, :content_type => "application/rss+xml"
229   end
230
231   def picture
232     trace = Trace.find(params[:id])
233
234     if trace.inserted?
235       if trace.public? or (@user and @user == trace.user)
236         expires_in 7.days, :private => !trace.public, :public => trace.public
237         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
238       else
239         render :nothing => true, :status => :forbidden
240       end
241     else
242       render :nothing => true, :status => :not_found
243     end
244   rescue ActiveRecord::RecordNotFound
245     render :nothing => true, :status => :not_found
246   end
247
248   def icon
249     trace = Trace.find(params[:id])
250
251     if trace.inserted?
252       if trace.public? or (@user and @user == trace.user)
253         expires_in 7.days, :private => !trace.public, :public => trace.public
254         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
255       else
256         render :nothing => true, :status => :forbidden
257       end
258     else
259       render :nothing => true, :status => :not_found
260     end
261   rescue ActiveRecord::RecordNotFound
262     render :nothing => true, :status => :not_found
263   end
264
265   def api_details
266     trace = Trace.find(params[:id])
267
268     if trace.public? or trace.user == @user
269       render :text => trace.to_xml.to_s, :content_type => "text/xml"
270     else
271       render :nothing => true, :status => :forbidden
272     end
273   rescue ActiveRecord::RecordNotFound
274     render :nothing => true, :status => :not_found
275   end
276
277   def api_data
278     trace = Trace.find(params[:id])
279
280     if trace.public? or trace.user == @user
281       send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
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_create
290     if request.post?
291       tags = params[:tags] || ""
292       description = params[:description] || ""
293       pub = params[:public] || false
294       
295       if params[:file].respond_to?(:read)
296         do_create(params[:file], tags, description, pub)
297
298         if @trace.id
299           render :text => @trace.id.to_s, :content_type => "text/plain"
300         elsif @trace.valid?
301           render :nothing => true, :status => :internal_server_error
302         else
303           render :nothing => true, :status => :bad_request
304         end
305       else
306         render :nothing => true, :status => :bad_request
307       end
308     else
309       render :nothing => true, :status => :method_not_allowed
310     end
311   end
312
313 private
314
315   def do_create(file, tags, description, public)
316     # Sanitise the user's filename
317     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
318
319     # Get a temporary filename...
320     filename = "/tmp/#{rand}"
321
322     # ...and save the uploaded file to that location
323     File.open(filename, "w") { |f| f.write(file.read) }
324
325     # Create the trace object, falsely marked as already
326     # inserted to stop the import daemon trying to load it
327     @trace = Trace.new({
328       :name => name,
329       :tagstring => tags,
330       :description => description,
331       :public => public,
332       :inserted => true,
333       :user => @user,
334       :timestamp => Time.now.getutc
335     })
336
337     # Save the trace object
338     if @trace.save
339       # Rename the temporary file to the final name
340       FileUtils.mv(filename, @trace.trace_name)
341
342       # Clear the inserted flag to make the import daemon load the trace
343       @trace.inserted = false
344       @trace.save!
345     else
346       # Remove the file as we have failed to update the database
347       FileUtils.rm_f(filename)
348     end
349     
350     # Finally save whether the user marked the trace as being public
351     if @trace.public?
352       if @user.trace_public_default.nil?
353         @user.preferences.create(:k => "gps.trace.public", :v => "default")
354       end
355     else
356       pref = @user.trace_public_default
357       pref.destroy unless pref.nil?
358     end
359     
360   end
361
362 end