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