X-Git-Url: https://git.openstreetmap.org/rails.git/blobdiff_plain/59c69a642ff64111caf8ff4c1dd6e25e7d430b4e..0f5646d7f0e0ccaf3e7b5fd36c9ac4047d5df210:/app/models/changeset.rb diff --git a/app/models/changeset.rb b/app/models/changeset.rb index 3e0ba9f8e..8e51cead2 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -27,15 +27,11 @@ class Changeset < ActiveRecord::Base # maximum number of elements allowed in a changeset MAX_ELEMENTS = 50000 - # maximum time a changeset is allowed to be open for (note that this - # is in days - so one hour is Rational(1,24)). - MAX_TIME_OPEN = 1 + # maximum time a changeset is allowed to be open for. + MAX_TIME_OPEN = 1.day - # idle timeout increment, one hour as a rational number of days. - # NOTE: DO NOT CHANGE THIS TO 1.hour! when this was done the idle - # timeout changed to 1 second, which meant all changesets closed - # almost immediately. - IDLE_TIMEOUT = Rational(1,24) + # idle timeout increment, one hour seems reasonable. + IDLE_TIMEOUT = 1.hour # Use a method like this, so that we can easily change how we # determine whether a changeset is open, without breaking code in at @@ -46,41 +42,52 @@ class Changeset < ActiveRecord::Base # note that this may not be a hard limit - due to timing changes and # concurrency it is possible that some changesets may be slightly # longer than strictly allowed or have slightly more changes in them. - return ((closed_at > DateTime.now) and (num_changes <= MAX_ELEMENTS)) + return ((closed_at > Time.now.getutc) and (num_changes <= MAX_ELEMENTS)) end def set_closed_time_now - unless is_open? - self.closed_at = DateTime.now + if is_open? + self.closed_at = Time.now.getutc end end def self.from_xml(xml, create=false) begin - p = XML::Parser.new - p.string = xml + p = XML::Parser.string(xml) doc = p.parse - cs = Changeset.new - doc.find('//osm/changeset').each do |pt| - if create - cs.created_at = Time.now - # initial close time is 1h ahead, but will be increased on each - # modification. - cs.closed_at = Time.now + IDLE_TIMEOUT - # initially we have no changes in a changeset - cs.num_changes = 0 - end - - pt.find('tag').each do |tag| - cs.add_tag_keyval(tag['k'], tag['v']) - end + return Changeset.from_xml_node(pt, create) end - rescue Exception => ex - cs = nil + raise OSM::APIBadXMLError.new("changeset", xml, "XML doesn't contain an osm/changeset element.") + rescue LibXML::XML::Error, ArgumentError => ex + raise OSM::APIBadXMLError.new("changeset", xml, ex.message) + end + end + + def self.from_xml_node(pt, create=false) + cs = Changeset.new + if create + cs.created_at = Time.now.getutc + # initial close time is 1h ahead, but will be increased on each + # modification. + cs.closed_at = cs.created_at + IDLE_TIMEOUT + # initially we have no changes in a changeset + cs.num_changes = 0 + else + raise OSM::APIBadXMLError.new("changeset", pt, "ID is required when updating.") if pt['id'].nil? + cs.id = pt['id'].to_i + # .to_i will return 0 if there is no number that can be parsed. + # We want to make sure that there is no id with zero anyway. + raise OSM::APIBadUserInput.new("ID of changeset cannot be zero when updating.") if cs.id == 0 end + pt.find('tag').each do |tag| + raise OSM::APIBadXMLError.new("changeset", pt, "tag is missing key") if tag['k'].nil? + raise OSM::APIBadXMLError.new("changeset", pt, "tag is missing value") if tag['v'].nil? + cs.add_tag_keyval(tag['k'], tag['v']) + end + return cs end @@ -90,6 +97,20 @@ class Changeset < ActiveRecord::Base def bbox @bbox ||= [ min_lon, min_lat, max_lon, max_lat ] end + + def has_valid_bbox? + not bbox.include? nil + end + + ## + # returns area of the changset bbox as a rough comparitive quantity for use of changset displays + def area + if has_valid_bbox? + (max_lon - min_lon) * (max_lat - min_lat) + else + 0 + end + end ## # expand the bounding box to include the given bounding box. also, @@ -103,10 +124,10 @@ class Changeset < ActiveRecord::Base # FIXME - this looks nasty and violates DRY... is there any prettier # way to do this? - @bbox[0] = array[0] + EXPAND * (@bbox[0] - @bbox[2]) if array[0] < @bbox[0] - @bbox[1] = array[1] + EXPAND * (@bbox[1] - @bbox[3]) if array[1] < @bbox[1] - @bbox[2] = array[2] + EXPAND * (@bbox[2] - @bbox[0]) if array[2] > @bbox[2] - @bbox[3] = array[3] + EXPAND * (@bbox[3] - @bbox[1]) if array[3] > @bbox[3] + @bbox[0] = [-180 * GeoRecord::SCALE, array[0] + EXPAND * (@bbox[0] - @bbox[2])].max if array[0] < @bbox[0] + @bbox[1] = [ -90 * GeoRecord::SCALE, array[1] + EXPAND * (@bbox[1] - @bbox[3])].max if array[1] < @bbox[1] + @bbox[2] = [ 180 * GeoRecord::SCALE, array[2] + EXPAND * (@bbox[2] - @bbox[0])].min if array[2] > @bbox[2] + @bbox[3] = [ 90 * GeoRecord::SCALE, array[3] + EXPAND * (@bbox[3] - @bbox[1])].min if array[3] > @bbox[3] # update active record. rails 2.1's dirty handling should take care of # whether this object needs saving or not. @@ -141,23 +162,18 @@ class Changeset < ActiveRecord::Base def add_tag_keyval(k, v) @tags = Hash.new unless @tags + + # duplicate tags are now forbidden, so we can't allow values + # in the hash to be overwritten. + raise OSM::APIDuplicateTagsError.new("changeset", self.id, k) if @tags.include? k + @tags[k] = v end def save_with_tags! - t = Time.now - # do the changeset update and the changeset tags update in the # same transaction to ensure consistency. Changeset.transaction do - # set the auto-close time to be one hour in the future unless - # that would make it more than 24h long, in which case clip to - # 24h, as this has been decided is a reasonable time limit. - if (closed_at - created_at) > (MAX_TIME_OPEN - IDLE_TIMEOUT) - self.closed_at = created_at + MAX_TIME_OPEN - else - self.closed_at = DateTime.now + IDLE_TIMEOUT - end self.save! tags = self.tags @@ -172,6 +188,20 @@ class Changeset < ActiveRecord::Base end end end + + ## + # set the auto-close time to be one hour in the future unless + # that would make it more than 24h long, in which case clip to + # 24h, as this has been decided is a reasonable time limit. + def before_save + if self.is_open? + if (closed_at - created_at) > (MAX_TIME_OPEN - IDLE_TIMEOUT) + self.closed_at = created_at + MAX_TIME_OPEN + else + self.closed_at = Time.now.getutc + IDLE_TIMEOUT + end + end + end def to_xml doc = OSM::API.new.get_xml_doc @@ -226,7 +256,7 @@ class Changeset < ActiveRecord::Base def update_from(other, user) # ensure that only the user who opened the changeset may modify it. unless user.id == self.user_id - raise OSM::APIUserChangesetMismatchError + raise OSM::APIUserChangesetMismatchError.new end # can't change a closed changeset