Add condition for user not found in trace controller.
[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         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
237       else
238         render :nothing => true, :status => :forbidden
239       end
240     else
241       render :nothing => true, :status => :not_found
242     end
243   rescue ActiveRecord::RecordNotFound
244     render :nothing => true, :status => :not_found
245   end
246
247   def icon
248     trace = Trace.find(params[:id])
249
250     if trace.inserted?
251       if trace.public? or (@user and @user == trace.user)
252         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
253       else
254         render :nothing => true, :status => :forbidden
255       end
256     else
257       render :nothing => true, :status => :not_found
258     end
259   rescue ActiveRecord::RecordNotFound
260     render :nothing => true, :status => :not_found
261   end
262
263   def api_details
264     trace = Trace.find(params[:id])
265
266     if trace.public? or trace.user == @user
267       render :text => trace.to_xml.to_s, :content_type => "text/xml"
268     else
269       render :nothing => true, :status => :forbidden
270     end
271   rescue ActiveRecord::RecordNotFound
272     render :nothing => true, :status => :not_found
273   end
274
275   def api_data
276     trace = Trace.find(params[:id])
277
278     if trace.public? or trace.user == @user
279       send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
280     else
281       render :nothing => true, :status => :forbidden
282     end
283   rescue ActiveRecord::RecordNotFound
284     render :nothing => true, :status => :not_found
285   end
286
287   def api_create
288     if request.post?
289       tags = params[:tags] || ""
290       description = params[:description] || ""
291       pub = params[:public] || false
292       
293       if params[:file].respond_to?(:read)
294         do_create(params[:file], tags, description, pub)
295
296         if @trace.id
297           render :text => @trace.id.to_s, :content_type => "text/plain"
298         elsif @trace.valid?
299           render :nothing => true, :status => :internal_server_error
300         else
301           render :nothing => true, :status => :bad_request
302         end
303       else
304         render :nothing => true, :status => :bad_request
305       end
306     else
307       render :nothing => true, :status => :method_not_allowed
308     end
309   end
310
311 private
312
313   def do_create(file, tags, description, public)
314     # Sanitise the user's filename
315     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
316
317     # Get a temporary filename...
318     filename = "/tmp/#{rand}"
319
320     # ...and save the uploaded file to that location
321     File.open(filename, "w") { |f| f.write(file.read) }
322
323     # Create the trace object, falsely marked as already
324     # inserted to stop the import daemon trying to load it
325     @trace = Trace.new({
326       :name => name,
327       :tagstring => tags,
328       :description => description,
329       :public => public,
330       :inserted => true,
331       :user => @user,
332       :timestamp => Time.now.getutc
333     })
334
335     # Save the trace object
336     if @trace.save
337       # Rename the temporary file to the final name
338       FileUtils.mv(filename, @trace.trace_name)
339
340       # Clear the inserted flag to make the import daemon load the trace
341       @trace.inserted = false
342       @trace.save!
343     else
344       # Remove the file as we have failed to update the database
345       FileUtils.rm_f(filename)
346     end
347     
348     # Finally save whether the user marked the trace as being public
349     if @trace.public?
350       if @user.trace_public_default.nil?
351         @user.preferences.create(:k => "gps.trace.public", :v => "default")
352       end
353     else
354       pref = @user.trace_public_default
355       pref.destroy unless pref.nil?
356     end
357     
358   end
359
360 end