-
Notifications
You must be signed in to change notification settings - Fork 5
/
scores.rb
188 lines (151 loc) · 4.66 KB
/
scores.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env ruby
#Freifunk node highscore game
#Copyright (C) 2012 Anton Pirogov
#Licensed under The GPLv3
require 'json'
require 'open-uri'
require './settings'
#write line to log if log enabled
def log(txt)
`echo "#{Time.now.to_s}: #{txt}" >> #{LOG_FILE}` if LOG
end
class Scores
@@scorepath = 'public/scores.json'
#--------
#return last update time -> last modification to file
def self.last_update
return File.mtime @@scorepath
rescue
return Time.new(0)
end
#take score file and generate a sorted highscore list of requested span for output
def self.generate(days=1, offset=0)
scores = read_scores
#sum up requested day scores
scores.each{|e| e['points'] = e['points'][offset,days].to_a.inject(&:+).to_i}
#return without losers
scores.delete_if{|e| e['points']<=0}
#sort by score
scores.sort_by! {|e| e['points']}.reverse!
return scores
end
def self.reset
File.delete @@scorepath
return true
rescue
return false
end
#run one update cycle and generate/update the score file
def self.update
scores = read_scores
#load node data
jsonstr = nil
begin
jsonstr = open(JSONSRC,'r:UTF-8').read
rescue
return false #failed!
end
#NOTE: filtering and analyzing of JSON data fits perfectly here
data = JSON.parse jsonstr
snapshot = transform data
merge scores, snapshot
scorejson = JSON.generate scores
File.write @@scorepath, scorejson
return true
end
private
#load current score file or fall back to empty array
def self.read_scores
return JSON.parse open(@@scorepath,'r:UTF-8').read
rescue
return []
end
#insert fresh new day score entry
def self.rotate(scores)
scores.each do |e|
e['points'].unshift 0
e['points'].pop if e['points'].length > 30
end
end
#clean and prepare node data
def self.transform(nodejson)
nodes = nodejson['nodes']
links = nodejson['links']
nodes.each do |n|
n['meshs']=[]
n['vpns']=[]
n['clients']=n['clientcount']
n.delete 'clientcount' #copied to clients
n.delete 'geo' #not interesting
n.delete 'id' #not interesting
end
links.each do |l|
t = l['type']
src = l['source']
dst = l['target']
if t.nil? #meshing
quality=l['quality'].split(", ").map(&:to_f)
nodes[src]['meshs'] << quality[0]
nodes[dst]['meshs'] << quality[1] if quality.size>1
elsif t=='vpn'
quality=l['quality'].split(", ").map(&:to_f)
nodes[src]['vpns'] << quality[0]
nodes[dst]['vpns'] << quality[1] if quality.size>1
end
end
#now can delete nodes (before order mattered)
nodes.delete_if{|n| n['name'].empty?}
nodes.delete_if{|n| BLACKLIST.index n['name']}
return nodes
end
#calculate and add points for node in current round and set info for html
def self.calc_points(node)
#reset current status data
node['sc_offline'] = node['sc_gateway'] = node['sc_clients'] = 0
node['sc_vpns'] = node['sc_meshs'] = 0
node['points'] = [0] if node['points'].nil?
p = node['points']
if !node['flags']['online'] #offline penalty
p[0] += (node['sc_offline'] = SC_OFFLINE)
return
end
p[0] += ( node['sc_gateway'] = SC_GATEWAY ) if node['flags']['gateway']
p[0] += ( node['sc_clients'] = SC_PERCLIENT * node['clients'] )
p[0] += ( node['sc_vpns'] = node['vpns'].map{|e| SC_PERVPN / e}.inject(&:+).to_i )
p[0] += ( node['sc_meshs'] = node['meshs'].map{|e| SC_PERMESH / e}.inject(&:+).to_i )
end
#update scores, add new nodes, remove old nodes with <=0 points
def self.merge(scores, data)
#start new day points field on day change between updates
rotate scores if last_update.day < Time.now.day
#garbage collection:
#detect nodes which are gone from source data (by name so node renames affected too)
#and let them slowly die (by offline penalty)
scores.select{|s| !data.index{|d| d['name']==s['name']}}.each do |s|
s['flags']['online']=false
s['flags']['gateway']=false
s['vpns'] = []
s['meshs'] = []
s['clients'] = 0
calc_points s
end
#To prevent multiple nodes with same name mixed up take only first
seen = Hash.new false
#perform regular update
data.each do |n|
next if seen[n['name']]
seen[n['name']] = true
i = scores.index{|s| s['name'] == n['name'] }
if i.nil? #new entry
scores.push n
calc_points scores[-1]
elsif #update preserving points array
p = scores[i]['points']
scores[i] = n
scores[i]['points'] = p
calc_points scores[i]
end
end
return scores
end
end