Skip to content

Commit b866bd2

Browse files
committed
1.55.2
1 parent 7e0676e commit b866bd2

15 files changed

Lines changed: 699 additions & 620 deletions

assets/control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: craftserversetup
2-
Version: 1.55.1
2+
Version: 1.55.2
33
Maintainer: Enderbyte Programs <enderbyte09@gmail.com>
44
Homepage: https://github.com/Enderbyte-Programs/CraftServerSetup
55
Architecture: all

changelog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
1.55.2:
2+
- Make log loader use datetimes again instead of dates, fixing a bug in server analytics (nasty nasty bug...)
3+
- Fix serveral bugs in the analytics loader that occur if your cache has 100% coverage
4+
- Split out analytics system
5+
- Fix bug where program would crash if started with no internet connection
6+
- Fix bug where cancelling a package server operation would cause a crash
17
1.55.1:
28
- Fix OOBE order
39
1.55:

scripts/crss-wine.iss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44

5-
#define MyAppVersion "1.55"
5+
#define MyAppVersion "1.55.2"
66
;The above line must be on line 5!
77

88

@@ -39,7 +39,7 @@ OutputDir=C:\python\crss\installer
3939

4040

4141

42-
OutputBaseFilename=CraftServerSetup-1.55-installer
42+
OutputBaseFilename=CraftServerSetup-1.55.2-installer
4343
;The above line MUST be on line 42
4444

4545

scripts/vf.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ VSVersionInfo(
77
ffi=FixedFileInfo(
88
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
99
# Set not needed items to zero 0. Must always contain 4 elements.
10-
filevers=(1,55,0,0),
11-
prodvers=(1,55,0,0),
10+
filevers=(1,55,2,0),
11+
prodvers=(1,55,2,0),
1212
# Contains a bitmask that specifies the valid bits 'flags'r
1313
mask=0x3f,
1414
# Contains a bitmask that specifies the Boolean attributes of the file.
@@ -32,12 +32,12 @@ VSVersionInfo(
3232
u'040904B0',
3333
[StringStruct(u'CompanyName', u'Enderbyte Programs'),
3434
StringStruct(u'FileDescription', u'Minecraft Server Maker'),
35-
StringStruct(u'FileVersion', u'1.55'),
35+
StringStruct(u'FileVersion', u'1.55.2'),
3636
StringStruct(u'InternalName', u'craftserversetup'),
3737
StringStruct(u'LegalCopyright', u'© 2023-2024 Enderbyte Programs. Some rights reserved.'),
3838
StringStruct(u'OriginalFilename', u'craftserversetup.exe'),
3939
StringStruct(u'ProductName', u'CraftServerSetup'),
40-
StringStruct(u'ProductVersion', u'1.55')])
40+
StringStruct(u'ProductVersion', u'1.55.2')])
4141
]),
4242
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
4343
]

src/analytics.py

Lines changed: 345 additions & 0 deletions
Large diffs are not rendered by default.

src/analyticsexplorer.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""The analytics explorer, a subset of server analytics that was taken out because the file was still too big"""
2+
3+
import curses
4+
import cursesplus
5+
import tempdir
6+
import jsongz
7+
import uicomponents
8+
import analyticsstructures
9+
import utils
10+
import enum
11+
import datetime
12+
13+
def server_analytics_explorer(stdscr,data:dict[int,analyticsstructures.ServerMinuteFrame]):
14+
cursesplus.displaymsg(stdscr,["Analytics Explorer","Initializing..."],False)
15+
#This function allows users to explore their analytics
16+
offset = 0
17+
datasize = len(data)-1
18+
currentzoomlevel = analyticsstructures.AnalyticsExplorerZoomLevels.MINUTE#Also passively the list chunk size
19+
currentdatatype = analyticsstructures.AnalyticsExplorerDataTypes.PLAYERCOUNT
20+
zoomlevels = {
21+
analyticsstructures.AnalyticsExplorerZoomLevels.MINUTE: "Minute (default)",
22+
analyticsstructures.AnalyticsExplorerZoomLevels.HOUR: "Hour",
23+
analyticsstructures.AnalyticsExplorerZoomLevels.DAY: "Day",
24+
analyticsstructures.AnalyticsExplorerZoomLevels.WEEK: "Week"
25+
}
26+
datatypes = {
27+
analyticsstructures.AnalyticsExplorerDataTypes.TOTALPLAYERMINUTES: "Player Minutes Spent",
28+
analyticsstructures.AnalyticsExplorerDataTypes.PLAYERCOUNT: "Online Players (default)"
29+
}
30+
ldata = list(data.values())#List representation of data to prevent performance issues?
31+
32+
td = tempdir.generate_temp_dir()
33+
34+
#ogdata = copy.deepcopy(data)
35+
#ogldata = copy.deepcopy(ldata)#For use later
36+
jsongz.write(td+"/data.json.gz",{k:analyticsstructures.serialize_smf(v) for k,v in list(data.items())})
37+
maxval = max([len(p.onlineplayers) for p in ldata])
38+
while True:
39+
my,mx = stdscr.getmaxyx()
40+
xspace = mx-1
41+
yspace = my-4#Top Bottom, and weird bug
42+
stdscr.erase()
43+
cursesplus.utils.fill_line(stdscr,0,cursesplus.set_colour(cursesplus.BLUE,cursesplus.BLUE))
44+
stdscr.addstr(0,0,"Analytics Explorer - Press Q to quit and H for help",cursesplus.set_colour(cursesplus.BLUE,cursesplus.WHITE))
45+
46+
47+
ti = 0
48+
for i in range(offset-xspace//2,offset+xspace//2):
49+
if i < 0 or i > datasize:
50+
#Write red bar
51+
#print("w")
52+
for dy in range(1,yspace+1):
53+
stdscr.addstr(dy,ti,"█",cursesplus.set_colour(cursesplus.RED,cursesplus.RED))
54+
else:
55+
seldata = ldata[i]
56+
if currentdatatype == analyticsstructures.AnalyticsExplorerDataTypes.PLAYERCOUNT:
57+
scale = int(seldata.howmanyonline()/maxval*yspace)
58+
else:
59+
scale = int(seldata.getplayerminutes()/maxval*yspace)
60+
61+
if i == offset:
62+
for p in range(1,yspace+2):
63+
stdscr.addstr(p,ti,"█",cursesplus.set_colour(cursesplus.GREEN,cursesplus.GREEN))#Central marker
64+
for p in range(yspace+1,yspace+1-scale,-1):
65+
stdscr.addstr(p,ti,"█",cursesplus.set_colour(cursesplus.CYAN,cursesplus.CYAN))
66+
else:
67+
for p in range(yspace+1,yspace+1-scale,-1):
68+
stdscr.addstr(p,ti,"█",cursesplus.set_colour(cursesplus.WHITE,cursesplus.WHITE))
69+
70+
ti += 1
71+
72+
seldata = ldata[offset]
73+
if currentdatatype == analyticsstructures.AnalyticsExplorerDataTypes.TOTALPLAYERMINUTES:
74+
stdscr.addstr(my-2,0,f"{utils.strip_datetime(analyticsstructures.get_datetime_from_minute_id(seldata.minuteid))} || {seldata.getplayerminutes()} player-minutes")
75+
else:
76+
try:
77+
stdscr.addstr(my-2,0,f"{utils.strip_datetime(analyticsstructures.get_datetime_from_minute_id(seldata.minuteid))} || {seldata.howmanyonline()} players online - {seldata.onlineplayers}")
78+
except:
79+
stdscr.addstr(my-2,0,f"{utils.strip_datetime(analyticsstructures.get_datetime_from_minute_id(seldata.minuteid))} || {seldata.howmanyonline()} players online")#Too long for list
80+
81+
ch = curses.keyname(stdscr.getch()).decode()
82+
if ch == "q":
83+
break
84+
elif ch == "h":
85+
cursesplus.displaymsg(stdscr,["KEYBINDS","q - Quit","h - Help","<- -> Scroll","END - Go to end","HOME - Go to beginning","j - Jump to time","SHIFT <-- --> - Jump hour","Ctrl <-- --> - Jump day","T - Select time unit","D - Select data type"])
86+
elif ch == "KEY_LEFT":
87+
if offset > 0:
88+
offset -= 1
89+
elif ch == "KEY_RIGHT":
90+
offset += 1
91+
elif ch == "KEY_SLEFT":
92+
if offset > 60:
93+
offset -= 60
94+
else:
95+
offset = 0
96+
elif ch == "KEY_SRIGHT":#Jump around by an hour
97+
offset += 60
98+
elif ch == "kRIT5":#ctrl left
99+
offset += 1440
100+
elif ch == "kLFT5":#ctrl right
101+
if offset > 1440:
102+
offset -= 1440
103+
else:
104+
offset = 0
105+
elif ch == "KEY_END":
106+
offset = datasize - 1
107+
elif ch == "KEY_HOME":
108+
offset = 0
109+
elif ch == "j":
110+
stdscr.clear()
111+
ndate:datetime.datetime = cursesplus.date_time_selector(stdscr,cursesplus.DateTimeSelectorTypes.DATEANDTIME,"Choose a date and time to jump to",True,False,analyticsstructures.get_datetime_from_minute_id(ldata[offset].minuteid)) # type: ignore
112+
if currentzoomlevel == analyticsstructures.AnalyticsExplorerZoomLevels.HOUR:
113+
ndate = ndate.replace(second=0,minute=0)
114+
if currentzoomlevel == analyticsstructures.AnalyticsExplorerZoomLevels.DAY or currentzoomlevel == analyticsstructures.AnalyticsExplorerZoomLevels.WEEK:
115+
ndate = ndate.replace(hour=0)
116+
nmid = analyticsstructures.get_minute_id_from_datetime(ndate)
117+
if not nmid in data:
118+
cursesplus.messagebox.showerror(stdscr,["Records do not exist for the selected date."])
119+
else:
120+
offset = list(data.keys()).index(nmid)
121+
elif ch == "t":
122+
currentzoomlevel = list(zoomlevels.keys())[uicomponents.menu(stdscr,list(zoomlevels.values()),"Choose a zoom level")]
123+
cursesplus.displaymsg(stdscr,["Generating Data"],False)
124+
#This will use ogdat because data may have been used for hour or day, which will screw it up
125+
offset = 0
126+
if currentzoomlevel == analyticsstructures.AnalyticsExplorerZoomLevels.MINUTE:
127+
data = {k:analyticsstructures.deserialize_smf(v) for k,v in list(jsongz.read(td+"/data.json.gz").items())}
128+
ldata = list(data.values())
129+
else:
130+
chunked_data:list[list[analyticsstructures.ServerMinuteFrame]] = list(utils.split_list_into_chunks(list({k:analyticsstructures.deserialize_smf(v) for k,v in list(jsongz.read(td+"/data.json.gz").items())}.values()),get_chunk_size_from_aezl(currentzoomlevel)))
131+
final_data:dict[int,analyticsstructures.ServerMinuteFrame] = {}
132+
for chunk in chunked_data:
133+
s:analyticsstructures.ServerMinuteFrame = analyticsstructures.ServerMinuteFrame(chunk[0].minuteid)
134+
#Append all unique players, and set playerminutes
135+
total_playerminutes = 0
136+
unique_players = []
137+
for minute in chunk:
138+
total_playerminutes += minute.howmanyonline()
139+
for onlineplayer in minute.onlineplayers:
140+
if onlineplayer not in unique_players:
141+
unique_players.append(onlineplayer)
142+
s.playerminutes = total_playerminutes
143+
s.onlineplayers = unique_players
144+
final_data[s.minuteid] = s
145+
data = final_data
146+
del chunked_data
147+
del final_data
148+
ldata = list(data.values())
149+
maxval = max([p.get_data(currentdatatype) for p in list(data.values())])
150+
#if currentdatatype == analyticsstructures.AnalyticsExplorerDataTypes.TOTALPLAYERMINUTES:
151+
# maxval = maxval*get_chunk_size_from_aezl(currentzoomlevel)
152+
datasize = len(data)-1
153+
154+
elif ch == "d":
155+
currentdatatype = list(datatypes.keys())[uicomponents.menu(stdscr,list(datatypes.values()),"Choose a data type")]
156+
maxval = max([p.get_data(currentdatatype) for p in ldata])
157+
#if currentdatatype == analyticsstructures.AnalyticsExplorerDataTypes.TOTALPLAYERMINUTES:
158+
# maxval = maxval*get_chunk_size_from_aezl(currentzoomlevel)
159+
if offset > datasize:
160+
offset = datasize - 1
161+
162+
163+
164+
def get_chunk_size_from_aezl(s:analyticsstructures.AnalyticsExplorerZoomLevels):
165+
return {
166+
analyticsstructures.AnalyticsExplorerZoomLevels.MINUTE:1,
167+
analyticsstructures.AnalyticsExplorerZoomLevels.HOUR:60,
168+
analyticsstructures.AnalyticsExplorerZoomLevels.DAY:1440,
169+
analyticsstructures.AnalyticsExplorerZoomLevels.WEEK:1440*7
170+
}[s]

src/analyticsstructures.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Structures and utility functions for analytics"""
2+
3+
import datetime
4+
import logutils
5+
import enum
6+
7+
def get_minute_id_from_datetime(d:datetime.datetime) -> int:
8+
return int(d.timestamp()/60//1)
9+
10+
def get_datetime_from_minute_id(t:int) -> datetime.datetime:
11+
return datetime.datetime.fromtimestamp(t*60)
12+
13+
class JLLogFrame:
14+
data:str
15+
ext:datetime.datetime
16+
def __init__(self,lowerdata:logutils.LogEntry):
17+
rtime = lowerdata.data.split(" ")[0].strip()
18+
rdate = lowerdata.logdate
19+
20+
self.ext = datetime.datetime.strptime(f"{rdate.year}-{rdate.month}-{rdate.day} {rtime[1:-1]}","%Y-%m-%d %H:%M:%S")
21+
self.data = lowerdata.data
22+
23+
class ServerMinuteFrame:
24+
minuteid:int
25+
onlineplayers:list[str] = []
26+
playerminutes:int = None#type: ignore
27+
28+
def __init__(self,minuteid):
29+
self.minuteid = minuteid
30+
def wasonline(self,name) -> bool:
31+
return name in self.onlineplayers
32+
def todatetime(self) -> datetime.datetime:
33+
return datetime.datetime.fromtimestamp(self.minuteid*60)
34+
def howmanyonline(self) -> int:
35+
return len(self.onlineplayers)
36+
def getplayerminutes(self) -> int:
37+
if self.playerminutes is None:
38+
return self.howmanyonline()
39+
else:
40+
return self.playerminutes
41+
def get_data(self,setting):
42+
if setting == AnalyticsExplorerDataTypes.PLAYERCOUNT:
43+
return self.howmanyonline()
44+
else:
45+
return self.getplayerminutes()
46+
47+
def serverminuteframe_uf(smf:ServerMinuteFrame):
48+
return f"{smf.minuteid} ({smf.todatetime()}) - {smf.onlineplayers}"
49+
50+
def serialize_smf(s:ServerMinuteFrame) -> dict:
51+
return {
52+
"id" : s.minuteid,
53+
"playerminutes" : s.playerminutes,
54+
"onlineplayers" : s.onlineplayers
55+
}
56+
57+
def deserialize_smf(s:dict) -> ServerMinuteFrame:
58+
d = ServerMinuteFrame(s["id"])
59+
d.onlineplayers = s["onlineplayers"]
60+
d.playerminutes = s["playerminutes"]
61+
return d
62+
63+
class AnalyticsExplorerZoomLevels(enum.Enum):
64+
MINUTE = 1
65+
HOUR = 60
66+
DAY = 1440
67+
WEEK = 1440*7
68+
69+
class AnalyticsExplorerDataTypes(enum.Enum):
70+
TOTALPLAYERMINUTES = 0
71+
PLAYERCOUNT = 1

src/archiveutils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ def package_server(stdscr,serverdir:str,chosenserver:int):
3333
f.write(json.dumps(sdata))
3434
#Write server data into a temporary file
3535
wdir=cursesplus.filedialog.openfolderdialog(stdscr,"Please choose a folder for the output server file")
36+
37+
if wdir is None:
38+
return
39+
3640
if os.path.isdir(wdir):
3741
wxfileout=wdir+"/"+sdata["name"]+".amc"
3842
nwait = cursesplus.PleaseWaitScreen(stdscr,["Packaging Server"])

src/internetcheck.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""A library for checking if an internet connection exists"""
2+
3+
import socket
4+
5+
def is_computer_connected_to_internet() -> bool:
6+
try:
7+
socket.create_connection(("google.com",80))
8+
9+
except:
10+
return False
11+
else:
12+
return True

src/jsongz.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Json-gzip reader and writer."""
2+
3+
import json
4+
import gzip
5+
6+
def read(path:str) -> dict:
7+
with open(path,'rb') as f:
8+
rd = f.read()
9+
return json.loads(gzip.decompress(rd).decode())
10+
def write(path:str,data:dict) -> None:
11+
with open(path,'wb+') as f:
12+
f.write(gzip.compress(json.dumps(data).encode()))

0 commit comments

Comments
 (0)