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