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