Support more PDUs and create Client class
The Client class provides a nice, high-level async API for client-server communication.
This commit is contained in:
parent
38d6b2fa9b
commit
9588e88b93
@ -1,27 +1,6 @@
|
|||||||
namespace StudySystemClient {
|
namespace StudySystemClient {
|
||||||
private struct Activity {
|
|
||||||
public string subject;
|
|
||||||
public ActivityType type;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ActivityType {
|
|
||||||
EXERCISES,
|
|
||||||
READING;
|
|
||||||
|
|
||||||
public string to_string() {
|
|
||||||
switch (this) {
|
|
||||||
case EXERCISES:
|
|
||||||
return "Exercises";
|
|
||||||
case READING:
|
|
||||||
return "Reading";
|
|
||||||
default:
|
|
||||||
return "Invalid activity type";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ActivitiesView : Gtk.Box {
|
public class ActivitiesView : Gtk.Box {
|
||||||
public ActivitiesView(Connection connection) {
|
public ActivitiesView(Client client) {
|
||||||
margin_top = margin_bottom = margin_start = margin_end = 0;
|
margin_top = margin_bottom = margin_start = margin_end = 0;
|
||||||
|
|
||||||
var scrolled_window = new Gtk.ScrolledWindow();
|
var scrolled_window = new Gtk.ScrolledWindow();
|
||||||
@ -37,15 +16,15 @@ namespace StudySystemClient {
|
|||||||
scrolled_window.add_css_class("card-container");
|
scrolled_window.add_css_class("card-container");
|
||||||
|
|
||||||
var activities = new Activity[] {
|
var activities = new Activity[] {
|
||||||
{ "Linguistics", ActivityType.EXERCISES },
|
{ 2, "Linguistics", ActivityType.EXERCISES },
|
||||||
{ "Cybernetics", ActivityType.EXERCISES },
|
{ 1, "Cybernetics", ActivityType.EXERCISES },
|
||||||
{ "Linguistics", ActivityType.READING },
|
{ 2, "Linguistics", ActivityType.READING },
|
||||||
{ "Physics", ActivityType.READING },
|
{ 0, "Physics", ActivityType.READING },
|
||||||
{ "Cybernetics", ActivityType.READING },
|
{ 1, "Cybernetics", ActivityType.READING },
|
||||||
{ "Physics", ActivityType.EXERCISES },
|
{ 0, "Physics", ActivityType.EXERCISES },
|
||||||
};
|
};
|
||||||
foreach (var activity in activities) {
|
foreach (var activity in activities) {
|
||||||
var card = new ActivityCard(connection, activity);
|
var card = new ActivityCard(client, activity);
|
||||||
card_container.append(card);
|
card_container.append(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +34,7 @@ namespace StudySystemClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ActivityCard : Gtk.Frame {
|
private class ActivityCard : Gtk.Frame {
|
||||||
public ActivityCard(Connection connection, Activity activity) {
|
public ActivityCard(Client client, Activity activity) {
|
||||||
add_css_class("card");
|
add_css_class("card");
|
||||||
|
|
||||||
var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
|
var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
|
||||||
@ -63,7 +42,7 @@ namespace StudySystemClient {
|
|||||||
var text = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
|
var text = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
|
||||||
text.hexpand = true;
|
text.hexpand = true;
|
||||||
|
|
||||||
var subject = new Gtk.Label(activity.subject);
|
var subject = new Gtk.Label(activity.subject_name);
|
||||||
subject.halign = Gtk.Align.START;
|
subject.halign = Gtk.Align.START;
|
||||||
subject.add_css_class("activity-subject");
|
subject.add_css_class("activity-subject");
|
||||||
text.append(subject);
|
text.append(subject);
|
||||||
@ -84,7 +63,7 @@ namespace StudySystemClient {
|
|||||||
|
|
||||||
set_child(content);
|
set_child(content);
|
||||||
|
|
||||||
var log_session_popover = new LogSessionPopover(connection);
|
var log_session_popover = new LogSessionPopover(client);
|
||||||
log_session_popover.set_parent(button);
|
log_session_popover.set_parent(button);
|
||||||
button.clicked.connect(() => log_session_popover.popup());
|
button.clicked.connect(() => log_session_popover.popup());
|
||||||
}
|
}
|
||||||
@ -94,10 +73,10 @@ namespace StudySystemClient {
|
|||||||
private const int DEFAULT_LENGTH = 30;
|
private const int DEFAULT_LENGTH = 30;
|
||||||
|
|
||||||
private Gtk.SpinButton input;
|
private Gtk.SpinButton input;
|
||||||
private Connection connection;
|
private Client client;
|
||||||
|
|
||||||
public LogSessionPopover(Connection connection) {
|
public LogSessionPopover(Client client) {
|
||||||
this.connection = connection;
|
this.client = client;
|
||||||
|
|
||||||
var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
|
var content = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
|
||||||
|
|
||||||
@ -126,8 +105,13 @@ namespace StudySystemClient {
|
|||||||
private async void submit() {
|
private async void submit() {
|
||||||
reset();
|
reset();
|
||||||
popdown();
|
popdown();
|
||||||
yield connection.send(new Request.Ping());
|
|
||||||
stderr.printf("Got ACK\n");
|
try {
|
||||||
|
yield client.ping();
|
||||||
|
stderr.printf("Successfully pinged server\n");
|
||||||
|
} catch (ClientError e) {
|
||||||
|
stderr.printf("Error pinging server: %s\n", e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
|
24
client/src/activity.vala
Normal file
24
client/src/activity.vala
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
namespace StudySystemClient {
|
||||||
|
public struct Activity {
|
||||||
|
public int subject_id;
|
||||||
|
public string subject_name;
|
||||||
|
public ActivityType type;
|
||||||
|
public double priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActivityType {
|
||||||
|
EXERCISES,
|
||||||
|
READING;
|
||||||
|
|
||||||
|
public string to_string() {
|
||||||
|
switch (this) {
|
||||||
|
case EXERCISES:
|
||||||
|
return "Exercises";
|
||||||
|
case READING:
|
||||||
|
return "Reading";
|
||||||
|
default:
|
||||||
|
return "Invalid activity type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
client/src/client.vala
Normal file
62
client/src/client.vala
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
namespace StudySystemClient {
|
||||||
|
public errordomain ClientError {
|
||||||
|
ERROR_RESPONSE,
|
||||||
|
UNEXPECTED_RESPONSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Client {
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
public Client(string cert_dir) throws Error {
|
||||||
|
connection = new Connection(cert_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ping() throws ClientError {
|
||||||
|
var response = yield connection.send(new Request.Ping());
|
||||||
|
if (response is Response.Ack) {
|
||||||
|
return;
|
||||||
|
} else if (response is Response.Error) {
|
||||||
|
throw new ClientError.ERROR_RESPONSE(
|
||||||
|
"Error response to Ping: %s",
|
||||||
|
response.value.to_string());
|
||||||
|
} else {
|
||||||
|
throw new ClientError.UNEXPECTED_RESPONSE(
|
||||||
|
"Unexpected response to Ping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Array<Activity> list_activities()
|
||||||
|
throws ClientError {
|
||||||
|
var request = new Request.ListActivities();
|
||||||
|
var response = yield connection.send(request);
|
||||||
|
if (response is Response.Activities) {
|
||||||
|
return response.value;
|
||||||
|
} else if (response is Response.Error) {
|
||||||
|
throw new ClientError.ERROR_RESPONSE(
|
||||||
|
"Error response to ListActivities: %s",
|
||||||
|
response.value.to_string());
|
||||||
|
} else {
|
||||||
|
throw new ClientError.UNEXPECTED_RESPONSE(
|
||||||
|
"Unexpected response to ListActivities");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void log_session(int subject_id, ActivityType type,
|
||||||
|
int minutes) throws ClientError {
|
||||||
|
var timestamp = new DateTime.now_utc().to_unix();
|
||||||
|
var request = new Request.LogSession(subject_id, type,
|
||||||
|
timestamp, minutes);
|
||||||
|
var response = yield connection.send(request);
|
||||||
|
if (response is Response.Ack) {
|
||||||
|
return;
|
||||||
|
} else if (response is Response.Error) {
|
||||||
|
throw new ClientError.ERROR_RESPONSE(
|
||||||
|
"Error response to LogSession: %s",
|
||||||
|
response.value.to_string());
|
||||||
|
} else {
|
||||||
|
throw new ClientError.UNEXPECTED_RESPONSE(
|
||||||
|
"Unexpected response to LogSession");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,8 +14,8 @@ namespace StudySystemClient {
|
|||||||
css_provider,
|
css_provider,
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
|
||||||
var connection = new Connection(Config.CERT_DIR);
|
var client = new Client(Config.CERT_DIR);
|
||||||
var main_window = new MainWindow(this, connection);
|
var main_window = new MainWindow(this, client);
|
||||||
main_window.present();
|
main_window.present();
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
stderr.printf("Failed to initialize: %s\n", e.message);
|
stderr.printf("Failed to initialize: %s\n", e.message);
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
namespace StudySystemClient {
|
namespace StudySystemClient {
|
||||||
public class MainWindow : Gtk.ApplicationWindow {
|
public class MainWindow : Gtk.ApplicationWindow {
|
||||||
private Connection connection;
|
public MainWindow(Gtk.Application app, Client client) {
|
||||||
|
|
||||||
public MainWindow(Gtk.Application app, Connection connection) {
|
|
||||||
Object(application: app);
|
Object(application: app);
|
||||||
|
|
||||||
default_width = 360;
|
default_width = 360;
|
||||||
default_height = 580;
|
default_height = 580;
|
||||||
|
|
||||||
this.connection = connection;
|
|
||||||
|
|
||||||
var header_bar = new Gtk.HeaderBar();
|
var header_bar = new Gtk.HeaderBar();
|
||||||
var title = new Gtk.Label("Study System Client");
|
var title = new Gtk.Label("Study System Client");
|
||||||
title.add_css_class("title");
|
title.add_css_class("title");
|
||||||
header_bar.title_widget = title;
|
header_bar.title_widget = title;
|
||||||
set_titlebar(header_bar);
|
set_titlebar(header_bar);
|
||||||
|
|
||||||
var activities_view = new ActivitiesView(connection);
|
var activities_view = new ActivitiesView(client);
|
||||||
set_child(activities_view);
|
set_child(activities_view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,9 @@ configure_file(
|
|||||||
lib = library(
|
lib = library(
|
||||||
'study-system-client',
|
'study-system-client',
|
||||||
sources: files(
|
sources: files(
|
||||||
|
'activity.vala',
|
||||||
'activities_view.vala',
|
'activities_view.vala',
|
||||||
|
'client.vala',
|
||||||
'connection.vala',
|
'connection.vala',
|
||||||
'der.vala',
|
'der.vala',
|
||||||
'main_window.vala',
|
'main_window.vala',
|
||||||
|
@ -15,16 +15,40 @@ namespace StudySystemClient.Request {
|
|||||||
public abstract class Body {
|
public abstract class Body {
|
||||||
protected enum Tag {
|
protected enum Tag {
|
||||||
PING = 0,
|
PING = 0,
|
||||||
LIST_PRIORITIZED_ACTIVITIES = 1,
|
LIST_ACTIVITIES = 1,
|
||||||
LOG_SESSION = 2,
|
LOG_SESSION = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Der.Datum datum;
|
internal Der.Datum datum;
|
||||||
|
|
||||||
|
protected Body(Tag tag, Der.Datum datum) {
|
||||||
|
this.datum = new Der.Choice(tag, datum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Ping : Body {
|
public class Ping : Body {
|
||||||
public Ping() {
|
public Ping() {
|
||||||
datum = new Der.Choice(Tag.PING, new Der.Null());
|
base(Tag.PING, new Der.Null());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListActivities : Body {
|
||||||
|
public ListActivities() {
|
||||||
|
base(Tag.LIST_ACTIVITIES, new Der.Null());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogSession : Body {
|
||||||
|
public LogSession(int subject_id, ActivityType type,
|
||||||
|
int64 timestamp, int minutes)
|
||||||
|
{
|
||||||
|
var fields = new Der.Datum[] {
|
||||||
|
new Der.Integer(subject_id),
|
||||||
|
new Der.Enumerated((int)type),
|
||||||
|
new Der.Integer(timestamp),
|
||||||
|
new Der.Integer(minutes),
|
||||||
|
};
|
||||||
|
base(Tag.LOG_SESSION, new Der.Sequence(fields));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ namespace StudySystemClient.Response {
|
|||||||
throw new DecodeError.INVALID_RESPONSE(
|
throw new DecodeError.INVALID_RESPONSE(
|
||||||
"Response was not a SEQUENCE");
|
"Response was not a SEQUENCE");
|
||||||
}
|
}
|
||||||
if (sequence.value.length != 2) {
|
if (sequence.value.length < 2) {
|
||||||
throw new DecodeError.INVALID_RESPONSE(
|
throw new DecodeError.INVALID_RESPONSE(
|
||||||
"Response sequnce contained %u fields (expected 2)",
|
"Too few fields in Response: %u (expected 2)",
|
||||||
sequence.value.length);
|
sequence.value.length);
|
||||||
}
|
}
|
||||||
var id_datum = sequence.value[0] as Der.Integer;
|
var id_datum = sequence.value[0] as Der.Integer;
|
||||||
@ -68,7 +68,20 @@ namespace StudySystemClient.Response {
|
|||||||
public enum Value {
|
public enum Value {
|
||||||
INVALID_REQUEST = 0,
|
INVALID_REQUEST = 0,
|
||||||
INVALID_ARGUMENTS = 1,
|
INVALID_ARGUMENTS = 1,
|
||||||
SERVER_ERROR = 2,
|
SERVER_ERROR = 2;
|
||||||
|
|
||||||
|
public string to_string() {
|
||||||
|
switch (this) {
|
||||||
|
case INVALID_REQUEST:
|
||||||
|
return "Invalid request";
|
||||||
|
case INVALID_ARGUMENTS:
|
||||||
|
return "Invalid arguments";
|
||||||
|
case SERVER_ERROR:
|
||||||
|
return "Server error";
|
||||||
|
default:
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Value value { get; private set; }
|
public Value value { get; private set; }
|
||||||
@ -95,4 +108,71 @@ namespace StudySystemClient.Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Activities : Body {
|
||||||
|
public Array<Activity> value { get; private set; }
|
||||||
|
|
||||||
|
internal Activities.from_datum(Der.Datum datum) throws DecodeError {
|
||||||
|
value = new Array<Activity>();
|
||||||
|
if (datum is Der.Sequence) {
|
||||||
|
foreach (var activity_datum in datum.value)
|
||||||
|
value.append_val(activity_from_datum(activity_datum));
|
||||||
|
} else {
|
||||||
|
throw new DecodeError.INVALID_BODY(
|
||||||
|
"Activities was not a SEQUENCE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Activity activity_from_datum(Der.Datum datum)
|
||||||
|
throws DecodeError {
|
||||||
|
if (datum is Der.Sequence) {
|
||||||
|
var fields = datum.value;
|
||||||
|
if (fields.length < 4) {
|
||||||
|
throw new DecodeError.INVALID_BODY(
|
||||||
|
"Too few fields in Activity: %u (expected 4)",
|
||||||
|
fields.length);
|
||||||
|
}
|
||||||
|
var subject_id = get_int("Activity.subjectId", fields[0]);
|
||||||
|
var subject_name
|
||||||
|
= get_string("Activity.subjectName", fields[1]);
|
||||||
|
var activity_type
|
||||||
|
= get_activity_type("Activity.type", fields[2]);
|
||||||
|
var int_priority = get_int("Activity.priority", fields[3]);
|
||||||
|
var priority = (double)int_priority / 100.0;
|
||||||
|
return { subject_id, subject_name, activity_type, priority };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new DecodeError.INVALID_BODY(
|
||||||
|
"Activity was not a SEQUENCE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int get_int(string name, Der.Datum datum)
|
||||||
|
throws DecodeError {
|
||||||
|
if (datum is Der.Integer)
|
||||||
|
return (int)datum.value;
|
||||||
|
throw new DecodeError.INVALID_BODY(@"$name was not an INTEGER");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string get_string(string name, Der.Datum datum)
|
||||||
|
throws DecodeError {
|
||||||
|
if (datum is Der.Utf8String)
|
||||||
|
return datum.value;
|
||||||
|
throw new DecodeError.INVALID_BODY(
|
||||||
|
@"$name was not a UTF8String");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActivityType get_activity_type(
|
||||||
|
string name, Der.Datum datum) throws DecodeError {
|
||||||
|
if (datum is Der.Enumerated) {
|
||||||
|
var value = datum.value;
|
||||||
|
if (0 <= value <= 1)
|
||||||
|
return (ActivityType)value;
|
||||||
|
throw new DecodeError.INVALID_BODY(
|
||||||
|
"Invalid value for ActivityType: %lld", value);
|
||||||
|
}
|
||||||
|
throw new DecodeError.INVALID_BODY(
|
||||||
|
@"$name was not an ENUMERATED");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user