+##
+# builds an XML representation of a changeset from the database
+class ChangesetBuilder
+ def initialize(now, conn)
+ @now = now
+ @conn = conn
+ end
+
+ def changeset_xml(cs)
+ xml = XML::Node.new("changeset")
+ xml["id"] = cs.id.to_s
+ xml["created_at"] = cs.created_at.getutc.xmlschema
+ xml["closed_at"] = cs.closed_at.getutc.xmlschema if cs.closed?(@now)
+ xml["open"] = cs.open?(@now).to_s
+ xml["num_changes"] = cs.num_changes.to_s
+
+ res = @conn.exec("select u.id, u.display_name, c.min_lat, c.max_lat, c.min_lon, c.max_lon from users u join changesets c on u.id=c.user_id where c.id=#{cs.id}")
+ xml["user"] = xml_sanitize(res[0]["display_name"])
+ xml["uid"] = res[0]["id"]
+
+ unless res[0]["min_lat"].nil? ||
+ res[0]["max_lat"].nil? ||
+ res[0]["min_lon"].nil? ||
+ res[0]["max_lon"].nil?
+ xml["min_lat"] = (res[0]["min_lat"].to_f / GEO_SCALE).to_s
+ xml["max_lat"] = (res[0]["max_lat"].to_f / GEO_SCALE).to_s
+ xml["min_lon"] = (res[0]["min_lon"].to_f / GEO_SCALE).to_s
+ xml["max_lon"] = (res[0]["max_lon"].to_f / GEO_SCALE).to_s
+ end
+
+ add_tags(xml, cs)
+ add_comments(xml, cs)
+
+ xml
+ end
+
+ def add_tags(xml, cs)
+ res = @conn.exec("select k, v from changeset_tags where changeset_id=#{cs.id}")
+ res.each do |row|
+ tag = XML::Node.new("tag")
+ tag["k"] = xml_sanitize(row["k"])
+ tag["v"] = xml_sanitize(row["v"])
+ xml << tag
+ end
+ end
+
+ def add_comments(xml, cs)
+ # grab the visible changeset comments as well
+ res = @conn.exec("select cc.author_id, u.display_name as author, cc.body, cc.created_at from changeset_comments cc join users u on cc.author_id=u.id where cc.changeset_id=#{cs.id} and cc.visible order by cc.created_at asc")
+ xml["comments_count"] = res.num_tuples.to_s
+
+ # early return if there aren't any comments
+ return unless res.num_tuples.positive?
+
+ discussion = XML::Node.new("discussion")
+ res.each do |row|
+ comment = XML::Node.new("comment")
+ comment["uid"] = row["author_id"]
+ comment["user"] = xml_sanitize(row["author"])
+ comment["date"] = Time.parse(row["created_at"]).getutc.xmlschema
+ text = XML::Node.new("text")
+ text.content = xml_sanitize(row["body"])
+ comment << text
+ discussion << comment
+ end
+ xml << discussion
+ end
+end
+
+##
+# sync a file to guarantee it's on disk
+def fsync(f)
+ File.open(f, &:fsync)
+end
+
+##
+# sync a directory to guarantee it's on disk. have to recurse to the root
+# to guarantee sync for newly created directories.
+def fdirsync(d)
+ while d != "/"
+ Dir.open(d) do |dh|
+ io = IO.for_fd(dh.fileno)
+ io.fsync
+ end
+ d = File.dirname(d)
+ end
+end
+