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