api06: Preliminary support for diff uploading. This will not return anything
authorGabriel Ebner <gabriel@svn.openstreetmap.org>
Sun, 4 May 2008 13:52:43 +0000 (13:52 +0000)
committerGabriel Ebner <gabriel@svn.openstreetmap.org>
Sun, 4 May 2008 13:52:43 +0000 (13:52 +0000)
useful and the code will need some heavy refactoring.

app/controllers/changeset_controller.rb
app/controllers/node_controller.rb
app/controllers/relation_controller.rb
app/controllers/way_controller.rb
app/models/node.rb
app/models/relation.rb
app/models/way.rb
config/routes.rb

index 1041c74..19d1591 100644 (file)
@@ -3,9 +3,10 @@
 class ChangesetController < ApplicationController
   require 'xml/libxml'
 
-  before_filter :authorize, :only => [:create, :update, :delete]
-  before_filter :check_write_availability, :only => [:create, :update, :delete]
-  before_filter :check_read_availability, :except => [:create, :update, :delete]
+  before_filter :authorize, :only => [:create, :update, :delete, :upload]
+  before_filter :check_write_availability, :only => [:create, :update, :delete, :upload]
+  before_filter :check_read_availability, :except => [:create, :update, :delete, :upload]
+  after_filter :compress_output
 
   # Create a changeset from XML.
   def create
@@ -23,4 +24,91 @@ class ChangesetController < ApplicationController
       render :nothing => true, :status => :method_not_allowed
     end
   end
+
+  def create_prim(ids, prim, nd)
+    prim.version = 0
+    prim.user_id = @user.id
+    prim.visible = true
+    prim.save_with_history!
+
+    ids[nd['id'].to_i] = prim.id
+  end
+
+  def fix_way(w, node_ids)
+    w.nds.each { |nd|
+      new_id = node_ids[nd.node_id]
+      nd.node_id = new_id unless new_id.nil?
+    }
+  end
+
+  def fix_rel(r, ids)
+    r.members.each { |memb|
+      new_id = ids[memb.member_type][memb.member_id]
+      nd.member_id = new_id unless new_id.nil?
+    }
+  end
+
+  def upload
+    if not request.put?
+      render :nothing => true, :status => :method_not_allowed
+      return
+    end
+
+    # FIXME: this should really be done without loading the whole XML file
+    # into memory.
+    p = XML::Parser.new
+    p.string  = request.raw_post
+    doc = p.parse
+
+    node_ids, way_ids, rel_ids = {}, {}, {}
+    ids = {"node"=>node_ids, "way"=>way_ids, "relation"=>rel_ids}
+
+    Changeset.transaction do
+      doc.find('//osm/create/node').each do |nd|
+       create_prim node_ids, Node.from_xml_node(nd, true), nd
+      end
+      doc.find('//osm/create/way').each do |nd|
+       way = Way.from_xml_node(nd, true)
+       raise OSM::APIPreconditionFailedError.new if !way.preconditions_ok?
+       create_prim way_ids, fix_way(way, node_ids), nd
+      end
+      doc.find('//osm/create/relation').each do |nd|
+       relation = Relation.from_xml_node(nd, true)
+       raise OSM::APIPreconditionFailedError.new if !way.preconditions_ok?
+       create_prim relation_ids, fix_rel(relation, ids), nd
+      end
+
+      doc.find('//osm/modify/node').each do |nd|
+       unless NodeController.update_internal nil, Node.from_xml_node(nd)
+         raise OSM::APIPreconditionFailedError.new
+       end
+      end
+      doc.find('//osm/modify/way').each do |nd|
+       unless WayController.update_internal nil, fix_way(Way.from_xml_node(nd), node_ids)
+         raise OSM::APIPreconditionFailedError.new
+       end
+      end
+      doc.find('//osm/modify/relation').each do |nd|
+       unless RelationController.update_internal nil, fix_rel(Relation.from_xml_node(nd), ids)
+         raise OSM::APIPreconditionFailedError.new
+       end
+      end
+
+      doc.find('//osm/delete/node').each do |nd|
+       unless NodeController.delete_internal nil, Node.from_xml_node(n)
+         raise OSM::APIPreconditionFailedError.new
+       end
+      end
+      doc.find('//osm/delete/way').each do |nd|
+       Way.from_xml_node(nd).delete_with_relations_and_history(@user)
+      end
+      doc.find('//osm/delete/relation').each do |nd|
+       unless RelationController.delete_internal nil, fix_rel(Relation.from_xml_node(nd), ids)
+         raise OSM::APIPreconditionFailedError.new
+       end
+      end
+    end
+
+    render :text => "Ok, Fine. Upload worked without errors.\n", :status => 200
+  end
 end
index 9f8f4a3..ae52450 100644 (file)
@@ -51,13 +51,7 @@ class NodeController < ApplicationController
       new_node = Node.from_xml(request.raw_post)
 
       if new_node and new_node.id == node.id
-        node.user_id = @user.id
-        node.latitude = new_node.latitude 
-        node.longitude = new_node.longitude
-        node.tags = new_node.tags
-        node.visible = true
-        node.save_with_history!
-
+       update_internal node, new_node
         render :nothing => true
       else
         render :nothing => true, :status => :bad_request
@@ -67,32 +61,54 @@ class NodeController < ApplicationController
     end
   end
 
+  def update_internal(node, new_node)
+    node = Node.find(new_node.id) if node.nil?
+
+    node.user_id = @user.id
+    node.latitude = new_node.latitude 
+    node.longitude = new_node.longitude
+    node.tags = new_node.tags
+    node.visible = true
+    node.save_with_history!
+
+    return true
+  end
+
   # Delete a node. Doesn't actually delete it, but retains its history in a wiki-like way.
   # FIXME remove all the fricking SQL
   def delete
     begin
       node = Node.find(params[:id])
 
-      if node.visible
-        if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", node.id ])
-          render :text => "", :status => :precondition_failed
-        elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", params[:id]])
-          render :text => "", :status => :precondition_failed
-        else
-          node.user_id = @user.id
-          node.visible = 0
-          node.save_with_history!
-
-          render :nothing => true
-        end
+      res = delete_internal(node)
+      unless res
+       render :text => "", :status => :precondition_failed
       else
-        render :text => "", :status => :gone
+       render :text => "", :status => res
       end
     rescue ActiveRecord::RecordNotFound
       render :nothing => true, :status => :not_found
     end
   end
 
+  def delete_internal(node)
+    if node.visible
+      if WayNode.find(:first, :joins => "INNER JOIN current_ways ON current_ways.id = current_way_nodes.id", :conditions => [ "current_ways.visible = 1 AND current_way_nodes.node_id = ?", node.id ])
+       return false
+      elsif RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='node' and member_id=?", params[:id]])
+       return false
+      else
+       node.user_id = @user.id
+       node.visible = 0
+       node.save_with_history!
+
+       return :ok
+      end
+    else
+      return :gone
+    end
+  end
+
   # WTF does this do?
   def nodes
     ids = params['nodes'].split(',').collect { |n| n.to_i }
index f4e9381..894ab40 100644 (file)
@@ -77,20 +77,11 @@ class RelationController < ApplicationController
     begin
       relation = Relation.find(params[:id])
 
-      if relation.visible
-        if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", params[:id]])
-          render :text => "", :status => :precondition_failed
-        else
-          relation.user_id = @user.id
-          relation.tags = []
-          relation.members = []
-          relation.visible = false
-          relation.save_with_history!
-
-          render :nothing => true
-        end
+      res = delete_internal(node)
+      unless res
+       render :text => "", :status => :precondition_failed
       else
-        render :text => "", :status => :gone
+       render :text => "", :status => res
       end
     rescue ActiveRecord::RecordNotFound
       render :nothing => true, :status => :not_found
@@ -99,6 +90,24 @@ class RelationController < ApplicationController
     end
   end
 
+  def delete_internal(relation)
+    if relation.visible
+      if RelationMember.find(:first, :joins => "INNER JOIN current_relations ON current_relations.id=current_relation_members.id", :conditions => [ "visible = 1 AND member_type='relation' and member_id=?", params[:id]])
+       return false
+      else
+       relation.user_id = @user.id
+       relation.tags = []
+       relation.members = []
+       relation.visible = false
+       relation.save_with_history!
+
+       return :ok
+      end
+    else
+      return :gone
+    end
+  end
+
   # -----------------------------------------------------------------
   # full
   # 
index a7f74e5..fd01008 100644 (file)
@@ -51,15 +51,9 @@ class WayController < ApplicationController
       new_way = Way.from_xml(request.raw_post)
 
       if new_way and new_way.id == way.id
-        if !new_way.preconditions_ok?
+        unless update_internal(way, new_way)
           render :text => "", :status => :precondition_failed
         else
-          way.user_id = @user.id
-          way.tags = new_way.tags
-          way.nds = new_way.nds
-          way.visible = true
-          way.save_with_history!
-
           render :nothing => true
         end
       else
@@ -70,6 +64,22 @@ class WayController < ApplicationController
     end
   end
 
+  def update_internal way, new_way
+    way = Way.find(new_way.id) if way.nil?
+
+    if !new_way.preconditions_ok?
+      return false
+    else
+      way.user_id = @user.id
+      way.tags = new_way.tags
+      way.nds = new_way.nds
+      way.visible = true
+      way.save_with_history!
+
+      return true
+    end
+  end
+
   # This is the API call to delete a way
   def delete
     begin
index 5e5e7a0..872c5c9 100644 (file)
@@ -55,40 +55,43 @@ class Node < GeoRecord
       p = XML::Parser.new
       p.string = xml
       doc = p.parse
-  
-      node = Node.new
 
       doc.find('//osm/node').each do |pt|
-        node.lat = pt['lat'].to_f
-        node.lon = pt['lon'].to_f
-
-        return nil unless node.in_world?
+       return Node.from_xml_node(pt, create)
+      end
+    rescue
+      return nil
+    end
+  end
 
-        unless create
-          if pt['id'] != '0'
-            node.id = pt['id'].to_i
-          end
-        end
+  def self.from_xml_node(pt, create=false)
+    node = Node.new
 
-        node.visible = pt['visible'] and pt['visible'] == 'true'
+    node.lat = pt['lat'].to_f
+    node.lon = pt['lon'].to_f
 
-        if create
-          node.timestamp = Time.now
-        else
-          if pt['timestamp']
-            node.timestamp = Time.parse(pt['timestamp'])
-          end
-        end
+    return nil unless node.in_world?
 
-        tags = []
+    unless create
+      if pt['id'] != '0'
+       node.id = pt['id'].to_i
+      end
+    end
 
-        pt.find('tag').each do |tag|
-          node.add_tag_key_val(tag['k'],tag['v'])
-        end
+    node.visible = pt['visible'] and pt['visible'] == 'true'
 
+    if create
+      node.timestamp = Time.now
+    else
+      if pt['timestamp']
+       node.timestamp = Time.parse(pt['timestamp'])
       end
-    rescue
-      node = nil
+    end
+
+    tags = []
+
+    pt.find('tag').each do |tag|
+      node.add_tag_key_val(tag['k'],tag['v'])
     end
 
     return node
index ec8c92c..2c155e9 100644 (file)
@@ -16,32 +16,38 @@ class Relation < ActiveRecord::Base
       p.string = xml
       doc = p.parse
 
-      relation = Relation.new
-
       doc.find('//osm/relation').each do |pt|
-        if !create and pt['id'] != '0'
-          relation.id = pt['id'].to_i
-        end
+       relation = Relation.from_xml_node pt, create
+      end
+    rescue
+      relation = nil
+    end
 
-        if create
-          relation.timestamp = Time.now
-          relation.visible = true
-        else
-          if pt['timestamp']
-            relation.timestamp = Time.parse(pt['timestamp'])
-          end
-        end
+    return relation
+  end
 
-        pt.find('tag').each do |tag|
-          relation.add_tag_keyval(tag['k'], tag['v'])
-        end
+  def self.from_xml_node(pt, create=false)
+    relation = Relation.new
 
-        pt.find('member').each do |member|
-          relation.add_member(member['type'], member['ref'], member['role'])
-        end
+    if !create and pt['id'] != '0'
+      relation.id = pt['id'].to_i
+    end
+
+    if create
+      relation.timestamp = Time.now
+      relation.visible = true
+    else
+      if pt['timestamp']
+       relation.timestamp = Time.parse(pt['timestamp'])
       end
-    rescue
-      relation = nil
+    end
+
+    pt.find('tag').each do |tag|
+      relation.add_tag_keyval(tag['k'], tag['v'])
+    end
+
+    pt.find('member').each do |member|
+      relation.add_member(member['type'], member['ref'], member['role'])
     end
 
     return relation
index 104c550..0c927c1 100644 (file)
@@ -17,32 +17,38 @@ class Way < ActiveRecord::Base
       p.string = xml
       doc = p.parse
 
-      way = Way.new
-
       doc.find('//osm/way').each do |pt|
-        if !create and pt['id'] != '0'
-          way.id = pt['id'].to_i
-        end
+       way = Way.from_xml_node pt, create
+      end
+    rescue
+      way = nil
+    end
 
-        if create
-          way.timestamp = Time.now
-          way.visible = true
-        else
-          if pt['timestamp']
-            way.timestamp = Time.parse(pt['timestamp'])
-          end
-        end
+    return way
+  end
 
-        pt.find('tag').each do |tag|
-          way.add_tag_keyval(tag['k'], tag['v'])
-        end
+  def self.from_xml_node(pt, create=false)
+    way = Way.new
 
-        pt.find('nd').each do |nd|
-          way.add_nd_num(nd['ref'])
-        end
+    if !create and pt['id'] != '0'
+      way.id = pt['id'].to_i
+    end
+
+    if create
+      way.timestamp = Time.now
+      way.visible = true
+    else
+      if pt['timestamp']
+       way.timestamp = Time.parse(pt['timestamp'])
       end
-    rescue
-      way = nil
+    end
+
+    pt.find('tag').each do |tag|
+      way.add_tag_keyval(tag['k'], tag['v'])
+    end
+
+    pt.find('nd').each do |nd|
+      way.add_nd_num(nd['ref'])
     end
 
     return way
index e4d68f8..64293b3 100644 (file)
@@ -2,6 +2,7 @@ ActionController::Routing::Routes.draw do |map|
 
   # API
   map.connect "api/#{API_VERSION}/changeset/create", :controller => 'changeset', :action => 'create'
+  map.connect "api/#{API_VERSION}/changeset/upload", :controller => 'changeset', :action => 'upload'
   
   map.connect "api/#{API_VERSION}/node/create", :controller => 'node', :action => 'create'
   map.connect "api/#{API_VERSION}/node/:id/ways", :controller => 'way', :action => 'ways_for_node', :id => /\d+/