Avoid reading traces and trace images into memory when sending them to
[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   #  paging_action - the action that will be linked back to from view
9   def list (target_user = nil, paging_action = 'list')
10     @traces_per_page = 20
11     page_index = params[:page] ? params[:page].to_i - 1 : 0 # nice 1-based page -> 0-based page index
12
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 and display_name != ''
16       @paging_action = 'view'
17       @display_name = display_name
18       target_user = User.find(:first, :conditions => [ "display_name = ?", display_name])
19     end
20
21     opt = Hash.new
22     opt[:include] = [:user, :tags] # load users and tags from db at same time as traces
23
24     # four main cases:
25     # 1 - all traces, logged in = all public traces + all user's (i.e + all mine)
26     # 2 - all traces, not logged in = all public traces
27     # 3 - user's traces, logged in as same user = all user's traces 
28     # 4 - user's traces, not logged in as that user = all user's public traces
29     if target_user.nil? # all traces
30       if @user
31         conditions = ["(public = 1 OR user_id = ?)", @user.id] #1
32       else
33         conditions  = ["public = 1"] #2
34       end
35     else
36       if @user and @user.id == target_user.id
37         conditions = ["user_id = ?", @user.id] #3 (check vs user id, so no join + can't pick up non-public traces by changing name)
38       else
39         conditions = ["public = 1 AND user_id = ?", target_user.id] #4
40       end
41     end
42     conditions[0] += " AND users.display_name != ''" # users need to set display name before traces will be exposed
43     
44     opt[:order] = 'timestamp DESC'
45     if params[:tag]
46       @tag = params[:tag]
47       conditions[0] += " AND gpx_file_tags.tag = ?"
48       conditions << @tag;
49     end
50     
51     opt[:conditions] = conditions
52
53     # count traces using all options except limit
54     @max_trace = Trace.count(opt)
55     @max_page = Integer((@max_trace + 1) / @traces_per_page) 
56     
57     # last step before fetch - add paging options
58     opt[:limit] = @traces_per_page
59     if page_index > 0
60       opt[:offset] = @traces_per_page * page_index
61     end
62
63     @traces = Trace.find(:all , opt)
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     @display_name = display_name
78     @all_tags = tagset.values
79     @paging_action = paging_action # the action that paging requests should route back to, e.g. 'list' or 'mine'
80     @page = page_index + 1 # nice 1-based external page numbers
81   end
82
83   def mine
84     if @user
85       list(@user, 'mine') unless @user.nil?
86     else
87       redirect_to :controller => 'user', :action => 'login', :referer => request.request_uri
88     end
89   end
90
91   def view
92     @trace = Trace.find(params[:id])
93     unless @trace.public
94       if @user
95         render :nothing, :status => 401 if @trace.user.id != @user.id
96       end
97     end
98   end
99
100   def create
101     filename = "/tmp/#{rand}"
102
103     File.open(filename, "w") { |f| f.write(params[:trace][:gpx_file].read) }
104     params[:trace][:name] = params[:trace][:gpx_file].original_filename.gsub(/[^a-zA-Z0-9.]/, '_') # This makes sure filenames are sane
105     params[:trace].delete('gpx_file') # remove the field from the hash, because there's no such field in the DB
106     @trace = Trace.new(params[:trace])
107     @trace.inserted = false
108     @trace.user = @user
109     @trace.timestamp = Time.now
110
111     if @trace.save
112       saved_filename = "/home/osm/gpx/#{@trace.id}.gpx"
113       File.rename(filename, saved_filename)
114
115       logger.info("id is #{@trace.id}")
116       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."
117       redirect_to :action => 'mine'
118     end
119   end
120
121   def data
122     trace = Trace.find(params[:id])
123     if trace and (trace.public? or (@user and @user == trace.user))
124       send_file(trace.trace_name, :filename => "#{trace.id}.gpx", :type => trace.mime_type, :disposition => 'attachment')
125     else
126       render :nothing, :status => 404
127     end
128   end
129
130   def make_public
131     trace = Trace.find(params[:id])
132     if @user and trace.user == @user and !trace.public
133       trace.public = true
134       trace.save
135       flash[:notice] = 'Track made public'
136       redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
137     end
138   end
139
140   def georss
141     traces = Trace.find(:all, :conditions => ['public = true'], :order => 'timestamp DESC', :limit => 20)
142
143     rss = OSM::GeoRSS.new
144
145     #def add(latitude=0, longitude=0, title_text='dummy title', url='http://www.example.com/', description_text='dummy description', timestamp=Time.now)
146     traces.each do |trace|
147       rss.add(trace.latitude, trace.longitude, trace.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     response.headers["Content-Type"] = 'application/rss+xml'
151
152     render :text => rss.to_s
153   end
154
155   def picture
156     trace = Trace.find(params[:id])
157     if trace and (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 => 404
161     end
162   end
163
164   def icon
165     trace = Trace.find(params[:id])
166     if trace and (trace.public? or (@user and @user == trace.user))
167       send_file(trace.icon_picture_name, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline')
168     else
169       render :nothing, :status => 404
170     end
171   end
172
173   def api_details
174     trace = Trace.find(params[:id])
175     doc = OSM::API.new.get_xml_doc
176     doc.root << trace.to_xml_node() if trace.public? or trace.user == @user
177     render :text => doc.to_s
178   end
179
180   def api_data
181     render :action => 'data'
182   end
183
184   def api_create
185     #FIXME merge this code with create as they're pretty similar?
186     
187     filename = "/tmp/#{rand}"
188     File.open(filename, "w") { |f| f.write(request.raw_post) }
189     params[:trace] = {}
190     params[:trace][:name] = params[:filename]
191     params[:trace][:tagstring] = params[:tags]
192     params[:trace][:description] = params[:description]
193     @trace = Trace.new(params[:trace])
194     @trace.inserted = false
195     @trace.user = @user
196     @trace.timestamp = Time.now
197
198     if @trace.save
199       saved_filename = "/home/osm/gpx/#{@trace.id}.gpx"
200       File.rename(filename, saved_filename)
201       logger.info("id is #{@trace.id}")
202       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."
203       render :nothing => true
204     else
205       render :nothing => true, :status => 400 # er FIXME what fricking code to return?
206     end
207
208   end
209 end