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