+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_tags={};
- File.open("#{RAILS_ROOT}/config/potlatch/icon_presets.txt") do |file|
- file.each_line {|line|
- (icon,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_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
- }
+ 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+(.+)$}
+
+ 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_tags]
+ [presets, presetmenus, presetnames, colours, casing, areas, autotags, relcolours, relalphas, relwidths, icon_list, {}, icon_tags]
end
end
-
end
-