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