Tidy up trace handling a bit, and add support for per-user and per-tag feeds.
[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     @trace_pages, @traces = paginate(:traces,
52                                      :include => [:user, :tags],
53                                      :conditions => conditions,
54                                      :order => "gpx_files.timestamp DESC",
55                                      :per_page => 20)
56
57     # put together SET of tags across traces, for related links
58     tagset = Hash.new
59     if @traces
60       @traces.each do |trace|
61         trace.tags.reload if params[:tag] # if searched by tag, ActiveRecord won't bring back other tags, so do explicitly here
62         trace.tags.each do |tag|
63           tagset[tag.tag] = tag.tag
64         end
65       end
66     end
67     
68     # final helper vars for view
69     @action = action
70     @display_name = target_user.display_name if target_user
71     @all_tags = tagset.values
72   end
73
74   def mine
75     if @user
76       list(@user, "mine") unless @user.nil?
77     else
78       redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
79     end
80   end
81
82   def view
83     @trace = Trace.find(params[:id])
84     @title = "Viewing trace #{@trace.name}"
85     unless @trace.public
86       if @user
87         render :nothing, :status => :forbidden if @trace.user.id != @user.id
88       end
89     end
90   rescue ActiveRecord::RecordNotFound
91     render :nothing => true, :status => :not_found
92   end
93
94   def create
95     name = params[:trace][:gpx_file].original_filename.gsub(/[^a-zA-Z0-9.]/, '_') # This makes sure filenames are sane
96
97     do_create(name, params[:trace][:tagstring], params[:trace][:description], params[:trace][:public]) do |f|
98       f.write(params[:trace][:gpx_file].read)
99     end
100
101     if @trace.id
102       logger.info("id is #{@trace.id}")
103       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."
104
105       redirect_to :action => 'mine'
106     end
107   end
108
109   def data
110     trace = Trace.find(params[:id])
111     if trace and (trace.public? or (@user and @user == trace.user))
112       send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
113     else
114       render :nothing, :status => :not_found
115     end
116   end
117
118   def make_public
119     trace = Trace.find(params[:id])
120     if @user and trace.user == @user and !trace.public
121       trace.public = true
122       trace.save
123       flash[:notice] = 'Track made public'
124       redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
125     end
126   end
127
128   def georss
129     conditions = ["gpx_files.public = 1"]
130
131     if params[:display_name]
132       conditions[0] += " AND users.display_name = ?"
133       conditions << params[:display_name]
134     end
135     
136     if params[:tag]
137       conditions[0] += " AND EXISTS (SELECT * FROM gpx_file_tags AS gft WHERE gft.gpx_id = gpx_files.id AND gft.tag = ?)"
138       conditions << params[:tag]
139     end
140
141     traces = Trace.find(:all, :include => :user, :conditions => conditions, 
142                         :order => "timestamp DESC", :limit => 20)
143
144     rss = OSM::GeoRSS.new
145
146     traces.each do |trace|
147       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)
148     end
149
150     render :text => rss.to_s, :content_type => "application/rss+xml"
151   end
152
153   def picture
154     begin
155       trace = Trace.find(params[:id])
156
157       if trace.public? or (@user and @user == trace.user)
158         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
159       else
160         render :nothing, :status => :forbidden
161       end
162     rescue ActiveRecord::RecordNotFound
163       render :nothing => true, :status => :not_found
164     rescue
165       render :nothing => true, :status => :internal_server_error
166     end
167   end
168
169   def icon
170     begin
171       trace = Trace.find(params[:id])
172
173       if trace.public? or (@user and @user == trace.user)
174         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
175       else
176         render :nothing, :status => :forbidden
177       end
178     rescue ActiveRecord::RecordNotFound
179       render :nothing => true, :status => :not_found
180     rescue
181       render :nothing => true, :status => :internal_server_error
182     end
183   end
184
185   def api_details
186     begin
187       trace = Trace.find(params[:id])
188
189       if trace.public? or trace.user == @user
190         render :text => trace.to_xml.to_s, :content_type => "text/xml"
191       else
192         render :nothing => true, :status => :forbidden
193       end
194     rescue ActiveRecord::RecordNotFound
195       render :nothing => true, :status => :not_found
196     rescue
197       render :nothing => true, :status => :internal_server_error
198     end
199   end
200
201   def api_data
202     render :action => 'data'
203   end
204
205   def api_create
206     do_create(params[:filename], params[:tags], params[:description], true) do |f|
207       f.write(request.raw_post)
208     end
209
210     if @trace.id
211       render :nothing => true
212     else
213       render :nothing => true, :status => :internal_server_error
214     end
215   end
216
217 private
218
219   def do_create(name, tags, description, public)
220     filename = "/tmp/#{rand}"
221
222     File.open(filename, "w") { |f| yield f }
223
224     @trace = Trace.new({:name => name, :tagstring => tags,
225                         :description => description, :public => public})
226     @trace.inserted = false
227     @trace.user = @user
228     @trace.timestamp = Time.now
229
230     if @trace.save
231       File.rename(filename, @trace.trace_name)
232     else
233       FileUtils.rm_f(filename)
234     end
235   end
236
237 end