Use FileUtils.mv instead of File.rename as it will do a copy+delete when
[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       if request.format == Mime::XML
125         send_file(trace.xml_file, :filename => "#{trace.id}.xml", :type => Mime::XML.to_s, :disposition => 'attachment')
126       else
127         send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
128       end
129     else
130       render :nothing, :status => :not_found
131     end
132   rescue ActiveRecord::RecordNotFound
133     render :nothing => true, :status => :not_found
134   end
135
136   def edit
137     @trace = Trace.find(params[:id])
138
139     if @user and @trace.user == @user
140       if params[:trace]
141         @trace.description = params[:trace][:description]
142         @trace.tagstring = params[:trace][:tagstring]
143         if @trace.save
144           redirect_to :action => 'view'
145         end        
146       end
147     else
148       render :nothing, :status => :forbidden
149     end
150   rescue ActiveRecord::RecordNotFound
151     render :nothing => true, :status => :not_found
152   end
153
154   def delete
155     trace = Trace.find(params[:id])
156
157     if @user and trace.user == @user
158       if request.post? and trace.visible?
159         trace.visible = false
160         trace.save
161         flash[:notice] = 'Track scheduled for deletion'
162         redirect_to :controller => 'traces', :action => 'mine'
163       else
164         render :nothing, :status => :bad_request
165       end
166     else
167       render :nothing, :status => :forbidden
168     end
169   rescue ActiveRecord::RecordNotFound
170     render :nothing => true, :status => :not_found
171   end
172
173   def make_public
174     trace = Trace.find(params[:id])
175
176     if @user and trace.user == @user
177       if request.post? and !trace.public?
178         trace.public = true
179         trace.save
180         flash[:notice] = 'Track made public'
181         redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
182       else
183         render :nothing, :status => :bad_request
184       end
185     else
186       render :nothing, :status => :forbidden
187     end
188   rescue ActiveRecord::RecordNotFound
189     render :nothing => true, :status => :not_found
190   end
191
192   def georss
193     conditions = ["gpx_files.public = 1"]
194
195     if params[:display_name]
196       conditions[0] += " AND users.display_name = ?"
197       conditions << params[:display_name]
198     end
199     
200     if params[:tag]
201       conditions[0] += " AND EXISTS (SELECT * FROM gpx_file_tags AS gft WHERE gft.gpx_id = gpx_files.id AND gft.tag = ?)"
202       conditions << params[:tag]
203     end
204
205     traces = Trace.find(:all, :include => :user, :conditions => conditions, 
206                         :order => "timestamp DESC", :limit => 20)
207
208     rss = OSM::GeoRSS.new
209
210     traces.each do |trace|
211       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)
212     end
213
214     render :text => rss.to_s, :content_type => "application/rss+xml"
215   end
216
217   def picture
218     trace = Trace.find(params[:id])
219
220     if trace.inserted?
221       if trace.public? or (@user and @user == trace.user)
222         send_file(trace.large_picture_name, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline')
223       else
224         render :nothing, :status => :forbidden
225       end
226     else
227       render :nothing => true, :status => :not_found
228     end
229   rescue ActiveRecord::RecordNotFound
230     render :nothing => true, :status => :not_found
231   end
232
233   def icon
234     trace = Trace.find(params[:id])
235
236     if trace.inserted?
237       if trace.public? or (@user and @user == trace.user)
238         send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
239       else
240         render :nothing, :status => :forbidden
241       end
242     else
243       render :nothing => true, :status => :not_found
244     end
245   rescue ActiveRecord::RecordNotFound
246     render :nothing => true, :status => :not_found
247   end
248
249   def api_details
250     trace = Trace.find(params[:id])
251
252     if trace.public? or trace.user == @user
253       render :text => trace.to_xml.to_s, :content_type => "text/xml"
254     else
255       render :nothing => true, :status => :forbidden
256     end
257   rescue ActiveRecord::RecordNotFound
258     render :nothing => true, :status => :not_found
259   end
260
261   def api_data
262     trace = Trace.find(params[:id])
263
264     if trace.public? or trace.user == @user
265       send_file(trace.trace_name, :filename => "#{trace.id}#{trace.extension_name}", :type => trace.mime_type, :disposition => 'attachment')
266     else
267       render :nothing => true, :status => :forbidden
268     end
269   rescue ActiveRecord::RecordNotFound
270     render :nothing => true, :status => :not_found
271   end
272
273   def api_create
274     if request.post?
275       do_create(params[:file], params[:tags], params[:description], params[:public])
276
277       if @trace.id
278         render :text => @trace.id.to_s, :content_type => "text/plain"
279       elsif @trace.valid?
280         render :nothing => true, :status => :internal_server_error
281       else
282         render :nothing => true, :status => :bad_request
283       end
284     else
285       render :nothing => true, :status => :method_not_allowed
286     end
287   end
288
289 private
290
291   def do_create(file, tags, description, public)
292     name = file.original_filename.gsub(/[^a-zA-Z0-9.]/, '_')
293     filename = "/tmp/#{rand}"
294
295     File.open(filename, "w") { |f| f.write(file.read) }
296
297     @trace = Trace.new({:name => name, :tagstring => tags,
298                         :description => description, :public => public})
299     @trace.inserted = false
300     @trace.user = @user
301     @trace.timestamp = Time.now
302
303     if @trace.save
304       FileUtils.mv(filename, @trace.trace_name)
305     else
306       FileUtils.rm_f(filename)
307     end
308   end
309
310 end