Standardise on double quoted strings
[rails.git] / lib / potlatch.rb
1 require "stringio"
2
3 # The Potlatch module provides helper functions for potlatch and its communication with the server
4 module Potlatch
5   # The AMF class is a set of helper functions for encoding and decoding AMF.
6   class AMF
7     # Return two-byte integer
8     def self.getint(s)
9       s.getbyte * 256 + s.getbyte
10     end
11
12     # Return four-byte long
13     def self.getlong(s)
14       ((s.getbyte * 256 + s.getbyte) * 256 + s.getbyte) * 256 + s.getbyte
15     end
16
17     # Return string with two-byte length
18     def self.getstring(s)
19       len = s.getbyte * 256 + s.getbyte
20       str = s.read(len)
21       str.force_encoding("UTF-8") if str.respond_to?("force_encoding")
22       str
23     end
24
25     # Return eight-byte double-precision float
26     def self.getdouble(s)
27       a = s.read(8).unpack("G")                 # G big-endian, E little-endian
28       a[0]
29     end
30
31     # Return numeric array
32     def self.getarray(s)
33       getlong(s).times.collect do
34         getvalue(s)
35       end
36     end
37
38     # Return object/hash
39     def self.getobject(s)
40       arr = {}
41       while (key = getstring(s))
42         break if key == ""
43         arr[key] = getvalue(s)
44       end
45       s.getbyte         # skip the 9 'end of object' value
46       arr
47     end
48
49     # Parse and get value
50     def self.getvalue(s)
51       case s.getbyte
52       when 0 then return getdouble(s)                   # number
53       when 1 then return s.getbyte                      # boolean
54       when 2 then return getstring(s)                   # string
55       when 3 then return getobject(s)                   # object/hash
56       when 5 then return nil                            # null
57       when 6 then return nil                            # undefined
58       when 8 then s.read(4)                             # mixedArray
59                   return getobject(s)                   #  |
60       when 10 then return getarray(s)                   # array
61       else         return nil                           # error
62       end
63     end
64
65     # Envelope data into AMF writeable form
66     def self.putdata(index, n)
67       d = encodestring(index + "/onResult")
68       d += encodestring("null")
69       d += [-1].pack("N")
70       d += encodevalue(n)
71       d
72     end
73
74     # Pack variables as AMF
75     def self.encodevalue(n)
76       case n.class.to_s
77       when "Array"
78         a = 10.chr + encodelong(n.length)
79         n.each do |b|
80           a += encodevalue(b)
81         end
82         a
83       when "Hash"
84         a = 3.chr
85         n.each do |k, v|
86           a += encodestring(k.to_s) + encodevalue(v)
87         end
88         a + 0.chr + 0.chr + 9.chr
89       when "String"
90         2.chr + encodestring(n)
91       when "Bignum", "Fixnum", "Float"
92         0.chr + encodedouble(n)
93       when "NilClass"
94         5.chr
95       when "TrueClass"
96         0.chr + encodedouble(1)
97       when "FalseClass"
98         0.chr + encodedouble(0)
99       else
100         Rails.logger.error("Unexpected Ruby type for AMF conversion: " + n.class.to_s)
101       end
102     end
103
104     # Encode string with two-byte length
105     def self.encodestring(n)
106       n = n.dup.force_encoding("ASCII-8BIT") if n.respond_to?("force_encoding")
107       a, b = n.size.divmod(256)
108       a.chr + b.chr + n
109     end
110
111     # Encode number as eight-byte double precision float
112     def self.encodedouble(n)
113       [n].pack("G")
114     end
115
116     # Encode number as four-byte long
117     def self.encodelong(n)
118       [n].pack("N")
119     end
120   end
121
122   # The Dispatcher class handles decoding a series of RPC calls
123   # from the request, dispatching them, and encoding the response
124   class Dispatcher
125     def initialize(request, &_block)
126       # Get stream for request data
127       @request = StringIO.new(request + 0.chr)
128
129       # Skip version indicator and client ID
130       @request.read(2)
131
132       # Skip headers
133       AMF.getint(@request).times do     # Read number of headers and loop
134         AMF.getstring(@request)         #  | skip name
135         req.getbyte                     #  | skip boolean
136         AMF.getvalue(@request)          #  | skip value
137       end
138
139       # Capture the dispatch routine
140       @dispatch = Proc.new
141     end
142
143     def each(&_block)
144       # Read number of message bodies
145       bodies = AMF.getint(@request)
146
147       # Output response header
148       a, b = bodies.divmod(256)
149       yield 0.chr + 0.chr + 0.chr + 0.chr + a.chr + b.chr
150
151       # Process the bodies
152       bodies.times do                     # Read each body
153         name = AMF.getstring(@request)    #  | get message name
154         index = AMF.getstring(@request)   #  | get index in response sequence
155         AMF.getlong(@request)             #  | get total size in bytes
156         args = AMF.getvalue(@request)     #  | get response (probably an array)
157
158         result = @dispatch.call(name, *args)
159
160         yield AMF.putdata(index, result)
161       end
162     end
163   end
164
165   # The Potlatch class is a helper for Potlatch
166   class Potlatch
167     # ----- getpresets
168     #             in:   none
169     #             does: reads tag preset menus, colours, and autocomplete config files
170     #         out:  [0] presets, [1] presetmenus, [2] presetnames,
171     #                           [3] colours, [4] casing, [5] areas, [6] autotags
172     #                           (all hashes)
173     def self.get_presets
174       Rails.logger.info("  Message: getpresets")
175
176       # Read preset menus
177       presets = {}
178       presetmenus = { "point" => [], "way" => [], "POI" => [] }
179       presetnames = { "point" => {}, "way" => {}, "POI" => {} }
180       presettype = ""
181       presetcategory = ""
182       # StringIO.open(txt) do |file|
183       File.open("#{Rails.root}/config/potlatch/presets.txt") do |file|
184         file.each_line do|line|
185           t = line.chomp
186           if t =~ /(\w+)\/(\w+)/
187             presettype = $1
188             presetcategory = $2
189             presetmenus[presettype].push(presetcategory)
190             presetnames[presettype][presetcategory] = ["(no preset)"]
191           elsif t =~ /^([\w\s]+):\s?(.+)$/
192             pre = $1
193             kv = $2
194             presetnames[presettype][presetcategory].push(pre)
195             presets[pre] = {}
196             kv.split(",").each do|a|
197               presets[pre][$1] = $2 if a =~ /^(.+)=(.*)$/
198             end
199           end
200         end
201       end
202
203       # Read colours/styling
204       colours = {}
205       casing = {}
206       areas = {}
207       File.open("#{Rails.root}/config/potlatch/colours.txt") do |file|
208         file.each_line do |line|
209           next unless line.chomp =~ /(\w+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/
210
211           tag = $1
212           colours[tag] = $2.hex if $2 != "-"
213           casing[tag] = $3.hex if $3 != "-"
214           areas[tag] = $4.hex if $4 != "-"
215         end
216       end
217
218       # Read relations colours/styling
219       relcolours = {}
220       relalphas = {}
221       relwidths = {}
222       File.open("#{Rails.root}/config/potlatch/relation_colours.txt") do |file|
223         file.each_line do |line|
224           next unless line.chomp =~ /(\w+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/
225
226           tag = $1
227           relcolours[tag] = $2.hex if $2 != "-"
228           relalphas[tag] = $3.to_i if $3 != "-"
229           relwidths[tag] = $4.to_i if $4 != "-"
230         end
231       end
232
233       # Read POI presets
234       icon_list = []
235       icon_tags = {}
236       File.open("#{Rails.root}/config/potlatch/icon_presets.txt") do |file|
237         file.each_line do |line|
238           (icon, tags) = line.chomp.split("\t")
239           icon_list.push(icon)
240           icon_tags[icon] = Hash[*tags.scan(/([^;=]+)=([^;=]+)/).flatten]
241         end
242       end
243       icon_list.reverse!
244
245       # Read auto-complete
246       autotags = { "point" => {}, "way" => {}, "POI" => {} }
247       File.open("#{Rails.root}/config/potlatch/autocomplete.txt") do |file|
248         file.each_line do|line|
249           next unless line.chomp =~ /^([\w:]+)\/(\w+)\s+(.+)$/
250
251           tag = $1
252           type = $2
253           values = $3
254           if values == "-"
255             autotags[type][tag] = []
256           else
257             autotags[type][tag] = values.split(",").sort.reverse
258           end
259         end
260       end
261
262       [presets, presetmenus, presetnames, colours, casing, areas, autotags, relcolours, relalphas, relwidths, icon_list, {}, icon_tags]
263     end
264   end
265 end