X-Git-Url: https://git.openstreetmap.org./rails.git/blobdiff_plain/64fb530581ffd21e2522e70e8a2d4c46d666f760..ce761b3193c41081182e129d4f4aa18268e69264:/lib/potlatch.rb diff --git a/lib/potlatch.rb b/lib/potlatch.rb index e8de1aa8e..49e1e7a74 100644 --- a/lib/potlatch.rb +++ b/lib/potlatch.rb @@ -1,222 +1,263 @@ +require "stringio" + # The Potlatch module provides helper functions for potlatch and its communication with the server module Potlatch - # The AMF class is a set of helper functions for encoding and decoding AMF. class AMF - # Return two-byte integer - def self.getint(s) - s.getc*256+s.getc + def self.getint(s) + s.getbyte * 256 + s.getbyte end # Return four-byte long - def self.getlong(s) - ((s.getc*256+s.getc)*256+s.getc)*256+s.getc + def self.getlong(s) + ((s.getbyte * 256 + s.getbyte) * 256 + s.getbyte) * 256 + s.getbyte end - # Return string with two-byte length - def self.getstring(s) - len=s.getc*256+s.getc - s.read(len) + # Return string with two-byte length + def self.getstring(s) + len = s.getbyte * 256 + s.getbyte + str = s.read(len) + str.force_encoding("UTF-8") if str.respond_to?("force_encoding") + str end - # Return eight-byte double-precision float - def self.getdouble(s) - a=s.read(8).unpack('G') # G big-endian, E little-endian + # Return eight-byte double-precision float + def self.getdouble(s) + a = s.read(8).unpack("G") # G big-endian, E little-endian a[0] end # Return numeric array - def self.getarray(s) - len=getlong(s) - arr=[] - for i in (0..len-1) - arr[i]=getvalue(s) - end - arr + def self.getarray(s) + Array.new(getlong(s)) { getvalue(s) } end - # Return object/hash - def self.getobject(s) - arr={} - while (key=getstring(s)) - if (key=='') then break end - arr[key]=getvalue(s) + # Return object/hash + def self.getobject(s) + arr = {} + while (key = getstring(s)) + break if key == "" + + arr[key] = getvalue(s) end - s.getc # skip the 9 'end of object' value + s.getbyte # skip the 9 'end of object' value arr end # Parse and get value - def self.getvalue(s) - case s.getc - when 0; return getdouble(s) # number - when 1; return s.getc # boolean - when 2; return getstring(s) # string - when 3; return getobject(s) # object/hash - when 5; return nil # null - when 6; return nil # undefined - when 8; s.read(4) # mixedArray - return getobject(s) # | - when 10;return getarray(s) # array - else; return nil # error + def self.getvalue(s) + case s.getbyte + when 0 then getdouble(s) # number + when 1 then s.getbyte # boolean + when 2 then getstring(s) # string + when 3 then getobject(s) # object/hash + when 5 then nil # null + when 6 then nil # undefined + when 8 then s.read(4) # mixedArray + getobject(s) # | + when 10 then getarray(s) # array end end # Envelope data into AMF writeable form - def self.putdata(index,n) - d =encodestring(index+"/onResult") - d+=encodestring("null") - d+=[-1].pack("N") - d+=encodevalue(n) + def self.putdata(index, n) + d = encodestring(index + "/onResult") + d += encodestring("null") + d += [-1].pack("N") + d += encodevalue(n) + d end # Pack variables as AMF - def self.encodevalue(n) - case n.class.to_s - when 'Array' - a=10.chr+encodelong(n.length) + def self.encodevalue(n) + case n + when Array + a = 10.chr + encodelong(n.length) n.each do |b| - a+=encodevalue(b) + a += encodevalue(b) end a - when 'Hash' - a=3.chr - n.each do |k,v| - a+=encodestring(k.to_s)+encodevalue(v) + when Hash + a = 3.chr + n.each do |k, v| + a += encodestring(k.to_s) + encodevalue(v) end - a+0.chr+0.chr+9.chr - when 'String' - 2.chr+encodestring(n) - when 'Bignum','Fixnum','Float' - 0.chr+encodedouble(n) - when 'NilClass' + a + 0.chr + 0.chr + 9.chr + when String + 2.chr + encodestring(n) + when Numeric, GeoRecord::Coord + 0.chr + encodedouble(n) + when NilClass 5.chr - when 'TrueClass' - 0.chr+encodedouble(1) - when 'FalseClass' - 0.chr+encodedouble(0) + when TrueClass + 0.chr + encodedouble(1) + when FalseClass + 0.chr + encodedouble(0) else - RAILS_DEFAULT_LOGGER.error("Unexpected Ruby type for AMF conversion: "+n.class.to_s) + raise "Unexpected Ruby type for AMF conversion: #{n.class.name}" end end # Encode string with two-byte length - def self.encodestring(n) - a,b=n.size.divmod(256) - a.chr+b.chr+n + def self.encodestring(n) + n = n.dup.force_encoding("ASCII-8BIT") if n.respond_to?("force_encoding") + a, b = n.size.divmod(256) + a.chr + b.chr + n end - # Encode number as eight-byte double precision float - def self.encodedouble(n) - [n].pack('G') + # Encode number as eight-byte double precision float + def self.encodedouble(n) + [n.to_f].pack("G") end # Encode number as four-byte long - def self.encodelong(n) - [n].pack('N') + def self.encodelong(n) + [n].pack("N") end - end + # The Dispatcher class handles decoding a series of RPC calls + # from the request, dispatching them, and encoding the response + class Dispatcher + def initialize(request, &_block) + # Get stream for request data + @request = StringIO.new(request + 0.chr) + + # Skip version indicator and client ID + @request.read(2) + + # Skip headers + AMF.getint(@request).times do # Read number of headers and loop + AMF.getstring(@request) # | skip name + req.getbyte # | skip boolean + AMF.getvalue(@request) # | skip value + end + + # Capture the dispatch routine + @dispatch = Proc.new + end + + def each(&_block) + # Read number of message bodies + bodies = AMF.getint(@request) + + # Output response header + a, b = bodies.divmod(256) + yield 0.chr + 0.chr + 0.chr + 0.chr + a.chr + b.chr + + # Process the bodies + bodies.times do # Read each body + name = AMF.getstring(@request) # | get message name + index = AMF.getstring(@request) # | get index in response sequence + AMF.getlong(@request) # | get total size in bytes + args = AMF.getvalue(@request) # | get response (probably an array) + + result = @dispatch.call(name, *args) + + yield AMF.putdata(index, result) + end + end + end # The Potlatch class is a helper for Potlatch class Potlatch - # ----- getpresets - # in: none - # does: reads tag preset menus, colours, and autocomplete config files - # out: [0] presets, [1] presetmenus, [2] presetnames, - # [3] colours, [4] casing, [5] areas, [6] autotags - # (all hashes) + # in: none + # does: reads tag preset menus, colours, and autocomplete config files + # out: [0] presets, [1] presetmenus, [2] presetnames, + # [3] colours, [4] casing, [5] areas, [6] autotags + # (all hashes) def self.get_presets - RAILS_DEFAULT_LOGGER.info(" Message: getpresets") + Rails.logger.info(" Message: getpresets") # Read preset menus - presets={} - presetmenus={}; presetmenus['point']=[]; presetmenus['way']=[]; presetmenus['POI']=[] - presetnames={}; presetnames['point']={}; presetnames['way']={}; presetnames['POI']={} - presettype='' - presetcategory='' - # StringIO.open(txt) do |file| - File.open("#{RAILS_ROOT}/config/potlatch/presets.txt") do |file| - file.each_line {|line| - t=line.chomp - if (t=~/(\w+)\/(\w+)/) then - presettype=$1 - presetcategory=$2 + presets = {} + presetmenus = { "point" => [], "way" => [], "POI" => [] } + presetnames = { "point" => {}, "way" => {}, "POI" => {} } + presettype = "" + presetcategory = "" + # StringIO.open(txt) do |file| + File.open(Rails.root.join("config", "potlatch", "presets.txt")) do |file| + file.each_line do |line| + t = line.chomp + if t =~ %r{(\w+)/(\w+)} + presettype = Regexp.last_match(1) + presetcategory = Regexp.last_match(2) presetmenus[presettype].push(presetcategory) - presetnames[presettype][presetcategory]=["(no preset)"] - elsif (t=~/^([\w\s]+):\s?(.+)$/) then - pre=$1; kv=$2 + presetnames[presettype][presetcategory] = ["(no preset)"] + elsif t =~ /^([\w\s]+):\s?(.+)$/ + pre = Regexp.last_match(1) + kv = Regexp.last_match(2) presetnames[presettype][presetcategory].push(pre) - presets[pre]={} - kv.split(',').each {|a| - if (a=~/^(.+)=(.*)$/) then presets[pre][$1]=$2 end - } + presets[pre] = {} + kv.split(",").each do |a| + presets[pre][Regexp.last_match(1)] = Regexp.last_match(2) if a =~ /^(.+)=(.*)$/ + end end - } + end end # Read colours/styling - colours={}; casing={}; areas={} - File.open("#{RAILS_ROOT}/config/potlatch/colours.txt") do |file| - file.each_line {|line| - t=line.chomp - if (t=~/(\w+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/) then - tag=$1 - if ($2!='-') then colours[tag]=$2.hex end - if ($3!='-') then casing[tag]=$3.hex end - if ($4!='-') then areas[tag]=$4.hex end - end - } + colours = {} + casing = {} + areas = {} + File.open(Rails.root.join("config", "potlatch", "colours.txt")) do |file| + file.each_line do |line| + next unless line.chomp =~ /(\w+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/ + + tag = Regexp.last_match(1) + colours[tag] = Regexp.last_match(2).hex if Regexp.last_match(2) != "-" + casing[tag] = Regexp.last_match(3).hex if Regexp.last_match(3) != "-" + areas[tag] = Regexp.last_match(4).hex if Regexp.last_match(4) != "-" + end end # Read relations colours/styling - relcolours={}; relalphas={}; relwidths={} - File.open("#{RAILS_ROOT}/config/potlatch/relation_colours.txt") do |file| - file.each_line {|line| - t=line.chomp - if (t=~/(\w+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/) then - tag=$1 - if ($2!='-') then relcolours[tag]=$2.hex end - if ($3!='-') then relalphas[tag]=$3.to_i end - if ($4!='-') then relwidths[tag]=$4.to_i end - end - } + relcolours = {} + relalphas = {} + relwidths = {} + File.open(Rails.root.join("config", "potlatch", "relation_colours.txt")) do |file| + file.each_line do |line| + next unless line.chomp =~ /(\w+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/ + + tag = Regexp.last_match(1) + relcolours[tag] = Regexp.last_match(2).hex if Regexp.last_match(2) != "-" + relalphas[tag] = Regexp.last_match(3).to_i if Regexp.last_match(3) != "-" + relwidths[tag] = Regexp.last_match(4).to_i if Regexp.last_match(4) != "-" + end end # Read POI presets - icon_list=[]; icon_names={}; icon_tags={}; - File.open("#{RAILS_ROOT}/config/potlatch/icon_presets.txt") do |file| - file.each_line {|line| - (icon,name,tags)=line.chomp.split("\t") + icon_list = [] + icon_tags = {} + File.open(Rails.root.join("config", "potlatch", "icon_presets.txt")) do |file| + file.each_line do |line| + (icon, tags) = line.chomp.split("\t") icon_list.push(icon) - icon_names[icon]=name - icon_tags[icon]=Hash[*tags.scan(/([^;=]+)=([^;=]+)/).flatten] - } + icon_tags[icon] = Hash[*tags.scan(/([^;=]+)=([^;=]+)/).flatten] + end end icon_list.reverse! - + # Read auto-complete - autotags={}; autotags['point']={}; autotags['way']={}; autotags['POI']={}; - File.open("#{RAILS_ROOT}/config/potlatch/autocomplete.txt") do |file| - file.each_line {|line| - t=line.chomp - if (t=~/^([\w:]+)\/(\w+)\s+(.+)$/) then - tag=$1; type=$2; values=$3 - if values=='-' then autotags[type][tag]=[] - else autotags[type][tag]=values.split(',').sort.reverse end - end - } - end + autotags = { "point" => {}, "way" => {}, "POI" => {} } + File.open(Rails.root.join("config", "potlatch", "autocomplete.txt")) do |file| + file.each_line do |line| + next unless line.chomp =~ %r{^([\w:]+)/(\w+)\s+(.+)$} -# # Read internationalisation -# localised = YAML::load(File.open("#{RAILS_ROOT}/config/potlatch/localised.yaml")) + tag = Regexp.last_match(1) + type = Regexp.last_match(2) + values = Regexp.last_match(3) + autotags[type][tag] = if values == "-" + [] + else + values.split(",").sort.reverse + end + end + end - [presets,presetmenus,presetnames,colours,casing,areas,autotags,relcolours,relalphas,relwidths,icon_list,icon_names,icon_tags] + [presets, presetmenus, presetnames, colours, casing, areas, autotags, relcolours, relalphas, relwidths, icon_list, {}, icon_tags] end end - end -