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