]> git.openstreetmap.org Git - rails.git/blob - app/controllers/trace_controller.rb
532893ef170ebc238523b8487db1cb61adffc5a8
[rails.git] / app / controllers / trace_controller.rb
1 class TraceController < ApplicationController
2   layout 'site'
3
4   before_filter :authorize_web
5   before_filter :set_locale
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     @title = I18n.t('trace.create.upload_trace')
146   end
147
148   def data
149     trace = Trace.find(params[:id])
150
151     if trace.visible? and (trace.public? or (@user and @user == trace.user))
152       if request.format == Mime::XML
153         send_file(trace.xml_file, :filename => "#{trace.id}.xml", :type => Mime::XML.to_s, :disposition => 'attachment')
154       else
155         send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
156       end
157     else
158       render :nothing => true, :status => :not_found
159     end
160   rescue ActiveRecord::RecordNotFound
161     render :nothing => true, :status => :not_found
162   end
163
164   def edit
165     @trace = Trace.find(params[:id])
166
167     if @user and @trace.user == @user
168       if params[:trace]
169         @trace.description = params[:trace][:description]
170         @trace.tagstring = params[:trace][:tagstring]
171         if @trace.save
172           redirect_to :action => 'view'
173         end        
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 delete
183     trace = Trace.find(params[:id])
184
185     if @user and trace.user == @user
186       if request.post? and trace.visible?
187         trace.visible = false
188         trace.save
189         flash[:notice] = 'Track scheduled for deletion'
190         redirect_to :controller => 'traces', :action => 'mine'
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 make_public
202     trace = Trace.find(params[:id])
203
204     if @user and trace.user == @user
205       if request.post? and !trace.public?
206         trace.public = true
207         trace.save
208         flash[:notice] = 'Track made public'
209         redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
210       else
211         render :nothing => true, :status => :bad_request
212       end
213     else
214       render :nothing => true, :status => :forbidden
215     end
216   rescue ActiveRecord::RecordNotFound
217     render :nothing => true, :status => :not_found
218   end
219
220   def georss
221     conditions = ["gpx_files.public = ?", true]
222
223     if params[:display_name]
224       conditions[0] += " AND users.display_name = ?"
225       conditions << params[:display_name]
226     end
227
228     if params[:tag]
229       conditions[0] += " AND EXISTS (SELECT * FROM gpx_file_tags AS gft WHERE gft.gpx_id = gpx_files.id AND gft.tag = ?)"
230       conditions << params[:tag]
231     end
232
233     traces = Trace.find(:all, :include => :user, :conditions => conditions, 
234                         :order => "timestamp DESC", :limit => 20)
235
236     rss = OSM::GeoRSS.new
237
238     traces.each do |trace|
239       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)
240     end
241
242     render :text => rss.to_s, :content_type => "application/rss+xml"
243   end
244
245   def picture
246     trace = Trace.find(params[:id])
247
248     if trace.inserted?
249       if trace.public? or (@user and @user == trace.user)
250         expires_in 7.days, :private => !trace.public, :public => trace.public
251         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
252       else
253         render :nothing => true, :status => :forbidden
254       end
255     else
256       render :nothing => true, :status => :not_found
257     end
258   rescue ActiveRecord::RecordNotFound
259     render :nothing => true, :status => :not_found
260   end
261
262   def icon
263     trace = Trace.find(params[:id])
264
265     if trace.inserted?
266       if trace.public? or (@user and @user == trace.user)
267         expires_in 7.days, :private => !trace.public, :public => trace.public
268         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
269       else
270         render :nothing => true, :status => :forbidden
271       end
272     else
273       render :nothing => true, :status => :not_found
274     end
275   rescue ActiveRecord::RecordNotFound
276     render :nothing => true, :status => :not_found
277   end
278
279   def api_details
280     trace = Trace.find(params[:id])
281
282     if trace.public? or trace.user == @user
283       render :text => trace.to_xml.to_s, :content_type => "text/xml"
284     else
285       render :nothing => true, :status => :forbidden
286     end
287   rescue ActiveRecord::RecordNotFound
288     render :nothing => true, :status => :not_found
289   end
290
291   def api_data
292     trace = Trace.find(params[:id])
293
294     if trace.public? or trace.user == @user
295       send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
296     else
297       render :nothing => true, :status => :forbidden
298     end
299   rescue ActiveRecord::RecordNotFound
300     render :nothing => true, :status => :not_found
301   end
302
303   def api_create
304     if request.post?
305       tags = params[:tags] || ""
306       description = params[:description] || ""
307       pub = params[:public] || false
308       
309       if params[:file].respond_to?(:read)
310         do_create(params[:file], tags, description, pub)
311
312         if @trace.id
313           render :text => @trace.id.to_s, :content_type => "text/plain"
314         elsif @trace.valid?
315           render :nothing => true, :status => :internal_server_error
316         else
317           render :nothing => true, :status => :bad_request
318         end
319       else
320         render :nothing => true, :status => :bad_request
321       end
322     else
323       render :nothing => true, :status => :method_not_allowed
324     end
325   end
326
327 private
328
329   def do_create(file, tags, description, public)
330     # Sanitise the user's filename
331     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
332
333     # Get a temporary filename...
334     filename = "/tmp/#{rand}"
335
336     # ...and save the uploaded file to that location
337     File.open(filename, "w") { |f| f.write(file.read) }
338
339     # Create the trace object, falsely marked as already
340     # inserted to stop the import daemon trying to load it
341     @trace = Trace.new({
342       :name => name,
343       :tagstring => tags,
344       :description => description,
345       :public => public,
346       :inserted => true,
347       :user => @user,
348       :timestamp => Time.now.getutc
349     })
350
351     # Save the trace object
352     if @trace.save
353       # Rename the temporary file to the final name
354       FileUtils.mv(filename, @trace.trace_name)
355
356       # Clear the inserted flag to make the import daemon load the trace
357       @trace.inserted = false
358       @trace.save!
359     else
360       # Remove the file as we have failed to update the database
361       FileUtils.rm_f(filename)
362     end
363     
364     # Finally save whether the user marked the trace as being public
365     if @trace.public?
366       if @user.trace_public_default.nil?
367         @user.preferences.create(:k => "gps.trace.public", :v => "default")
368       end
369     else
370       pref = @user.trace_public_default
371       pref.destroy unless pref.nil?
372     end
373     
374   end
375
376 end