REFACTOR: storing Process instead of PID

This commit is contained in:
Patrick Haßel 2025-07-30 16:46:29 +02:00
parent 33ae1642e2
commit 6ee826f94b
17 changed files with 469 additions and 286 deletions

View File

@ -1 +1,3 @@
#logging.level.de.ph87=DEBUG
#-
server.port=8083 server.port=8083

View File

@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 512 85.169998" xmlns="http://www.w3.org/2000/svg">
<style>
.st0 {
fill: #020303;
}
.st1 {
fill: #A9A09D;
}
.st2 {
fill: #282729;
}
.st3 {
fill: #595558;
}
.st4 {
fill: #E1DCD7;
}
</style>
<g transform="translate(0.05,-213.36)">
<path class="st0"
d="m 511.67,276.51 -1.54,-5.47 -1.54,-4.91 c 0,0 -4.63,-15.15 -5.33,-17.26 -0.7,-2.53 -1.54,-4.91 -2.24,-7.3 l -0.14,-0.42 10.38,-12.49 -4.91,-15.29 h -52.06 l -1.54,2.53 -0.56,-2.53 H 399.7 l -1.26,2.95 -0.56,-2.95 h -52.9 l -0.98,3.51 -0.42,-3.51 h -53.46 l -0.56,5.61 -0.14,-5.61 h -53.74 l -0.14,5.19 -0.56,-5.19 H 181.8 l -0.42,3.37 -0.98,-3.37 h -25.68 l -2.1,14.31 -1.12,-2.95 h -0.56 l -4.49,-11.37 h -25.4 l -0.56,2.81 -1.4,-2.81 H 93.96 l -0.56,2.53 -1.54,-2.53 H 67.02 l -2.95,11.08 h -6.73 l -1.26,4.91 -11.22,-15.43 H 20.58 l -20.63,64.26 22.03,20.35 h 23.99 l 3.79,-14.31 1.82,2.1 h 6.45 l 10.66,12.21 H 93.4 l 0.42,-2.39 1.68,2.39 h 24.84 l 0.42,-2.53 1.54,2.53 h 25.12 l 1.96,-13.47 6.45,13.47 h 25.4 l 0.28,-3.09 1.12,3.09 h 52.76 l 0.14,-4.91 0.56,4.91 h 53.18 l 0.56,-5.33 0.14,5.33 h 26.1 l 0.84,-3.79 0.28,3.79 h 25.82 l 1.12,-3.23 0.28,3.23 h 25.54 l 1.26,-2.81 0.42,2.81 h 25.26 l 1.54,-2.67 0.42,2.67 h 25.12 l 14.45,-20.63 -0.7,-3.37 h 8 l 11.37,-15.71 9.82,39.71 h 24.55 l 20.49,-20.49 z m -248.77,-15.72 0.28,-8.84 h 25.12 l 0.28,-2.95 0.42,11.65 z"/>
<polygon class="st1" points="57.48,240.16 60.56,228.66 67.3,228.66 70.25,217.43 89.89,217.43 76,276.79 54.95,276.79 61.27,251.53 53.69,251.53 50.32,263.88 36.71,263.88 40.08,251.53 32.36,251.53 24.93,276.79 4.16,276.79 23.24,217.57 42.46,217.43 39.24,228.24 45.97,228.52 42.6,240.16 "/>
<polygon class="st1" points="84,276.93 97.19,217.57 116.83,217.57 105.18,276.93 "/>
<polygon class="st1" points="139.42,245.22 134.37,276.79 113.32,276.79 124.27,217.43 143.91,217.43 142.23,228.38 148.96,228.52 147.28,240.02 154.85,240.16 158.08,217.43 177.73,217.43 170.85,276.65 149.66,276.79 152.33,257.84 145.31,257.7 147.14,245.36 "/>
<polygon class="st1" points="178.85,276.65 185.16,217.29 231.89,217.29 231.47,227.82 203.96,227.82 203.12,239.88 230.9,240.02 230.34,251.53 202,251.53 201.16,264.3 229.78,264.44 229.22,276.65 "/>
<path class="st1" d="m 503.81,217.01 3.37,10.52 h -13.61 l 0.14,0.28 c 0.56,2.25 1.12,4.63 1.82,7.02 0.7,2.39 1.4,4.91 2.1,7.3 0.7,2.53 1.4,5.05 2.24,7.58 0.7,2.53 1.54,5.05 2.39,7.58 0.42,1.54 0.98,3.09 1.4,4.63 0.42,1.54 0.98,3.23 1.4,4.77 0.42,1.54 0.98,3.23 1.4,4.91 0.42,1.68 0.98,3.23 1.4,4.91 H 486.53 L 473.2,227.54 h -13.61 l -2.67,-10.52 z"/>
<polygon class="st1" points="429.31,251.39 434.5,276.65 413.31,276.65 402.37,217.15 449.37,217.15 451.9,227.68 424.25,227.68 426.78,239.6 454.84,239.6 457.79,251.39 "/>
<path class="st1" d="m 373.18,257.42 2.67,19.22 h -21.19 l -6.59,-59.49 h 46.86 l 10.24,59.49 h -21.19 l -2.81,-19.22 z m 13.75,-23.29 -1.26,-7.58 h -9.26 l 0.84,6.88 h -7.3 l -0.98,-6.88 h -8.98 l 0.98,7.58 h 8.28 l 0.42,2.81 h -4.21 l 1.54,11.65 h 4.91 l -0.42,-3.93 h 7.44 l 0.56,3.93 h 5.05 l -1.82,-11.65 h -4.07 l -0.42,-2.81 h 8.7"/>
<path class="st1" d="m 336.42,245.92 h 7.02 l 3.09,30.73 h -21.19 l -1.82,-25.12 -7.86,-0.14 1.68,25.26 h -21.19 l -2.24,-59.35 h 46.86 l 2.24,22.03 H 336 Z m -13.89,-5.9 -0.98,-12.21 h -7.58 l 0.84,12.21 h 7.72"/>
<polygon class="st1" points="286.19,217.29 239.32,217.29 237.36,276.65 287.87,276.65 287.45,264.44 258.69,264.44 259.25,227.82 286.47,227.82 "/>
<path class="st2" d="m 6.97,276.79 c -1.54,0 -1.82,0.84 -0.7,1.96 l 15.01,14.45 c 1.12,0.98 3.23,1.96 4.77,1.96 h 13.19 c 1.54,0 1.96,-0.84 0.84,-1.96 L 26.75,279.03 c -0.98,-1.12 -3.09,-1.96 -4.63,-1.96 z"/>
<path class="st2" d="m 57.76,276.79 c -1.54,0 -1.96,0.98 -0.98,2.1 l 11.79,14.03 c 0.98,1.12 3.09,2.1 4.49,2.1 h 13.89 c 1.54,0 1.96,-0.98 1.12,-2.24 L 77.83,279.03 c -0.84,-1.26 -2.95,-2.24 -4.49,-2.24 z"/>
<path class="st2" d="m 39.52,263.88 c -1.54,0 -1.96,0.98 -0.98,2.1 l 13.05,14.73 c 0.98,1.12 2.81,1.96 4.07,1.96 1.26,0 1.54,-1.12 0.7,-2.39 l -0.84,-1.26 c -0.84,-1.26 -1.26,-3.51 -0.84,-4.91 l 0.28,-0.98 c 0.42,-1.4 0,-3.65 -0.98,-4.77 l -1.82,-2.25 c -0.98,-1.26 -2.95,-2.1 -4.49,-2.1 z"/>
<path class="st2" d="m 86.8,276.93 c -1.54,0 -1.96,0.98 -1.12,2.24 l 9.96,13.61 c 0.84,1.26 2.95,2.25 4.35,2.25 h 13.61 c 1.54,0 2.1,-1.12 1.26,-2.39 l -8.42,-13.47 c -0.84,-1.26 -2.67,-2.39 -4.21,-2.39 z"/>
<path class="st2" d="m 115.99,276.79 c -1.54,0 -2.1,1.12 -1.26,2.39 l 8.14,13.33 c 0.84,1.26 2.67,2.39 4.21,2.39 h 13.47 c 1.54,0 2.24,-1.12 1.54,-2.53 l -6.45,-13.05 c -0.7,-1.4 -2.53,-2.53 -3.93,-2.53 z"/>
<path class="st2" d="m 152.47,276.79 c -1.54,0 -2.25,1.12 -1.54,2.53 l 6.17,13.19 c 0.7,1.4 2.39,2.53 3.93,2.53 h 13.89 c 1.54,0 2.39,-1.12 1.82,-2.67 l -4.77,-13.05 c -0.56,-1.4 -2.25,-2.67 -3.79,-2.67 z"/>
<path class="st2" d="m 181.65,276.65 c -1.54,0 -2.39,1.12 -1.82,2.67 l 4.49,12.63 c 0.56,1.4 2.24,2.67 3.65,2.67 h 40.97 c 1.54,0 2.53,-1.26 2.39,-2.81 l -1.68,-12.35 c -0.28,-1.54 -1.68,-2.81 -3.09,-2.81 z"/>
<path class="st2" d="m 240.16,276.65 c -1.54,0 -2.67,1.26 -2.53,2.81 l 1.4,12.77 c 0.14,1.54 1.54,2.81 3.09,2.81 h 40.83 c 1.54,0 2.95,-1.26 3.09,-2.81 l 1.54,-12.77 c 0.14,-1.54 -0.98,-2.81 -2.39,-2.81 z"/>
<path class="st2" d="m 298.68,276.65 c -1.54,0 -2.95,1.26 -3.09,2.81 l -1.54,12.63 c -0.14,1.54 0.84,2.81 2.39,2.81 h 13.61 c 1.54,0 3.09,-1.26 3.37,-2.67 l 3.09,-12.77 c 0.28,-1.54 -0.56,-2.67 -2.1,-2.67 z"/>
<path class="st2" d="m 328,276.65 c -1.54,0 -3.09,1.26 -3.37,2.67 l -2.95,12.49 c -0.42,1.54 0.56,2.67 2.1,2.67 h 13.61 c 1.54,0 3.23,-1.12 3.65,-2.67 l 4.49,-12.63 c 0.56,-1.4 -0.28,-2.67 -1.82,-2.67 z"/>
<path class="st2" d="m 357.33,276.65 c -1.54,0 -3.23,1.12 -3.65,2.67 l -4.49,12.91 c -0.56,1.4 0.28,2.67 1.82,2.67 h 13.61 c 1.54,0 3.37,-1.12 3.93,-2.53 l 6.17,-13.05 c 0.7,-1.4 -0.14,-2.53 -1.54,-2.53 z"/>
<path class="st2" d="m 386.65,276.65 c -1.54,0 -3.37,1.12 -3.93,2.53 l -6.45,13.19 c -0.7,1.4 0,2.53 1.54,2.53 h 14.03 c 1.54,0 3.37,-1.12 4.21,-2.39 l 7.72,-13.33 c 0.7,-1.26 0.14,-2.39 -1.4,-2.39 h -15.71 v -0.14 z"/>
<path class="st2" d="m 416.12,276.65 c -1.54,0 -3.37,1.12 -4.21,2.39 l -8.14,13.47 c -0.84,1.26 -0.14,2.39 1.26,2.39 h 13.89 c 1.54,0 3.51,-0.98 4.35,-2.24 l 9.68,-13.61 c 0.84,-1.26 0.42,-2.25 -1.12,-2.25 z"/>
<path class="st2" d="m 489.36,276.65 c -1.54,0 -3.65,0.98 -4.63,2.1 l -12.21,13.75 c -0.98,1.12 -0.56,2.1 0.98,2.1 h 13.19 c 1.54,0 3.65,-0.84 4.77,-1.96 l 14.45,-14.03 c 1.12,-1.12 0.7,-1.96 -0.84,-1.96 z"/>
<path class="st0" d="m 19.73,227.4 c -0.14,0.42 0.98,0.7 2.53,0.7 h 14.45 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.67,-0.7 H 22.82 c -1.54,-0.01 -2.95,0.28 -3.09,0.7"/>
<path class="st0" d="m 18.75,257.56 c 0.42,-1.4 -0.42,-2.67 -1.96,-2.67 h -3.51 c -1.54,0 -2.81,0.28 -2.81,0.7 0,0.42 1.26,0.7 2.81,0.7 h 1.82 c 1.54,0 2.39,1.12 1.96,2.67 l -3.23,9.82 c -0.42,1.4 0.42,2.67 1.96,2.67 h 10.8 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 h -9.12 c -1.54,0 -2.39,-1.12 -1.96,-2.67 l 0.28,-0.84 c 0.42,-1.4 2.1,-2.67 3.65,-2.67 h 8.42 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 h -8 c -1.54,0 -2.39,-1.12 -1.96,-2.67 z"/>
<path class="st0" d="m 76.56,229.92 c 0.14,-1.54 -0.84,-2.81 -2.39,-2.81 h -4.21 c -1.54,0 -2.81,0.28 -2.95,0.7 -0.14,0.28 0.98,0.7 2.53,0.7 h 3.23 c 1.54,0 2.67,1.26 2.39,2.81 l -0.56,4.35 c -0.14,1.54 -1.68,2.81 -3.09,2.81 H 58.18 c -1.54,0 -2.81,0.28 -2.81,0.7 0,0.42 1.26,0.7 2.81,0.7 h 13.05 3.09 3.09 6.74 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 H 78.1 c -1.54,0 -2.67,-1.26 -2.39,-2.81 z"/>
<path class="st0" d="m 94.8,269.49 c 0.28,-1.54 0.14,-2.1 -0.42,-1.26 -0.42,0.84 -1.12,2.67 -1.4,4.07 l -0.42,2.24 c -0.28,1.54 0.14,3.79 0.98,5.05 l 0.7,0.98 c 0.84,1.26 2.25,2.81 3.09,3.37 0.84,0.56 0.84,0 0,-1.26 l -2.39,-3.51 c -0.84,-1.26 -1.26,-3.51 -0.98,-5.05 z"/>
<path class="st0" d="m 115.71,229.5 c 0,-0.42 -1.26,-0.56 -2.81,-0.56 l -5.47,0.14 c -1.54,0 -2.1,0.28 -1.4,0.56 0.7,0.28 2.67,0.56 4.21,0.56 h 2.67 c 1.53,-0.14 2.8,-0.42 2.8,-0.7"/>
<path class="st0" d="m 123.71,272.58 c 0.28,-1.54 0.14,-2.1 -0.42,-1.26 -0.56,0.84 -1.12,2.39 -1.4,3.37 -0.28,1.12 0,2.95 0.42,4.35 0.56,1.26 1.4,2.95 1.96,3.65 0.56,0.7 0.56,0.14 0.14,-1.4 l -0.7,-2.1 c -0.42,-1.4 -0.56,-3.79 -0.28,-5.33 z"/>
<path class="st0" d="m 118.51,257.56 c -1.54,-0.14 -2.81,0.14 -2.81,0.56 0,0.42 1.26,0.7 2.67,0.84 l 15.43,0.84 c 1.54,0.14 3.23,0.7 3.65,1.54 0.42,0.7 1.12,1.26 1.4,0.98 0.28,-0.14 0,-1.12 -0.56,-1.96 -0.56,-0.84 -2.25,-1.68 -3.79,-1.82 z"/>
<path class="st0" d="m 134.79,227.4 c 0.56,0.42 2.39,0.7 3.79,0.7 h 1.12 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 l -3.37,-0.14 c -1.26,0.13 -2.1,0.42 -1.54,0.84"/>
<path class="st0" d="m 153.31,257.98 c -1.54,0 -2.81,0.42 -2.81,0.7 0,0.42 1.26,0.56 2.81,0.56 l 8.98,-0.42 c 1.54,0 2.53,1.12 2.39,2.67 l -1.82,12.49 c -0.28,1.54 0,3.93 0.42,5.33 l 0.42,1.26 c 0.42,1.4 1.26,3.37 1.96,4.35 0.7,0.84 0.84,0.42 0.42,-0.98 l -1.4,-4.63 c -0.42,-1.4 -0.56,-3.93 -0.42,-5.33 l 2.1,-13.75 c 0.28,-1.54 -0.84,-2.67 -2.39,-2.67 z"/>
<path class="st0" d="m 156.4,227.12 c 0,0.28 1.12,0.84 2.67,1.12 l 15.29,1.82 c 1.54,0.14 2.81,0 2.81,-0.28 0,-0.42 -1.26,-0.84 -2.81,-0.98 l -15.15,-1.82 c -1.55,-0.29 -2.81,-0.29 -2.81,0.14"/>
<path class="st0" d="m 189.65,266.12 c 0,-1.54 -1.12,-2.81 -2.67,-2.81 h -5.47 c -1.54,0 -2.81,0.28 -2.81,0.7 0,0.42 1.26,0.7 2.81,0.7 h 4.07 c 1.54,0 2.67,1.26 2.67,2.81 l -0.28,6.45 c 0,1.54 0.28,3.93 0.56,5.47 l 0.98,3.93 c 0.42,1.54 1.12,3.37 1.54,4.35 0.42,0.84 0.56,0.42 0.28,-1.12 l -1.68,-7.3 c -0.28,-1.54 -0.56,-3.93 -0.56,-5.47 z"/>
<path class="st0" d="m 182.08,234.69 c 0,0.42 1.26,0.7 2.81,0.7 h 5.19 c 1.54,0 3.37,-0.28 4.21,-0.7 0.7,-0.42 0.14,-0.7 -1.4,-0.7 l -8,0.14 c -1.55,-0.14 -2.81,0.14 -2.81,0.56"/>
<path class="st0" d="m 217.71,241.29 c 0,-1.54 -0.42,-2.81 -0.7,-2.81 -0.28,0 -0.7,1.26 -0.7,2.81 v 6.31 c 0,1.54 0.28,2.1 0.7,1.12 0.42,-0.84 0.7,-2.81 0.7,-4.35 z"/>
<path class="st0" d="m 223.89,264.86 c 0,1.54 -1.4,2.81 -2.81,2.81 h -4.21 c -1.54,0 -2.81,1.26 -2.81,2.81 v 4.21 c 0,1.54 0.28,2.81 0.7,2.81 0.42,0 0.7,-1.26 0.7,-2.81 v -2.81 c 0,-1.54 1.26,-2.81 2.81,-2.81 h 4.21 c 1.54,0 2.81,-1.26 2.81,-2.81 v -1.4 c 0,-1.54 -0.14,-2.81 -0.56,-2.81 -0.42,0 -0.7,1.13 -0.84,2.81 z"/>
<path class="st0" d="m 276.93,267.67 c -0.28,0.56 -0.42,2.24 -0.28,3.79 l 0.28,2.67 c 0.14,1.54 0.14,3.93 -0.14,5.47 l -1.82,13.19 c -0.14,1.54 0,2.67 0.42,2.67 0.42,0 0.98,-1.26 1.12,-2.81 l 1.68,-13.05 c 0.14,-1.54 0.14,-3.93 0,-5.47 l -0.56,-4.63 c 0,-1.55 -0.42,-2.4 -0.7,-1.83"/>
<path class="st0" d="m 238.06,288.15 c 0,0.42 1.26,0.7 2.81,0.7 h 0.7 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 h -0.7 c -1.55,0 -2.81,0.28 -2.81,0.7"/>
<path class="st0" d="m 242.97,236.38 c 0.7,0.42 2.53,0.98 3.93,1.26 l 10.66,1.96 c 1.4,0.56 2.67,0.84 2.81,0.56 0.14,-0.28 -0.98,-0.98 -2.39,-1.4 l -13.61,-2.39 c -1.4,-0.41 -2.1,-0.41 -1.4,0.01"/>
<path class="st0" d="m 242.83,248.3 c 0.56,-0.42 -0.14,-0.7 -1.68,-0.7 h -0.56 c -1.54,0 -2.81,0.42 -2.81,0.7 0,0.42 0.84,0.7 1.96,0.7 1.13,0.14 2.39,-0.28 3.09,-0.7"/>
<path class="st0" d="m 303.02,237.08 c 0.14,0.84 1.4,1.54 2.95,1.54 h 1.26 c 1.54,0 2.95,0 3.23,0.14 0.28,0.14 1.12,0.7 1.4,0.56 0.28,-0.14 0.14,-0.7 -0.56,-1.12 -0.56,-0.42 -2.39,-0.84 -3.93,-0.84 h -0.28 c -1.54,0 -2.95,-1.26 -3.09,-2.81 l -1.68,-15.29 c -0.14,-1.54 -0.56,-2.67 -0.98,-2.67 -0.28,0 -0.56,1.26 -0.28,2.81 l 1.4,12.07 c 0.14,1.54 -0.98,2.81 -2.53,2.81 h -4.35 c -1.54,0 -2.81,0.28 -2.81,0.7 0,0.42 1.26,0.7 2.81,0.7 h 4.49 c 1.55,-0.15 2.81,0.56 2.95,1.4"/>
<path class="st0" d="m 301.2,267.39 c 0.14,1.54 -0.42,3.79 -1.12,5.19 l -1.12,1.26 c -0.42,1.54 -0.42,2.81 0,2.81 0.28,0.14 0.98,-0.98 1.54,-2.53 0,0 0.14,-0.7 0.7,-1.4 0.56,-0.84 1.12,-1.4 1.4,-1.4 0.14,0 0.28,-1.26 0.14,-2.81 l -0.42,-7.02 c -0.14,-1.54 1.12,-2.81 2.53,-2.81 h 10.38 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 h -11.79 c -1.54,0 -2.67,1.26 -2.53,2.81 z"/>
<path class="st0" d="m 322.95,262.05 c 0,0.42 1.26,0.7 2.81,0.7 h 7.02 c 1.54,0 3.37,-0.28 3.93,-0.7 0.7,-0.42 0,-0.7 -1.54,-0.7 h -9.4 c -1.56,0 -2.82,0.28 -2.82,0.7"/>
<path class="st0" d="m 328.98,243.81 c 0.42,0.28 1.96,0.56 3.51,0.56 h 1.96 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.56 h -3.37 c -1.68,0 -2.52,0.28 -2.1,0.7"/>
<path class="st0" d="m 293.48,289.13 c 0,0.42 1.26,0.7 2.67,0.84 l 15.43,1.12 c 1.54,0.14 2.81,-0.14 2.81,-0.42 0,-0.42 -1.26,-0.7 -2.67,-0.84 l -15.43,-1.12 c -1.54,-0.14 -2.81,0.14 -2.81,0.42"/>
<path class="st0" d="m 383.28,261.49 c -1.54,0 -2.81,0.28 -2.81,0.7 0,0.42 1.26,0.7 2.81,0.56 l 18.8,2.1 c 1.54,0.14 2.81,0 2.95,-0.28 0.14,-0.28 -0.98,-0.7 -2.53,-0.84 z"/>
<path class="st0" d="m 441.93,237.64 c -0.28,0.14 -0.56,1.4 -0.42,2.95 l 1.68,8.7 c -0.7,1.4 -0.98,2.67 -0.56,2.81 0.28,0.14 1.12,-0.84 1.82,-2.24 l -1.68,-9.68 c -0.27,-1.56 -0.56,-2.68 -0.84,-2.54"/>
<path class="st0" d="m 431.97,221.5 c -1.4,0 -2.81,-1.12 -3.23,-2.53 -0.42,-1.4 -1.12,-2.53 -1.4,-2.39 -0.28,0.14 -0.28,1.4 0.14,2.81 l 0.28,0.7 c 0.42,1.4 1.96,2.67 3.37,2.67 1.4,0 2.95,1.12 3.37,2.67 l 0.7,1.82 c 0.56,1.4 1.12,2.53 1.54,2.39 0.28,-0.14 0.14,-1.4 -0.28,-2.81 l -0.98,-2.81 c -0.7,-1.39 -2.25,-2.52 -3.51,-2.52"/>
<path class="st0" d="m 483.05,258.97 c -1.54,0 -2.81,0.28 -2.81,0.7 0,0.42 1.26,0.7 2.81,0.7 h 17.68 c 1.54,0 2.81,-0.28 2.81,-0.7 0,-0.42 -1.26,-0.7 -2.81,-0.7 z"/>
<path class="st0" d="m 496.38,228.66 c 0.28,-0.14 0.28,-1.4 -0.14,-2.81 l -1.82,-6.45 c -0.42,-1.4 -0.98,-2.53 -1.4,-2.53 -0.28,0.14 -0.28,1.4 0.14,2.81 l 1.82,6.45 c 0.41,1.41 1.11,2.53 1.4,2.53"/>
<path class="st0" d="m 66.46,228.24 h 5.75 c 1.54,0 1.54,-0.28 0,-0.56 l -1.96,-0.42 c -1.54,-0.28 -2.67,0 -2.53,0.56 z"/>
<path class="st0" d="m 34.05,285.63 c -0.56,-0.7 -2.24,-0.98 -3.79,-0.84 l -3.37,0.42 c -1.54,0.14 -1.54,0.42 0,0.7 l 5.47,0.7 c 1.55,0 2.25,-0.42 1.69,-0.98"/>
<path class="st0" d="m 88.91,292.78 c -0.42,-0.7 -1.96,-1.12 -3.51,-0.98 l -7.16,0.56 c -1.54,0.14 -1.54,0.28 0,0.42 l 8.7,0.84 c 1.41,0.14 2.25,-0.28 1.97,-0.84"/>
<path class="st0" d="m 155.28,289.98 c -0.14,-0.56 0.98,-0.98 2.39,-0.98 h 1.82 c 1.54,0 1.54,0.42 0.14,0.84 l -1.4,0.42 c -1.55,0.56 -2.81,0.42 -2.95,-0.28"/>
<path class="st0" d="m 231.18,285.77 c -0.14,-0.84 -1.4,-1.4 -2.95,-1.26 l -5.05,0.7 c -1.54,0.28 -1.54,0.56 0,0.84 l 5.47,0.84 c 1.55,0.42 2.68,-0.14 2.53,-1.12"/>
<path class="st0" d="m 237.5,288.85 c -0.14,-0.84 1.12,-1.4 2.67,-1.26 h 0.14 c 1.54,0.14 1.68,0.84 0.28,1.54 l -0.42,0.14 c -1.41,0.71 -2.53,0.57 -2.67,-0.42"/>
<path class="st0" d="m 189.51,274.12 c -0.14,-1.26 -0.56,-1.4 -0.7,-0.28 -0.28,1.12 -0.14,3.37 0.42,4.77 l 0.28,1.12 c 0.42,1.4 0.7,1.4 0.56,-0.14 v -0.56 c -0.14,-1.4 -0.42,-3.65 -0.56,-4.91"/>
<path class="st0" d="m 214.63,275.1 c -0.28,-0.56 -0.56,0.28 -0.42,1.82 v 0.7 c 0,1.54 0.28,1.82 0.56,0.56 0.28,-1.25 0.14,-2.66 -0.14,-3.08"/>
<path class="st0" d="m 163.55,274.54 c 0,-1.26 -0.28,-1.26 -0.56,0 -0.28,1.26 0,3.51 0.42,5.05 l 0.42,1.26 c 0.42,1.4 0.7,1.4 0.42,-0.14 l -0.14,-0.98 c -0.13,-1.68 -0.42,-3.93 -0.56,-5.19"/>
<path class="st0" d="m 122.58,275.52 c -0.28,-0.56 -0.56,-0.42 -0.84,0.28 -0.28,0.7 0,1.82 0.56,2.67 0.56,0.85 0.98,0.7 0.84,-0.28 -0.14,-0.98 -0.28,-2.25 -0.56,-2.67"/>
<path class="st0" d="m 93.96,274.54 c -0.28,-1.26 -0.84,-1.12 -1.12,0.28 v 0 c -0.42,1.54 0.28,3.51 1.26,4.63 1.12,1.12 1.54,0.98 1.12,-0.28 -0.42,-1.26 -0.98,-3.37 -1.26,-4.63"/>
<path class="st0" d="m 42.32,272.72 c -0.42,-0.56 0.42,-0.84 1.96,-0.7 l 4.35,0.42 c 1.54,0.14 1.54,0.42 0,0.56 l -2.95,0.42 c -1.39,0.14 -2.93,-0.14 -3.36,-0.7"/>
<path class="st0" d="m 277.35,273.56 c 0.28,-1.54 0.7,-1.54 0.98,0 l 0.14,0.56 c 0.28,1.54 0.28,3.93 -0.14,5.33 l -0.56,1.96 c -0.42,1.4 -0.7,1.4 -0.84,-0.14 v -1.82 c 0,-1.54 0.14,-4.07 0.28,-5.47 z"/>
<path class="st0" d="m 260.09,240.31 c 0,0.7 -0.98,0.42 -2.24,-0.42 l -0.84,-0.56 c -1.26,-0.84 -1.12,-1.26 0.42,-0.84 v 0 c 1.4,0.41 2.66,1.25 2.66,1.82"/>
<path class="st0" d="m 344.14,284.22 c 0.14,-0.56 -0.98,-0.98 -2.39,-0.98 h -1.96 c -1.54,0 -1.54,0.42 -0.14,0.84 l 1.54,0.42 c 1.4,0.42 2.81,0.28 2.95,-0.28"/>
<path class="st0" d="m 333.33,275.1 c -0.42,-0.84 -0.7,-0.42 -0.7,1.12 v 0.28 c 0,1.54 0.42,2.25 0.7,1.4 0.56,-0.69 0.42,-1.96 0,-2.8"/>
<path class="st0" d="m 371.64,286.89 c 0.28,-0.7 -0.84,-1.12 -2.39,-0.98 l -5.19,0.42 c -1.54,0.14 -1.54,0.42 0,0.7 l 4.49,0.7 c 1.4,0.14 2.81,-0.14 3.09,-0.84"/>
<path class="st0" d="m 366.31,275.38 c -0.28,-0.7 -0.56,-0.7 -0.7,-0.14 -0.14,0.7 -0.14,2.1 0.14,3.23 0.28,1.12 0.56,1.12 0.7,0.14 0.14,-1.12 0,-2.53 -0.14,-3.23"/>
<path class="st0" d="m 442.64,252.37 c -0.56,-0.14 -0.42,-1.4 0.14,-2.81 0.56,-1.4 0.98,-1.26 0.98,0.28 v 0.14 c -0.14,1.55 -0.56,2.67 -1.12,2.39"/>
<path class="st0" d="m 406.44,285.35 c 0.56,-0.7 2.24,-0.98 3.65,-0.84 l 7.44,1.12 c 1.54,0.28 1.54,0.42 0,0.42 l -9.4,0.28 c -1.41,0.28 -2.26,-0.28 -1.69,-0.98"/>
<path class="st0" d="m 477.15,285.35 c 0.7,-0.84 2.39,-1.4 3.93,-1.12 l 6.88,1.26 c 1.54,0.28 1.4,0.56 0,0.7 l -9.12,0.56 c -1.69,0.14 -2.39,-0.42 -1.69,-1.4"/>
<path class="st0" d="m 479.96,259.53 c -0.28,-0.98 0.7,-1.68 2.25,-1.4 l 6.17,0.84 c 1.54,0.28 1.54,0.7 0,0.84 l -5.05,0.98 c -1.69,0.28 -3.09,-0.28 -3.37,-1.26"/>
<path class="st0" d="m 472.66,229.64 c -0.42,-0.42 -0.84,-1.96 -0.98,-3.51 v -0.28 c 0,-1.54 0.28,-1.54 0.56,-0.14 l 0.56,1.96 c 0.42,1.41 0.42,2.39 -0.14,1.97"/>
<path class="st0" d="m 404.19,265.7 -3.93,-1.54 c -1.4,-0.56 -1.4,-0.84 0.14,-0.7 l 4.07,0.42"/>
<path class="st0" d="m 256.3,278.19 c 0.28,-0.84 0.28,-2.1 0,-2.95 -0.28,-0.7 -1.82,-1.4 -3.37,-1.4 h -1.54 c -1.54,0 -3.23,0.7 -3.79,1.68 -0.56,0.84 0.28,1.96 1.68,2.39 l 3.79,1.12 c 1.41,0.28 2.95,0 3.23,-0.84"/>
<path class="st0" d="m 253.63,275.94 c -0.98,-1.12 -0.98,-1.96 0.14,-1.96 1.12,0 2.39,0.56 2.95,1.12 0.56,0.7 0.28,2.24 -0.56,3.51 l -0.28,0.42 c -0.84,1.26 -1.26,1.54 -0.98,0.56 0.42,-0.84 -0.28,-2.52 -1.27,-3.65"/>
<path class="st0" d="m 156.12,227.54 c 0.14,-0.84 1.4,-1.26 2.95,-0.84 l 0.7,0.14 c 1.54,0.42 1.4,0.98 0,1.26 l -1.12,0.28 c -1.41,0.42 -2.53,0 -2.53,-0.84"/>
<path class="st0" d="m 86.52,239.32 c -0.28,0.56 -1.68,0.7 -3.23,0.28 l -0.84,-0.14 c -1.54,-0.42 -1.4,-0.7 0.14,-0.84 l 1.82,-0.14 c 1.41,-0.14 2.39,0.28 2.11,0.84"/>
<path class="st0" d="m 30.4,263.04 c 0.28,-0.7 -0.84,-1.12 -2.24,-0.7 l -2.39,0.42 c -1.54,0.28 -1.54,0.7 0,0.98 l 1.4,0.28 c 1.54,0.14 2.95,-0.28 3.23,-0.98"/>
<path class="st2" d="m 34.61,285.77 c -0.42,-0.56 -0.56,-2.1 -0.14,-3.65 l 0.56,-1.96 c 0.42,-1.4 0.7,-1.4 0.7,0.14 l -0.14,3.79 c 0,1.4 -0.42,2.24 -0.98,1.68"/>
<path class="st2" d="m 88.49,292.92 c -0.42,-0.28 -0.42,-1.54 0,-2.95 0.42,-1.26 0.84,-1.12 0.84,0.42 v 0.28 c 0,1.55 -0.28,2.53 -0.84,2.25"/>
<path class="st2" d="m 137.18,260.51 c 0,-0.98 0.28,-0.56 0.84,0.84 l 0.28,0.84 c 0.56,1.4 0.56,1.96 0,1.26 -0.56,-0.69 -1.12,-1.96 -1.12,-2.94"/>
<path class="st2" d="m 407.14,286.19 c 0.56,-0.42 0.56,-1.96 0.14,-3.37 l -0.7,-2.24 c -0.42,-1.4 -0.7,-1.4 -0.7,0.14 l 0.14,3.51 c 0.13,1.54 0.56,2.38 1.12,1.96"/>
<path class="st2" d="m 478.13,285.77 c 0.7,-0.42 0.84,-1.82 0.28,-3.23 l -2.81,-6.17 c -0.7,-1.4 -0.98,-1.26 -0.7,0.14 l 1.4,7.3 c 0.29,1.54 1.13,2.38 1.83,1.96"/>
<path class="st2" d="m 481.22,259.39 c -0.28,-0.56 -1.12,0 -1.82,1.4 l -2.95,6.03 c -0.7,1.4 -0.42,1.54 0.42,0.28 l 3.23,-4.35 c 0.98,-1.26 1.4,-2.66 1.12,-3.36"/>
<path class="st0" d="m 434.92,229.92 0.14,-3.51 c 0,-1.54 0.42,-1.54 0.84,-0.14 l 0.98,4.07"/>
<path class="st3" d="m 24.36,287.31 c -1.4,-0.42 -1.26,-0.7 0.28,-0.56 1.54,0.14 3.65,0.42 4.91,0.56 1.12,0.14 1.12,0.42 -0.28,0.56 -1.26,0.14 -3.5,-0.14 -4.91,-0.56"/>
<path class="st3" d="m 92.7,279.73 c -1.12,-1.12 -1.26,-0.98 -0.28,0.28 l 1.12,1.54 c 0.84,1.26 2.25,2.67 3.09,3.23 0.84,0.56 0.84,0.14 -0.14,-0.98 -0.84,-1.12 -2.53,-2.81 -3.51,-3.93 z"/>
<path class="st3" d="m 120.34,276.65 c -0.42,0 -0.14,0.98 0.42,2.25 0.56,1.26 0.84,1.26 0.7,0 -0.28,-1.27 -0.7,-2.25 -1.12,-2.25"/>
<path class="st3" d="m 160.05,290.4 c -1.12,-0.14 -0.98,-0.42 0.56,-0.7 l 1.68,-0.28 c 1.54,-0.28 2.1,-0.14 1.26,0.42 -0.7,0.42 -2.24,0.7 -3.5,0.56"/>
<path class="st3" d="m 187.69,278.89 c -0.56,-0.7 -0.7,-0.28 -0.42,1.12 0.28,1.4 1.12,3.51 1.68,4.63 0.7,1.12 0.84,0.98 0.56,-0.56 l -0.28,-1.12 c -0.28,-1.4 -0.98,-3.23 -1.54,-4.07"/>
<path class="st3" d="m 279.31,281.28 c -0.14,-0.84 -0.56,-0.7 -0.98,0.28 -0.42,1.12 -0.7,3.09 -0.7,4.49 0,1.4 0.42,1.4 0.84,0 l 0.14,-0.56 c 0.7,-1.41 0.84,-3.37 0.7,-4.21"/>
<path class="st3" d="m 248.86,275.38 c -0.28,-0.84 -0.98,-0.7 -1.54,0.28 -0.56,1.12 0.14,2.53 1.54,3.23 v 0 c 1.4,0.7 2.1,0.56 1.54,-0.42 -0.55,-0.7 -1.26,-2.24 -1.54,-3.09"/>
<path class="st3" d="m 299.94,290.82 c -1.4,-0.42 -1.26,-0.7 0.28,-0.7 h 0.56 c 1.54,0 3.93,0.42 5.19,0.84 1.4,0.42 1.26,0.84 -0.28,0.7 h -0.56 c -1.4,0 -3.79,-0.42 -5.19,-0.84"/>
<path class="st3" d="m 334.6,278.89 c -0.14,-0.56 -0.56,0 -0.98,1.12 -0.42,1.12 -0.28,1.54 0.28,0.98 0.41,-0.56 0.84,-1.54 0.7,-2.1"/>
<path class="st3" d="m 367.85,277.77 c -0.14,-0.56 -0.56,0 -0.98,1.12 -0.42,1.12 -0.28,1.54 0.28,0.98 0.42,-0.7 0.84,-1.54 0.7,-2.1"/>
<path class="st3" d="m 362.52,287.73 c -1.54,-0.28 -1.54,-0.7 0,-0.84 l 1.12,-0.14 c 1.54,-0.14 3.37,0.14 4.21,0.56 0.7,0.42 0.56,0.84 -0.56,0.84 -1.12,0 -3.23,-0.28 -4.63,-0.56 z"/>
<path class="st3" d="m 411.35,287.59 c -1.4,0.14 -1.4,-0.14 0,-0.56 1.4,-0.42 3.79,-0.7 5.19,-0.56 l 1.96,0.14 c 1.54,0.14 1.54,0.28 0,0.42 l -1.96,0.14 c -1.41,0.14 -3.79,0.42 -5.19,0.42"/>
<path class="st2" d="m 442.92,252.79 c -0.7,0.28 -1.82,1.68 -2.39,3.09 v 0 c -0.56,1.4 -0.28,1.54 0.56,0.42 l 1.4,-1.82 c 0.99,-1.13 1.13,-1.97 0.43,-1.69"/>
<path class="st0" d="m 496.52,229.22 c -0.7,0 -1.26,-1.26 -1.4,-2.81 l -0.14,-1.12 c -0.14,-1.54 0.28,-1.68 0.84,-0.28 l 0.7,1.68 c 0.7,1.41 0.7,2.53 0,2.53"/>
<path class="st0" d="m 292.92,235.39 c 0,-0.7 1.12,-1.4 2.67,-1.26 h 0.98 c 1.54,0 1.68,0.56 0.28,1.12 l -1.12,0.42 c -1.54,0.71 -2.81,0.57 -2.81,-0.28"/>
<path class="st0" d="m 217.15,238.2 c -0.56,-0.28 -0.98,0.7 -0.7,2.25 l 0.14,1.26 c 0.14,1.54 0.7,1.54 0.98,0 v -0.14 c 0.43,-1.55 0.14,-3.09 -0.42,-3.37"/>
<path class="st0" d="m 149.38,259.25 c -0.14,-0.56 0.98,-1.12 2.39,-1.12 h 5.89 c 1.54,0 1.54,0.28 0,0.56 l -5.33,1.12 c -1.4,0.28 -2.81,0 -2.95,-0.56"/>
<path class="st0" d="m 29.13,271.03 c 0.14,-0.42 -1.12,-0.84 -2.67,-0.84 h -1.4 c -1.54,0 -1.54,0.28 -0.14,0.7 l 1.26,0.28 c 1.55,0.42 2.81,0.28 2.95,-0.14"/>
<path class="st4" d="m 7.67,275.94 c -1.54,0 -2.53,0.28 -2.24,0.56 0.28,0.28 1.82,0.56 3.37,0.56 h 11.22 c 1.54,0 2.95,-0.28 3.37,-0.7 0.28,-0.42 -0.7,-0.7 -2.24,-0.56 z"/>
<path class="st4" d="m 58.74,275.94 c -1.54,0 -2.53,0.28 -2.24,0.56 0.28,0.28 1.82,0.56 3.37,0.56 h 11.22 c 1.54,0 2.95,-0.28 3.37,-0.7 0.28,-0.42 -0.7,-0.7 -2.25,-0.56 z"/>
<path class="st4" d="m 87.5,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 0.14 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -1.12,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 36.29,250.69 c -1.54,0 -2.67,0.28 -2.53,0.7 0.14,0.28 1.4,0.56 2.95,0.56 h 0.14 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -1.12,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 57.34,250.69 c -1.54,0 -2.67,0.28 -2.53,0.7 0.14,0.28 1.4,0.56 2.95,0.56 h 0.14 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -1.12,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 15.1,254.06 c -1.54,0 -2.67,0.28 -2.67,0.7 0.14,0.28 1.26,0.56 2.81,0.56 1.4,0 2.67,-0.28 2.81,-0.7 0.14,-0.42 -1.12,-0.56 -2.67,-0.56 z"/>
<path class="st4" d="m 40.5,262.76 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 5.05 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 28.85,225.85 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 5.05 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 116.83,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 0.14 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -1.12,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 167.06,275.8 c -1.54,0 -2.67,0.28 -2.67,0.7 0.14,0.28 1.26,0.56 2.81,0.56 1.4,0 2.67,-0.28 2.81,-0.7 0.14,-0.42 -1.12,-0.56 -2.67,-0.56 z"/>
<path class="st4" d="m 126.23,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 3.79 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 152.89,275.8 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 4.77 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 182.36,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 2.95,0.56 h 2.1 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 240.59,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 2.95,0.56 h 2.1 c 1.54,0 2.95,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 282.12,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 1.26 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 302.88,275.8 c -1.54,0 -2.53,0.28 -2.24,0.56 0.28,0.28 1.68,0.56 3.23,0.56 h 8.98 c 1.54,0 2.95,-0.28 3.23,-0.7 0.28,-0.42 -0.84,-0.7 -2.25,-0.56 z"/>
<path class="st4" d="m 328.42,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 0.42 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 337.68,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 3.93 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 358.03,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 2.39 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 370.09,275.8 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 0.98 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 387.21,275.8 c -1.54,0 -2.53,0.28 -2.25,0.56 0.28,0.28 1.82,0.56 3.37,0.56 h 12.49 c 1.54,0 3.09,-0.28 3.37,-0.7 0.28,-0.42 -0.7,-0.7 -2.24,-0.56 z"/>
<path class="st4" d="m 416.26,275.8 c -1.54,0 -2.53,0.28 -2.25,0.56 0.28,0.28 1.82,0.56 3.37,0.56 h 12.49 c 1.54,0 3.09,-0.28 3.37,-0.7 0.28,-0.42 -0.7,-0.7 -2.24,-0.56 z"/>
<path class="st4" d="m 489.78,275.8 c -1.54,0 -2.53,0.28 -2.24,0.56 0.28,0.28 1.82,0.56 3.37,0.56 h 12.49 c 1.54,0 3.09,-0.28 3.37,-0.7 0.28,-0.42 -0.7,-0.7 -2.24,-0.56 z"/>
<path class="st4" d="m 462.84,226.84 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 4.63 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 439.97,226.84 c -1.54,0 -2.53,0.28 -2.39,0.56 0.14,0.28 1.68,0.56 3.09,0.56 h 6.03 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 428.32,226.84 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 2.53 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 432.11,250.55 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 4.91 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 361.25,225.15 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 2.53 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 377.67,225.15 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 2.53 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 367.15,235.67 c -1.26,0 -2.25,0.28 -2.1,0.7 0.14,0.28 0.98,0.56 2.1,0.56 1.12,0 2.1,-0.28 2.1,-0.7 0,-0.41 -0.84,-0.56 -2.1,-0.56"/>
<path class="st4" d="m 373.04,232.31 c -1.54,0 -2.67,0.28 -2.67,0.7 0.14,0.28 1.26,0.56 2.67,0.56 1.4,0 2.53,-0.28 2.67,-0.7 0,-0.42 -1.13,-0.7 -2.67,-0.56 z"/>
<path class="st4" d="m 299.66,232.87 c -1.54,0 -2.67,0.28 -2.67,0.7 0.14,0.28 1.26,0.56 2.67,0.56 1.4,0 2.53,-0.28 2.67,-0.7 0.13,-0.28 -1.13,-0.56 -2.67,-0.56 z"/>
<path class="st4" d="m 318.04,250.13 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 0.42 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 305.13,256.3 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 3.79 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 488.1,257.56 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 3.79 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 183.76,262.33 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 1.54 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 157.94,257.14 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 1.54 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 121.04,256.72 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 2.95,0.56 H 123 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 65.34,236.94 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 4.07 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 170.01,227.68 c -1.54,-0.14 -2.67,0 -2.67,0.28 0.14,0.28 1.4,0.7 2.95,0.98 l 1.4,0.14 c 1.54,0.14 2.95,0.14 3.09,-0.28 0.14,-0.28 -0.98,-0.7 -2.39,-0.84 z"/>
<path class="st4" d="m 328.56,260.23 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 1.54 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 375.15,255.74 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.4,0.56 2.95,0.56 h 1.54 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 263.32,226.41 c -1.54,0 -2.39,0.28 -2.1,0.56 0.42,0.28 1.96,0.56 3.51,0.56 h 16.42 c 1.54,0 3.09,-0.28 3.51,-0.7 0.42,-0.42 -0.56,-0.7 -2.1,-0.56 z"/>
<path class="st4" d="m 207.89,226.55 c -1.54,0 -2.39,0.28 -2.1,0.56 0.42,0.28 1.96,0.56 3.51,0.56 h 17.12 c 1.54,0 3.09,-0.28 3.51,-0.7 0.42,-0.42 -0.56,-0.7 -2.1,-0.56 z"/>
<path class="st4" d="m 204.95,250.13 c -1.54,0 -2.53,0.28 -2.39,0.56 0.28,0.28 1.68,0.56 3.23,0.56 h 6.74 c 1.54,0 2.95,-0.28 3.23,-0.7 0.28,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 219.82,250.13 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.68,0.56 3.09,0.56 h 6.03 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 251.67,272.72 c -1.54,0 -2.67,0.28 -2.53,0.7 0.14,0.28 1.4,0.56 2.95,0.56 v 0 c 1.54,0 2.81,-0.28 2.95,-0.7 0.14,-0.42 -1.12,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 247.32,234.83 c -1.54,-0.14 -2.67,0 -2.53,0.28 0.14,0.28 1.4,0.7 2.95,0.98 l 2.81,0.42 c 1.54,0.14 2.95,0.14 3.09,-0.28 0.14,-0.28 -0.84,-0.7 -2.39,-0.98 z"/>
<path class="st4" d="m 386.23,260.37 c -1.54,-0.14 -2.67,0 -2.53,0.28 0.14,0.28 1.4,0.7 2.95,0.98 l 2.81,0.42 c 1.54,0.14 2.95,0.14 3.09,-0.28 0.14,-0.28 -0.84,-0.7 -2.39,-0.98 z"/>
<path class="st4" d="m 381.18,235.67 c -1.12,0 -1.96,0.28 -1.96,0.7 0,0.28 0.98,0.56 1.96,0.56 0.98,0 1.82,-0.28 1.96,-0.7 0.14,-0.41 -0.84,-0.56 -1.96,-0.56"/>
<path class="st4" d="m 446.85,250.55 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.68,0.56 3.09,0.56 h 6.03 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 500.16,226.84 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 2.81 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
<path class="st4" d="m 261.07,275.8 c -1.54,0 -2.53,0.28 -2.24,0.56 0.28,0.28 1.82,0.56 3.23,0.56 h 10.38 c 1.54,0 2.95,-0.28 3.23,-0.7 0.28,-0.42 -0.7,-0.7 -2.25,-0.56 z"/>
<path class="st4" d="m 193.16,275.8 c -1.54,0 -2.53,0.28 -2.1,0.56 0.42,0.28 1.96,0.56 3.37,0.56 h 15.43 c 1.54,0 3.09,-0.28 3.51,-0.7 0.42,-0.42 -0.56,-0.7 -2.1,-0.56 z"/>
<path class="st4" d="m 218.42,275.8 c -1.54,0 -2.67,0.28 -2.39,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 5.89 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.84,-0.56 -2.39,-0.56 z"/>
<path class="st4" d="m 98.45,275.94 c -1.54,0 -2.67,0.28 -2.53,0.56 0.14,0.28 1.54,0.56 3.09,0.56 h 2.53 c 1.54,0 2.95,-0.28 3.09,-0.7 0.14,-0.42 -0.98,-0.56 -2.53,-0.56 z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -7,9 +7,9 @@ export class Server {
readonly name: string, readonly name: string,
readonly motd: string, readonly motd: string,
readonly mode: Mode, readonly mode: Mode,
readonly serverPort: number, readonly port: number,
readonly running: boolean, readonly running: boolean,
readonly hasIcon: boolean, readonly icon: boolean,
) { ) {
// //
} }
@ -19,9 +19,9 @@ export class Server {
validateString(json.name), validateString(json.name),
validateString(json.motd), validateString(json.motd),
validateString(json.mode) as Mode, validateString(json.mode) as Mode,
validateNumber(json.serverPort), validateNumber(json.port),
validateBoolean(json.running), validateBoolean(json.running),
validateBoolean(json.hasIcon), validateBoolean(json.icon),
); );
} }

View File

@ -1,22 +1,17 @@
<div class="heading"> <div class="heading">
Serverliste <img src="minecraft.svg" alt="Minecraft">
</div>
<div class="hint">
<img src="info.svg" alt="(i)">
Beim Starten eines Servers werden alle anderen gestoppt.
</div> </div>
<div class="list"> <div class="list">
<div *ngFor="let server of servers()" class="server"> <div *ngFor="let server of servers()" class="server">
<div class="icon"> <div class="icon">
<img src="{{server.mode.toLowerCase()}}.png" alt="{{server.mode}}" *ngIf="!server.hasIcon"> <img src="{{server.mode.toLowerCase()}}.png" alt="{{server.mode}}" *ngIf="!server.icon">
<img [src]="url('http',['Server', server.name, 'icon'])" alt="{{server.mode}}" *ngIf="server.hasIcon"> <img [src]="url('http',['Server', server.name, 'icon'])" alt="{{server.mode}}" *ngIf="server.icon">
</div> </div>
<div class="name"> <div class="name">
{{ server.motd }} {{ server.motd }}
<div class="address"> <div class="address">
10.255.0.1:{{ server.serverPort }} 10.255.0.1:{{ server.port }}
</div> </div>
</div> </div>
<div class="map" *ngIf="server.name === 'survival24'"> <div class="map" *ngIf="server.name === 'survival24'">
@ -32,3 +27,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="hint">
<img src="info.svg" alt="(i)">
Beim Starten eines Servers werden alle anderen gestoppt.
</div>

View File

@ -18,6 +18,7 @@
} }
.name { .name {
color: #c8dddd;
flex-grow: 1; flex-grow: 1;
font-weight: bold; font-weight: bold;
} }

View File

@ -33,7 +33,7 @@ export class ServerListComponent extends CrudListComponent<Server, ServerService
} }
servers() { servers() {
return this.list.sort((a, b) => a.serverPort - b.serverPort); return this.list.sort((a, b) => a.port - b.port);
} }
} }

View File

@ -2,23 +2,26 @@ body {
font-family: sans-serif; font-family: sans-serif;
margin: 0; margin: 0;
font-size: 5vw; font-size: 5vw;
background-color: #2b3939;
} }
.heading { .heading {
padding: 0.25em; padding: 0.25em;
font-weight: bold; font-weight: bold;
text-align: center;
text-decoration: underline;
} }
.hint { .hint {
background-color: lightyellow; background-color: lightyellow;
border: 0.1em solid yellow; border: 0.1em solid yellow;
margin: 0.25em; margin: 0.5em;
padding: 0.25em; padding: 0.25em;
font-size: 60%; font-size: 60%;
border-radius: 0.25em; border-radius: 0.25em;
img { img {
height: 1.7em; height: 1.4em;
vertical-align: middle; vertical-align: bottom;
} }
} }

View File

@ -0,0 +1,17 @@
package de.ph87.mc.server;
import lombok.NonNull;
import java.io.File;
public class NoMinecraftServer extends Exception {
public NoMinecraftServer(final @NonNull File directory, final String string) {
super("Not a minecraft server: directory=%s, reason=%s".formatted(directory, string));
}
public NoMinecraftServer(final @NonNull File directory, final Exception e) {
super("Not a minecraft server: directory=%s".formatted(directory), e);
}
}

View File

@ -0,0 +1,45 @@
package de.ph87.mc.server;
import lombok.Data;
import lombok.NonNull;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Locale;
@Data
public class Properties {
public final String name;
public final String motd;
public final Mode mode;
public final int port;
public final int rconPort;
public final String rconPassword;
public Properties(final @NonNull File directory) throws NoMinecraftServer {
final File file = new File(directory, "server.properties");
if (!file.exists()) {
throw new NoMinecraftServer(directory, "server.properties not found");
}
final java.util.Properties properties = new java.util.Properties();
try (final FileReader reader = new FileReader(file)) {
properties.load(reader);
name = properties.getProperty("level-name");
motd = properties.getProperty("motd");
mode = Mode.valueOf(properties.getProperty("gamemode").toUpperCase(Locale.ROOT));
port = Integer.parseInt(properties.getProperty("server-port"));
rconPort = Integer.parseInt(properties.getProperty("rcon.port"));
rconPassword = properties.getProperty("rcon.password");
} catch (IOException | NumberFormatException e) {
throw new NoMinecraftServer(directory, e);
}
}
}

View File

@ -1,79 +1,55 @@
package de.ph87.mc.server; package de.ph87.mc.server;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.ph87.mc.websocket.IWebsocketMessage;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.Data; import lombok.Data;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.io.File; import java.io.File;
@Data @Data
public class Server implements IWebsocketMessage { @Slf4j
public class Server {
@NonNull @NonNull
@JsonIgnore
public final File directory; public final File directory;
@NonNull @NonNull
@JsonIgnore
public final File propertyFile;
@NonNull
@JsonIgnore
public final File pidFile; public final File pidFile;
@NonNull @NonNull
public final String name;
@NonNull
public final String motd;
@NonNull
public final Mode mode;
public final int serverPort;
@JsonIgnore
public final int rconPort;
@NonNull
@JsonIgnore
public final String rconPassword;
@JsonIgnore
public final int queryPort;
@JsonIgnore
public final File iconFile; public final File iconFile;
public final boolean hasIcon; @NonNull
public final Properties properties;
public final Object lock = new Object();
@Nullable @Nullable
private Long pid; public Process process = null;
public Server(@NonNull final File directory, @NonNull final String name, @NonNull final String motd, @NonNull final Mode mode, final int serverPort, final int rconPort, final String rconPassword, final int queryPort) { public boolean shutdown = false;
public Server(@NonNull final File directory) throws NoMinecraftServer {
this.directory = directory; this.directory = directory;
this.propertyFile = new File(directory, "server.properties");
this.pidFile = new File(directory, "pid"); this.pidFile = new File(directory, "pid");
this.iconFile = new File(directory, "McManagerIcon.png"); this.iconFile = new File(directory, "McManagerIcon.png");
this.name = name; this.properties = new Properties(directory);
this.motd = motd; }
this.mode = mode;
this.serverPort = serverPort; public boolean isRunning() {
this.rconPort = rconPort; synchronized (lock) {
this.rconPassword = rconPassword; return process != null && process.isAlive();
this.queryPort = queryPort; }
this.hasIcon = iconFile.isFile();
} }
@Override @Override
public String toString() { public String toString() {
return "Server(%s, \"%s\", %s)".formatted(mode, motd, isRunning() ? "RUNNING" : "stopped"); return "Server(%s, \"%s\", %s)".formatted(properties.mode, properties.motd, isRunning() ? "RUNNING" : "stopped");
} }
public boolean isRunning() { public boolean eq(@NonNull final Server other) {
return pid != null; return properties.name.equals(other.properties.name);
} }
} }

View File

@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -21,27 +22,28 @@ public class ServerController {
private final ServerService serverService; private final ServerService serverService;
private final ServerRepository serverRepository; @NonNull
@GetMapping("findAll") @GetMapping("findAll")
public List<Server> findAll() { public List<ServerDto> findAll() {
return serverRepository.findAll(); return serverService.findAll();
} }
@NonNull
@GetMapping("{name}/start") @GetMapping("{name}/start")
public Server start(@NonNull @PathVariable final String name) { public ServerDto start(@NonNull @PathVariable final String name) {
return serverService.start(name); return serverService.start(name);
} }
@NonNull
@GetMapping("{name}/stop") @GetMapping("{name}/stop")
public Server stop(@NonNull @PathVariable final String name) { public ServerDto stop(@NonNull @PathVariable final String name) {
return serverService.stop(name); return serverService.stop(name);
} }
@GetMapping("{name}/icon") @GetMapping("{name}/icon")
public void icon(@NonNull @PathVariable final String name, @NonNull final HttpServletResponse response) throws IOException { public void icon(@NonNull @PathVariable final String name, @NonNull final HttpServletResponse response) throws IOException {
final Server server = serverRepository.getByName(name); final File iconFile = serverService.getIconFileByName(name);
try (final FileInputStream inputStream = new FileInputStream(server.iconFile)) { try (final FileInputStream inputStream = new FileInputStream(iconFile)) {
response.getOutputStream().write(inputStream.readAllBytes()); response.getOutputStream().write(inputStream.readAllBytes());
} }
response.getOutputStream().flush(); response.getOutputStream().flush();

View File

@ -0,0 +1,31 @@
package de.ph87.mc.server;
import de.ph87.mc.websocket.IWebsocketMessage;
import lombok.Data;
import lombok.NonNull;
@Data
public class ServerDto implements IWebsocketMessage {
public final String name;
public final String motd;
public final Mode mode;
public final int port;
public final boolean running;
public boolean icon;
public ServerDto(final @NonNull Server server) {
this.name = server.properties.name;
this.motd = server.properties.motd;
this.mode = server.properties.mode;
this.port = server.properties.port;
this.running = server.isRunning();
this.icon = server.iconFile.isFile();
}
}

View File

@ -1,128 +0,0 @@
package de.ph87.mc.server;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Service
@RequiredArgsConstructor
public class ServerProcessHelper {
public static final String[] CMDLINE = {"java", "-jar", "server.jar"};
public static final String CMDLINE_STR = String.join(" ", CMDLINE);
private final ApplicationEventPublisher applicationEventPublisher;
public void updatePid(@NonNull final Server server) {
server.setPid(_readAndVerifyPid(server));
if (server.getPid() == null) {
deletePidFile(server);
}
}
@Nullable
private Long _readAndVerifyPid(@NonNull final Server server) {
if (!server.pidFile.exists()) {
return null;
}
final long pid;
try (final FileInputStream stream = new FileInputStream(server.pidFile)) {
pid = Long.parseLong(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
} catch (IOException | NumberFormatException e) {
log.error("Failed to read pid-file: file={}, error={}", server.pidFile, e.getMessage());
return null;
}
if (!validateProcFile(server, pid)) {
return null;
}
return pid;
}
private static boolean validateProcFile(@NonNull final Server server, final long pid) {
final File procFile = new File("/proc/%d/cmdline".formatted(pid));
if (!procFile.exists()) {
log.warn("Server not running: {}", server.name);
return false;
}
try (final FileInputStream stream = new FileInputStream(procFile)) {
final String cmdline = new String(stream.readAllBytes(), StandardCharsets.UTF_8).replace((char) 0, ' ').trim();
if (!CMDLINE_STR.equals(cmdline)) {
log.error("cmdline of running Server does not match: pid={}, running={}, expected={}", pid, cmdline, CMDLINE_STR);
return false;
}
} catch (IOException | NumberFormatException e) {
log.error("Failed to read proc-file: file={}, error={}", procFile, e.getMessage());
return false;
}
return true;
}
private void deletePidFile(@NonNull final Server server) {
server.setPid(null);
if (server.pidFile.delete()) {
log.info("PID-file removed: {}", server.pidFile);
applicationEventPublisher.publishEvent(server);
}
}
public void startProcess(@NonNull final Server server) {
if (server.isRunning()) {
return;
}
log.info("Starting Server: {}", server.name);
final ProcessBuilder builder = new ProcessBuilder(CMDLINE);
builder.directory(server.directory);
try {
final Process process = builder.start();
server.setPid(process.pid());
writePid(server, process.pid());
} catch (IOException e) {
log.error("Failed to start server: error={}, name={}", e.getMessage(), server.name);
}
}
public void stopProcess(@NonNull final Server server) {
if (!server.isRunning()) {
return;
}
new Thread(() -> {
try {
log.info("Stopping Server: {}", server.name);
new ProcessBuilder("kill", "-15", server.getPid() + "").start();
while (server.getPid() != null && validateProcFile(server, server.getPid())) {
//noinspection BusyWait
Thread.sleep(1000);
}
deletePidFile(server);
} catch (IOException | InterruptedException e) {
log.error("Failed to stop server: error={}, name={}", e.getMessage(), server.name);
}
}).start();
}
private void writePid(@NonNull final Server server, final long pid) throws IOException {
final File file = server.pidFile;
try (final FileOutputStream stream = new FileOutputStream(file)) {
stream.write("%d".formatted(pid).getBytes(StandardCharsets.UTF_8));
}
log.info("PID-file written: file={} = {}", server.pidFile, pid);
applicationEventPublisher.publishEvent(server);
}
}

View File

@ -1,70 +0,0 @@
package de.ph87.mc.server;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
@Slf4j
@Service
@RequiredArgsConstructor
public class ServerRepository {
private final ApplicationEventPublisher applicationEventPublisher;
private final ServerProcessHelper serverProcessHelper;
private final ServerConfig serverConfig;
@NonNull
public List<Server> findAll() {
final File ROOT = new File(serverConfig.getPath());
return Arrays.stream(Objects.requireNonNull(ROOT.listFiles())).map(this::_tryLoadingFromDir).filter(Optional::isPresent).map(Optional::get).toList();
}
@NonNull
private Optional<Server> _tryLoadingFromDir(@NonNull final File dir) {
final File file = new File(dir, "server.properties");
if (!file.isFile()) {
log.warn("Server directory without server.properties file: {}", dir);
return Optional.empty();
}
final Properties properties = new Properties();
try (final FileReader reader = new FileReader(file)) {
properties.load(reader);
final String name = properties.getProperty("level-name");
final String motd = properties.getProperty("motd");
final Mode gamemode = Mode.valueOf(properties.getProperty("gamemode").toUpperCase(Locale.ROOT));
final int serverPort = Integer.parseInt(properties.getProperty("server-port"));
final int rconPort = Integer.parseInt(properties.getProperty("rcon.port"));
final String rconPassword = properties.getProperty("rcon.password");
final int queryPort = Integer.parseInt(properties.getProperty("query.port"));
final Server server = new Server(dir, name, motd, gamemode, serverPort, rconPort, rconPassword, queryPort);
serverProcessHelper.updatePid(server);
applicationEventPublisher.publishEvent(server);
return Optional.of(server);
} catch (IOException e) {
log.error("Failed to read server.properties: file={}, error={}", file, e.getMessage());
return Optional.empty();
}
}
@NonNull
public Server getByName(@NonNull final String name) {
return findAll().stream().filter(server -> server.name.equals(name)).findFirst().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}

View File

@ -1,38 +1,151 @@
package de.ph87.mc.server; package de.ph87.mc.server;
import jakarta.annotation.PostConstruct;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class ServerService { public class ServerService {
private final ServerRepository repository; private final ApplicationEventPublisher applicationEventPublisher;
private final ServerProcessHelper serverProcessHelper; private final ServerConfig serverConfig;
private final Object lock = new Object();
private List<Server> list = new ArrayList<>();
@PostConstruct
public void startup() {
final File ROOT = new File(serverConfig.getPath());
synchronized (lock) {
list = Arrays.stream(Objects.requireNonNull(ROOT.listFiles())).map(this::_tryLoadingFromDir).filter(Optional::isPresent).map(Optional::get).toList();
}
}
@NonNull @NonNull
public Server start(@NonNull final String name) { private Optional<Server> _tryLoadingFromDir(@NonNull final File directory) {
final Server server = repository.getByName(name); try {
final Server server = new Server(directory);
list.stream().filter(server::eq).findFirst().ifPresent(old -> server.process = old.process);
return Optional.of(server);
} catch (NoMinecraftServer e) {
log.warn(e.getMessage());
}
return Optional.empty();
}
@NonNull
public ServerDto start(@NonNull final String name) {
return set(name, this::start);
}
@NonNull
public ServerDto stop(@NonNull final String name) {
return set(name, this::stop);
}
private void start(@NonNull final Server server) {
synchronized (server.lock) {
if (server.isRunning()) { if (server.isRunning()) {
return server; log.warn("Server is already running: name={}", server.properties.name);
return;
}
log.info("Starting server: name={}", server.properties.name);
final ProcessBuilder builder = new ProcessBuilder("java", "-jar", "server.jar");
builder.directory(server.directory);
try {
server.process = builder.start();
log.info("Server started: name={}", server.properties.name);
} catch (IOException e) {
log.error("Failed to start Server: name={}, error={}", server.properties.name, e.getMessage());
}
}
}
private void stop(@NonNull final Server server) {
synchronized (server.lock) {
if (!server.isRunning()) {
log.warn("Server is not running: name={}", server.properties.name);
return;
}
if (server.shutdown) {
log.warn("Server is already shutting down: name={}", server.properties.name);
return;
}
server.shutdown = true;
log.info("Stopping Server: name={}", server.properties.name);
new Thread(() -> _stop_async(server), "STOP-" + server.properties.name).start();
}
}
private void _stop_async(@NonNull final Server server) {
log.debug("Thread spawned: name={}", server.properties.name);
synchronized (server.lock) {
assert server.process != null;
log.info("Stopping server: name={}", server.properties.name);
server.process.destroy();
try {
server.process.waitFor();
log.info("Server stopped: name={}", server.properties.name);
} catch (InterruptedException e) {
log.error("Interrupted while waiting for server to stop: name={}", server.properties.name);
} finally {
server.process = null;
server.shutdown = false;
publish(server);
log.debug("Thread terminated: name={}", server.properties.name);
}
} }
repository.findAll().forEach(serverProcessHelper::stopProcess);
serverProcessHelper.startProcess(server);
return server;
} }
@NonNull @NonNull
public Server stop(@NonNull final String name) { private ServerDto set(final @NonNull String name, @NonNull final Consumer<Server> modifier) {
final Server server = repository.getByName(name); final Server server = getByName(name);
if (!server.isRunning()) { modifier.accept(server);
return server; return publish(server);
}
@NonNull
private ServerDto publish(@NonNull final Server server) {
final ServerDto dto = new ServerDto(server);
applicationEventPublisher.publishEvent(dto);
return dto;
}
@NonNull
private Server getByName(@NonNull final String name) {
synchronized (lock) {
return list.stream().filter(server -> server.properties.name.equals(name)).findFirst().orElseThrow();
}
}
@NonNull
public List<ServerDto> findAll() {
synchronized (lock) {
return list.stream().map(ServerDto::new).toList();
}
}
@NonNull
public File getIconFileByName(@NonNull final String name) {
synchronized (lock) {
return getByName(name).iconFile;
} }
serverProcessHelper.stopProcess(server);
return server;
} }
} }

View File

@ -5,7 +5,7 @@ public interface IWebsocketMessage {
default String getWebsocketDestination() { default String getWebsocketDestination() {
String name = getClass().getSimpleName(); String name = getClass().getSimpleName();
if (name.endsWith("Dto")) { if (name.endsWith("Dto")) {
return name.substring(0, name.length() - 4); return name.substring(0, name.length() - 3);
} }
return name; return name;
} }

View File

@ -16,7 +16,9 @@ public class WebSocketService {
@EventListener @EventListener
public void send(@NonNull final IWebsocketMessage message) { public void send(@NonNull final IWebsocketMessage message) {
simpMessageSendingOperations.convertAndSend(message.getWebsocketDestination(), message); final String destination = message.getWebsocketDestination();
log.debug("Websocket: destination={}", destination);
simpMessageSendingOperations.convertAndSend(destination, message);
} }
} }