UNPKG

osm-p2p-server

Version:

Peer-to-peer OpenStreetMap API v0.6 Server

144 lines (133 loc) 4.96 kB
// TODO: We do not consider forks and joins here: We need to var errors = require('../errors') module.exports = function filterSafeDeletes (osm, batch, cb) { // A mapping of ids of ways and relations to // the ids of elements that they reference var pendingRefs = {} // A list of all references to each element within the changeset itself // It seems possible that a changeset could modify an element to reference // an element that was not previously referenced, and is then deleted // later in the changeset. This logic checks for that var reverseRefs = {} var filtered = [] var inUseErrors = [] var pending = batch.length batch.forEach(function (change) { var id = change.id if (change.action !== 'delete' && change.type !== 'node') { // Remove previous reverse refs to this element ;(pendingRefs[id] || []).forEach(function (ref) { reverseRefs[ref] = (reverseRefs[ref] || []).filter(function (ref) { return id !== ref }) }) // If a way or relation is modified, keep a list of the elements // they reference, since this overrides what is in the db if (change.type === 'way') { pendingRefs[id] = change.nodes || [] } else if (change.type === 'relation') { pendingRefs[id] = change.members ? change.members.map(function (m) { return m.ref }) : [] } // Add new reverse refs ;(pendingRefs[id] || []).forEach(function (ref) { reverseRefs[ref] = reverseRefs[ref] || [] reverseRefs[ref].push(id) }) } // If action is not delete it is always included if (change.action !== 'delete') { filtered.push(change) return onCheck() } /** We only get here for change.action === 'delete' */ // If a way or relation that appeared in the create or modified block // is subsequently deleted, we can remove the references to it if (change.type !== 'node') { // Remove reverse refs to this element ;(pendingRefs[id] || []).forEach(function (ref) { reverseRefs[ref] = (reverseRefs[ref] || []).filter(function (ref) { return id !== ref }) }) pendingRefs[id] = [] } // Check whether the element to be deleted is referenced by any // existing ways or relations in the database refList(osm, id, function (err, refs) { if (err) return cb(err) // Update the refs from the database with pending refs from the changeset var mergedRefs = refs.filter(function (ref) { // If the element is referenced in the database by an element // that is earlier in the changeset, what is important is // whether the new version of the element still references it if (pendingRefs[ref.value]) { return pendingRefs[ref.value].indexOf(id) > -1 } else { return true } }) if (mergedRefs.length || reverseRefs[id] && reverseRefs[id].length) { // If there are any references in mergedRefs, then element is in use // and cannot be deleted without ... if (change.ifUnused) { // If the ifUnused prop is set, silently skip and continue onCheck() } else { refs.forEach(function (ref) { inUseErrors.push({id: id, usedBy: ref.value}) }) onCheck() } } else { // If the element is not referenced anywhere, we can safely delete it filtered.push(change) onCheck() } }) }) function onCheck () { if (--pending > 0) return if (!inUseErrors.length) return cb(null, filtered) var msg = '' inUseErrors.forEach(function (inUse) { msg += 'Element #' + inUse.id + ' is still used by element #' + inUse.usedBy + '.' }) cb(new errors.InUse(msg)) } } // work-around to ensure that reference counts are accurate for ifUnused calculation // TODO: Why do we need this? function refList (osm, id, cb) { var res = [] osm.refs.list(id, function (err, refs) { if (err) return cb(err) var pending = 1 refs.forEach(function (r) { var ref = r.value pending++ osm.get(ref, function (err, docs) { if (err && !notFound(err)) return cb(err) var contained = false Object.keys(docs || {}).forEach(function (key) { var d = docs[key] if (!d) return if (d.refs && d.refs.indexOf(id) >= 0) { contained = true } else if (d.nodes && d.nodes.indexOf(id) >= 0) { contained = true } else if (d.members && d.members.map(refid).indexOf(id) >= 0) { contained = true } }) if (contained) res.push(r) if (--pending === 0) cb(null, res) }) }) if (--pending === 0) cb(null, res) }) } function refid (m) { return m.ref || m.id } function notFound (err) { return err && (/^notfound/i.test(err) || err.notFound) }