Added tests for changeset upload code. Refactored diff reading code and put it into /lib. Changed the route of a changeset upload to explicitly refer to the changeset it applies to (i.e: resource).
This commit is contained in:
parent
136c5a7bf1
commit
dc2a959037
8 changed files with 596 additions and 96 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
class ChangesetController < ApplicationController
|
||||
require 'xml/libxml'
|
||||
require 'diff_reader'
|
||||
|
||||
before_filter :authorize, :only => [:create, :update, :delete, :upload]
|
||||
before_filter :check_write_availability, :only => [:create, :update, :delete, :upload]
|
||||
|
@ -70,102 +71,37 @@ class ChangesetController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Upload a diff in a single transaction.
|
||||
#
|
||||
# This means that each change within the diff must succeed, i.e: that
|
||||
# each version number mentioned is still current. Otherwise the entire
|
||||
# transaction *must* be rolled back.
|
||||
#
|
||||
# Furthermore, each element in the diff can only reference the current
|
||||
# changeset.
|
||||
#
|
||||
# Returns: ??? the new document? updated diffs?
|
||||
def upload
|
||||
unless request.put?
|
||||
# only allow POST requests, as the upload method is most definitely
|
||||
# not idempotent, as several uploads with placeholder IDs will have
|
||||
# different side-effects.
|
||||
# see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.2
|
||||
unless request.post?
|
||||
render :nothing => true, :status => :method_not_allowed
|
||||
return
|
||||
end
|
||||
|
||||
p = XML::Reader.new request.raw_post
|
||||
|
||||
node_ids, way_ids, rel_ids = {}, {}, {}
|
||||
ids = {"node"=>node_ids, "way"=>way_ids, "relation"=>rel_ids}
|
||||
|
||||
models = {"node"=>Node, "way"=>Way, "relation"=>Relation}
|
||||
|
||||
# FIXME shouldn't this be done through the
|
||||
# res = OSM::API.new.get_xml_doc
|
||||
# as everything else is?
|
||||
res = XML::Document.new
|
||||
res.encoding = 'UTF-8'
|
||||
root = XML::Node.new 'osm'
|
||||
root['version'] = API_VERSION
|
||||
root['creator'] = 'OpenStreetMap.org'
|
||||
res.root = root
|
||||
|
||||
root << XML::Node.new_comment(" Warning: this is a 0.6 result document, " +
|
||||
"not a normal OSM file. ")
|
||||
|
||||
changeset = Changeset.find(params[:id])
|
||||
|
||||
diff_reader = DiffReader.new(request.raw_post, changeset)
|
||||
Changeset.transaction do
|
||||
while p.read == 1
|
||||
break if p.node_type == 15 # end element
|
||||
next unless p.node_type == 1 # element
|
||||
|
||||
case p.name
|
||||
when 'create':
|
||||
while p.read == 1
|
||||
break if p.node_type == 15 # end element
|
||||
next unless p.node_type == 1 # element
|
||||
|
||||
model = models[p.name]
|
||||
next if model.nil?
|
||||
|
||||
elem = XML::Node.new p.name
|
||||
nd = p.expand; p.next
|
||||
osm = model.from_xml_node(nd, true)
|
||||
elem['old_id'] = nd['id']
|
||||
|
||||
case nd.name
|
||||
when 'way':
|
||||
fix_way(osm, node_ids)
|
||||
raise OSM::APIPreconditionFailedError.new if !osm.preconditions_ok?
|
||||
when 'relation':
|
||||
fix_rel(osm, ids)
|
||||
raise OSM::APIPreconditionFailedError.new if !osm.preconditions_ok?
|
||||
end
|
||||
|
||||
create_prim ids[nd.name], osm, nd
|
||||
elem['new_id'] = osm.id.to_s
|
||||
elem['new_version'] = osm.version.to_s
|
||||
root << elem
|
||||
end
|
||||
when 'modify':
|
||||
while p.read == 1
|
||||
break if p.node_type == 15 # end element
|
||||
next unless p.node_type == 1 # element
|
||||
|
||||
model = models[p.name]
|
||||
next if model.nil?
|
||||
|
||||
elem = XML::Node.new p.name
|
||||
new_osm = model.from_xml_node(p.expand); p.next
|
||||
osm = model.find(new_osm.id)
|
||||
osm.update_from new_osm, @user
|
||||
elem['old_id'] = elem['new_id'] = osm.id.to_s
|
||||
elem['new_version'] = osm.version.to_s
|
||||
root << elem
|
||||
end
|
||||
when 'delete':
|
||||
while p.read == 1
|
||||
break if p.node_type == 15 # end element
|
||||
next unless p.node_type == 1 # element
|
||||
|
||||
model = models[p.name]
|
||||
next if model.nil?
|
||||
|
||||
elem = XML::Node.new p.name
|
||||
osm = model.find(p.expand['id']); p.next
|
||||
osm.delete_with_history(@user)
|
||||
elem['old_id'] = elem['new_id'] = osm.id.to_s
|
||||
elem['new_version'] = osm.version.to_s
|
||||
root << elem
|
||||
end
|
||||
end
|
||||
end
|
||||
result = diff_reader.commit
|
||||
render :text => result.to_s, :content_type => "text/xml"
|
||||
end
|
||||
|
||||
render :text => res.to_s, :content_type => "text/xml"
|
||||
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :nothing => true, :status => :not_found
|
||||
rescue OSM::APIError => ex
|
||||
render ex.render_opts
|
||||
end
|
||||
|
|
|
@ -75,20 +75,25 @@ class Node < ActiveRecord::Base
|
|||
def self.from_xml_node(pt, create=false)
|
||||
node = Node.new
|
||||
|
||||
node.version = pt['version'].to_i
|
||||
node.lat = pt['lat'].to_f
|
||||
node.lon = pt['lon'].to_f
|
||||
node.changeset_id = pt['changeset'].to_i
|
||||
|
||||
return nil unless node.in_world?
|
||||
|
||||
# version must be present unless creating
|
||||
return nil unless create or not pt['version'].nil?
|
||||
node.version = pt['version'].to_i
|
||||
|
||||
unless create
|
||||
if pt['id'] != '0'
|
||||
node.id = pt['id'].to_i
|
||||
end
|
||||
end
|
||||
|
||||
node.visible = pt['visible'] and pt['visible'] == 'true'
|
||||
# visible if it says it is, or as the default if the attribute
|
||||
# is missing.
|
||||
node.visible = pt['visible'].nil? or pt['visible'] == 'true'
|
||||
|
||||
if create
|
||||
node.timestamp = Time.now
|
||||
|
@ -235,4 +240,11 @@ class Node < ActiveRecord::Base
|
|||
@tags[k] = v
|
||||
end
|
||||
|
||||
##
|
||||
# dummy method to make the interfaces of node, way and relation
|
||||
# more consistent.
|
||||
def fix_placeholders!(id_map)
|
||||
# nodes don't refer to anything, so there is nothing to do here
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -320,4 +320,22 @@ class Relation < ActiveRecord::Base
|
|||
def tags_as_hash
|
||||
return self.tags
|
||||
end
|
||||
|
||||
##
|
||||
# if any members are referenced by placeholder IDs (i.e: negative) then
|
||||
# this calling this method will fix them using the map from placeholders
|
||||
# to IDs +id_map+.
|
||||
def fix_placeholders!(id_map)
|
||||
self.members.map! do |type, id, role|
|
||||
old_id = id.to_i
|
||||
if old_id < 0
|
||||
new_id = id_map[type.to_sym][old_id]
|
||||
raise "invalid placeholder" if new_id.nil?
|
||||
[type, new_id, role]
|
||||
else
|
||||
[type, id, role]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -299,4 +299,21 @@ class Way < ActiveRecord::Base
|
|||
def tags_as_hash
|
||||
return self.tags
|
||||
end
|
||||
|
||||
##
|
||||
# if any referenced nodes are placeholder IDs (i.e: are negative) then
|
||||
# this calling this method will fix them using the map from placeholders
|
||||
# to IDs +id_map+.
|
||||
def fix_placeholders!(id_map)
|
||||
self.nds.map! do |node_id|
|
||||
if node_id < 0
|
||||
new_id = id_map[:node][node_id]
|
||||
raise "invalid placeholder for #{node_id.inspect}: #{new_id.inspect}" if new_id.nil?
|
||||
new_id
|
||||
else
|
||||
node_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue