Improve reporting of errors in GPX uploads, and allow private traces
[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.public? or (@user and @user == trace.user)
124       send_data(File.open("/home/osm/gpx/#{trace.id}.gpx",'r').read , :filename => "#{trace.id}.gpx", :type => 'text/plain', :disposition => 'inline')
125     end
126   end
127
128   def make_public
129     trace = Trace.find(params[:id])
130     if @user and trace.user == @user and !trace.public
131       trace.public = true
132       trace.save
133       flash[:notice] = 'Track made public'
134       redirect_to :controller => 'trace', :action => 'view', :id => params[:id]
135     end
136   end
137
138   def georss
139     traces = Trace.find(:all, :conditions => ['public = true'], :order => 'timestamp DESC', :limit => 20)
140
141     rss = OSM::GeoRSS.new
142
143     #def add(latitude=0, longitude=0, title_text='dummy title', url='http://www.example.com/', description_text='dummy description', timestamp=Time.now)
144     traces.each do |trace|
145       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)
146     end
147
148     response.headers["Content-Type"] = 'application/xml+rss'
149
150     render :text => rss.to_s
151   end
152
153   def picture
154     trace = Trace.find(params[:id])
155     send_data(trace.large_picture, :filename => "#{trace.id}.gif", :type => 'image/gif', :disposition => 'inline') if trace.public? or (@user and @user == trace.user)
156   end
157
158   def icon
159     trace = Trace.find(params[:id])
160     send_data(trace.icon_picture, :filename => "#{trace.id}_icon.gif", :type => 'image/gif', :disposition => 'inline') if trace.public? or (@user and @user == trace.user)
161   end
162
163   def api_details
164     trace = Trace.find(params[:id])
165     doc = OSM::API.new.get_xml_doc
166     doc.root << trace.to_xml_node() if trace.public? or trace.user == @user
167     render :text => doc.to_s
168   end
169
170   def api_data
171     render :action => 'data'
172   end
173
174   def api_create
175     #FIXME merge this code with create as they're pretty similar?
176     
177     filename = "/tmp/#{rand}"
178     File.open(filename, "w") { |f| f.write(request.raw_post) }
179     params[:trace] = {}
180     params[:trace][:name] = params[:filename]
181     params[:trace][:tagstring] = params[:tags]
182     params[:trace][:description] = params[:description]
183     @trace = Trace.new(params[:trace])
184     @trace.inserted = false
185     @trace.user = @user
186     @trace.timestamp = Time.now
187
188     if @trace.save
189       saved_filename = "/home/osm/gpx/#{@trace.id}.gpx"
190       File.rename(filename, saved_filename)
191       logger.info("id is #{@trace.id}")
192       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."
193       render :nothing => true
194     else
195       render :nothing => true, :status => 400 # er FIXME what fricking code to return?
196     end
197
198   end
199 end