Skip to content

Commit 371fc20

Browse files
authored
Merge pull request #13 from Deophius/prodtest
Doing production testing, preparing for v3.1 release
2 parents 96172c6 + a3da073 commit 371fc20

10 files changed

Lines changed: 265 additions & 90 deletions

File tree

client/gui_common.py

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
# Separating them from dbgui.pyw is because a py extension allows imports.
33
from tkinter import *
44
import dbclient, sys
5+
from tkinter.messagebox import *
56

67
__all__ = 'IPAsker LessonPicker Reporter'.split()
78

89
try:
910
import json
1011
config = json.load(open('cli.json', encoding = 'utf-8'))
1112
except FileNotFoundError:
12-
from tkinter.messagebox import showerror
1313
showerror('No config', 'Please add configuration file cli.json')
1414
sys.exit(1)
1515

@@ -36,7 +36,7 @@ def make_widgets(self):
3636
self.entry.focus()
3737
self.entry.bind('<Return>', lambda event: self.callback())
3838
f2 = Frame(self)
39-
self.label2 = Label(self, text = 'No machine data yet', font = 'Consolas')
39+
self.label2 = Label(self, text = 'No machine data yet', font = 'Consolas', foreground = 'green')
4040
self.br = Entry(f2, font = 'Consolas')
4141
self.br.insert(0, config['defhost'])
4242
self.br.bind('<Return>', lambda e: self.callback())
@@ -50,9 +50,8 @@ def make_widgets(self):
5050
self.label2.pack(side = TOP)
5151

5252
def callback(self):
53-
from tkinter.messagebox import showerror
5453
try:
55-
self.label2.config(text = 'Pending reply...')
54+
self.label2.config(text = 'Pending reply...', foreground = 'orange')
5655
self.label2.update()
5756
self.host = self.br.get()
5857
self.machine_data = dbclient.get_machine_data(self.entry.get(), self.host)
@@ -65,7 +64,7 @@ def callback(self):
6564
except dbclient.RequestFailed as ex:
6665
showerror('Request failed', ex.args[1])
6766
# No matter what failure it is, this line will be executed.
68-
self.label2.config(text = 'None machine data yet')
67+
self.label2.config(text = 'None machine data yet', foreground = 'red')
6968
self.label2.update()
7069

7170
class LessonPicker(Frame):
@@ -87,7 +86,8 @@ def __init__(self, lessons, host, master = None, **options):
8786
self.__make_widgets(lessons)
8887

8988
def __make_widgets(self, lessons):
90-
Label(self, text = "Please choose a lesson", font = 'Consolas').pack(side = TOP)
89+
self.__label = Label(self, text = "Please choose a lesson", font = 'Consolas', foreground = 'green')
90+
self.__label.pack(side = TOP)
9191
self.__var = IntVar()
9292
# This is actually a bunch of radio buttons
9393
for num, lesson in enumerate(lessons):
@@ -105,7 +105,7 @@ def __make_widgets(self, lessons):
105105
ok_button = Button(self, text = 'OK', command = self.__onlesson, font = "Consolas")
106106
ok_button.bind_all('<Return>', lambda event: self.__onlesson())
107107
ok_button.pack(side = TOP)
108-
Label(self, text = "\nMisc operations:", font = "Consolas").pack(side = TOP)
108+
Label(self, text = "\nMisc operations:", font = "Consolas", foreground = 'green').pack(side = TOP)
109109
pause_button = Button(self, text = "Pause watchdog", command = self.__on_pause_dog, font = "Consolas")
110110
pause_button.bind_all('p', lambda event: self.__on_pause_dog())
111111
pause_button.pack()
@@ -117,9 +117,9 @@ def __make_widgets(self, lessons):
117117
news_button.pack()
118118

119119
def __onlesson(self):
120-
from tkinter.messagebox import showwarning
121120
if self.__var.get() == -1:
122121
showwarning('Warning', 'You have to select a lesson!')
122+
self.__label.configure(foreground = 'red', text = 'Please choose a lesson')
123123
return
124124
# Not -1, something meaningful
125125
self.sessid = self.__var.get()
@@ -129,13 +129,28 @@ def __onlesson(self):
129129
self.quit()
130130

131131
def __on_pause_dog(self):
132-
dbclient.doggie_stick(self.__host, True)
132+
try:
133+
dbclient.doggie_stick(self.__host, True)
134+
self.__label.configure(text = "Paused watchdog", foreground = 'green')
135+
except dbclient.RequestFailed as ex:
136+
self.__label.configure(text = "Fail to pause dog", foreground = 'red')
137+
showerror('Request failed', ex.args[1])
133138

134139
def __on_resume_dog(self):
135-
dbclient.doggie_stick(self.__host, False)
140+
try:
141+
dbclient.doggie_stick(self.__host, False)
142+
self.__label.configure(text = "Resumed watchdog", foreground = 'green')
143+
except dbclient.RequestFailed as ex:
144+
self.__label.configure("Failed to resume watchdog", foreground = 'red')
145+
showerror('Request failed', ex.args[1])
136146

137147
def __on_flush_notice(self):
138-
dbclient.flush_notice(self.__host)
148+
try:
149+
dbclient.flush_notice(self.__host)
150+
self.__label.configure(text = 'Flushed notice!', foreground = 'green')
151+
except dbclient.RequestFailed as ex:
152+
self.__label.configure(text = 'Failed to flush notice', foreground = 'red')
153+
showerror('Request failed', ex.args[1])
139154

140155
class Reporter(Frame):
141156
''' Report absent and ask who will sign in. Return value in self.signin_names,
@@ -158,8 +173,7 @@ def __init__(self, host, sessid, master = None, **options):
158173

159174
def __getdata(self):
160175
''' Gets data. Stores list of absent people in self.__absent_names '''
161-
from tkinter.messagebox import askretrycancel, showerror
162-
self.__label.config(text = 'Please wait a sec...')
176+
self.__label.config(text = 'Please wait a sec...', foreground = 'orange')
163177
self.update()
164178
# User reply for retry, defaults to False, so when nothing special occurs,
165179
# doesn't retry by default
@@ -179,11 +193,10 @@ def __getdata(self):
179193
sys.exit(0)
180194

181195
def __showdata(self):
182-
from tkinter.messagebox import showinfo
183196
if len(self.__absent_names) == 0:
184197
showinfo('Good job!', 'Everybody has signed in! I might as well go back to sleep!')
185198
sys.exit(0)
186-
self.__label.config(text = 'These people didn\'t DK:')
199+
self.__label.config(text = 'These people didn\'t DK:', foreground = 'green')
187200
f = Frame(self)
188201
self.__listbox = Listbox(f, selectmode = EXTENDED, font = ('YaHei', 20))
189202
self.__listbox.insert(END, *self.__absent_names)
@@ -214,7 +227,7 @@ def __showdata(self):
214227

215228
def __write(self):
216229
''' Underlying writer.'''
217-
from tkinter.messagebox import showerror, showinfo
230+
self.__label.config(text = 'Sending write request to server', foreground = 'orange')
218231
try:
219232
dbclient.write_record(
220233
self.__sessid,
@@ -223,29 +236,31 @@ def __write(self):
223236
)
224237
except dbclient.RequestFailed as ex:
225238
showerror('Request failed', ex.args[1] + '\nPlease try again.')
239+
self.__label.config(foreground='red')
226240
except BaseException as ex:
227241
showerror('Unknown error', str(type(ex)) + '\n' + str(ex.args))
242+
self.__label.config(foreground='red')
243+
else:
244+
self.__label.config(foreground = 'green')
228245
self.__getdata()
229246
if len(self.__absent_names) == 0:
230247
showinfo('Good job!', 'Everybody has signed in! I might as well go back to sleep!')
231248
sys.exit(0)
232249
self.__listbox.delete(0, END)
233250
self.__listbox.insert(END, *self.__absent_names)
234-
self.__label.config(text = 'Last call OK! Choose more:')
251+
self.__label.config(text = 'Choose more:')
235252

236253
def __refresh(self):
237-
from tkinter.messagebox import showinfo
238254
self.__getdata()
239255
if len(self.__absent_names) == 0:
240256
showinfo('Good job!', 'Everybody has signed in! I might as well go back to sleep!')
241257
sys.exit(0)
242258
self.__listbox.delete(0, END)
243259
self.__listbox.insert(END, *self.__absent_names)
244-
self.__label.config(text = 'Refreshed! Choose more:')
260+
self.__label.config(text = 'Refreshed! Choose more:', foreground = 'green')
245261

246262
def __restart(self):
247263
''' Does the communication and restarts GS terminal. '''
248-
from tkinter.messagebox import showerror, askyesnocancel
249264
if not askyesnocancel('Restart?', 'Really restart terminal?'):
250265
return
251266
try:
@@ -257,6 +272,7 @@ def __restart(self):
257272
else:
258273
self.destroy()
259274
self.quit()
275+
self.__label.config(text = 'Restart failed', foreground = 'red')
260276

261277
def __find_abbrev(self):
262278
''' Finds the initials in self.__quick_find and selects it.
@@ -269,13 +285,13 @@ def __find_abbrev(self):
269285
# We have already checked in showdata() that the config is there.
270286
# Just assume that it was written correctly.
271287
if abbrev not in config['yearbook']:
272-
self.__label.configure(text = 'Abbrev not in config!')
288+
self.__label.configure(text = f'{abbrev} not in config!', foreground = 'red')
273289
return
274290
try:
275291
index = self.__absent_names.index(config['yearbook'][abbrev])
276292
except ValueError:
277-
self.__label.configure(text = 'Not in absent list')
293+
self.__label.configure(text = f'{abbrev} not in absent list', foreground = 'red')
278294
return
279295
self.__listbox.selection_set(index)
280296
self.__listbox.yview_moveto(index / len(self.__absent_names))
281-
self.__label.configure(text = f'Selected {abbrev}')
297+
self.__label.configure(text = f'Selected {abbrev}', foreground = 'green')

cppser/app.cpp

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ namespace Spirit {
3838
error_dialog("Type error", entry + " should be an int!"s);
3939
return false;
4040
}
41+
if (config[entry] <= 0) {
42+
error_dialog("Value error", entry + " should be positive!"s);
43+
return false;
44+
}
4145
return true;
4246
};
4347
auto check_str = [&config](const char* entry) {
@@ -53,36 +57,71 @@ namespace Spirit {
5357
};
5458
return check_int("gs_port") && check_int("serv_port") && check_str("url_stu_new")
5559
&& check_str("dbname") && check_str("passwd") && check_str("intro")
56-
&& check_int("watchdog_poll") && check_int("retry_wait") && check_db(config);
60+
&& check_int("watchdog_poll") && check_int("retry_wait") && check_int("keep_logs");
5761
}
5862

5963
void error_dialog(std::string_view caption, std::string_view text) {
6064
::MessageBox(NULL, text.data(), caption.data(), MB_ICONERROR);
6165
}
66+
67+
void kill_lock_mouse() {
68+
std::string cmd = "taskkill.exe /f /im LockMouse.exe";
69+
STARTUPINFO start;
70+
::ZeroMemory(&start, sizeof(start));
71+
start.cb = sizeof(STARTUPINFO);
72+
PROCESS_INFORMATION proc_info;
73+
::ZeroMemory(&proc_info, sizeof(proc_info));
74+
::CreateProcessA(
75+
NULL, cmd.data(), NULL, NULL, TRUE,
76+
CREATE_NO_WINDOW, NULL, NULL, &start, &proc_info
77+
);
78+
// Wait until child process exits.
79+
::WaitForSingleObject(proc_info.hProcess, INFINITE);
80+
// Close process and thread handles.
81+
::CloseHandle(proc_info.hProcess);
82+
::CloseHandle(proc_info.hThread);
83+
}
6284
}
6385

6486
int main() {
6587
using namespace Spirit;
6688
hide_window();
6789
Configuration config;
68-
std::ifstream istr("man.json");
69-
if (!istr) {
70-
error_dialog("Config error", "Cannot open the configuration!");
71-
return 1;
72-
}
73-
try {
74-
istr >> config;
75-
} catch (const decltype(config)::parse_error& ex) {
76-
error_dialog("Config error", ex.what());
77-
return 1;
78-
}
79-
istr.close();
80-
if (!validate(config)) {
81-
error_dialog("Config error", "Error with the man.json configuration file!");
82-
return 1;
90+
{
91+
// Put these into a new scope to ensure the log will be closed when entering singer
92+
// First override the old contents here. The singer will open a new config.
93+
Logfile logfile("startup.log");
94+
logfile << "About to kill the lock mouse" << std::endl;
95+
kill_lock_mouse();
96+
logfile << "Called kill_lock_mouse, last error was " << ::GetLastError() << std::endl;
97+
std::ifstream istr("man.json");
98+
if (!istr) {
99+
logfile << "Cannot open configuration file, exiting.\n";
100+
error_dialog("Config error", "Cannot open the configuration!");
101+
return 1;
102+
}
103+
try {
104+
istr >> config;
105+
} catch (const decltype(config)::parse_error& ex) {
106+
error_dialog("Config error", ex.what());
107+
logfile << "Config error: " << ex.what();
108+
return 1;
109+
}
110+
istr.close();
111+
if (!validate(config)) {
112+
error_dialog("Config error", "Error with the man.json configuration file!");
113+
logfile << "Config error: didn't pass the validator test\n";
114+
return 1;
115+
}
116+
if (!check_db(config))
117+
logfile << "Warning: database is corrupt!\n";
83118
}
119+
// Now we can be absolutely sure that keep_logs exist and is larger than 0.
120+
auto logname = select_logfile("singer", config["keep_logs"]);
121+
std::filesystem::rename("startup.log", logname);
122+
Logfile logfile(logname, std::ios::out | std::ios::app);
84123
Watchdog watchdog(config);
85124
Singer singer(config);
86125
watchdog.start();
87-
singer.mainloop(watchdog);
126+
singer.mainloop(watchdog, logfile);
88127
}

cppser/app.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ namespace Spirit {
1515

1616
// Displays an error message.
1717
void error_dialog(std::string_view caption, std::string_view text);
18+
19+
// Kills the lock mouse "utility".
20+
void kill_lock_mouse();
1821
}
1922

2023
// Main function for the spirit program.

cppser/dog_helper.cpp

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "singd.h"
2+
#include <array>
3+
#include <algorithm>
24
#include <boost/asio.hpp>
3-
#include <exception>
5+
#include <iterator>
46
#include <memory>
57
#include <regex>
68

@@ -142,19 +144,53 @@ namespace Spirit {
142144
throw NetworkError("execute_request timed out.");
143145
}
144146

145-
void send_to_gs(const Configuration& config, Logfile& log, const std::string& msg) {
147+
static void send_to_gs_impl(int gs_port, std::string msg, std::shared_ptr<std::promise<std::string>> prom) {
146148
namespace asio = boost::asio;
147149
asio::io_context ioc;
148150
asio::ip::udp::socket socket(ioc);
149151
socket.open(asio::ip::udp::v4());
150-
asio::ip::udp::endpoint addr(
151-
asio::ip::address::from_string("127.0.0.1"), config["gs_port"].get<int>()
152-
);
152+
asio::ip::udp::endpoint addr(asio::ip::address::from_string("127.0.0.1"), gs_port);
153+
std::exception_ptr exptr;
153154
try {
154-
socket.send_to(asio::buffer(msg), addr);
155+
socket.send_to(asio::buffer(std::move(msg)), addr);
156+
std::array<char, 128> buff;
157+
asio::ip::udp::endpoint endpoint;
158+
auto n = socket.receive_from(asio::buffer(buff), endpoint);
159+
prom->set_value(std::string(buff.begin(), n));
160+
return;
155161
} catch (const boost::system::system_error& ex) {
156-
log << ex.what() << '\n';
157-
throw ex;
162+
try {
163+
if (ex.code().value() == asio::error::connection_reset)
164+
throw NetworkError("Connection was reset, maybe GS not up?");
165+
else
166+
throw NetworkError("Network error " + std::to_string(ex.code().value()));
167+
} catch (const NetworkError& ex2) {
168+
// Set it to the promise
169+
try {
170+
prom->set_exception(std::current_exception());
171+
} catch (...) {
172+
// Nothing to do.
173+
}
174+
}
158175
}
159176
}
177+
178+
void send_to_gs(const Configuration& config, Logfile& log, const std::string& msg) {
179+
log << "Sending message to GS: " << msg << '\n';
180+
using Promise = std::promise<std::string>;
181+
std::shared_ptr<Promise> prom(new Promise());
182+
auto fut = prom->get_future();
183+
// Launch the detached thread
184+
std::thread(&send_to_gs_impl, config["gs_port"].get<int>(), msg, prom).detach();
185+
auto stat = fut.wait_for(std::chrono::seconds(2));
186+
if (stat == std::future_status::ready) {
187+
// This line might throw NetworkError
188+
std::string_view line = std::move(fut.get());
189+
if (line.substr(0, 7) != "success")
190+
throw GSError(line.data());
191+
// Success, return
192+
return;
193+
} else
194+
throw NetworkError("Sending to GS timed out");
195+
}
160196
}

0 commit comments

Comments
 (0)