summaryrefslogtreecommitdiff
path: root/resource.c
blob: c89e18807997b84765b68f624d6fa51a6088a7cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright (C) 2021 Russell King.
// Licensed under GPL version 2. See COPYING.
#include <gio/gio.h>
#include "event-httpd.h"
#include "resource.h"

struct resource_object {
	GList *client_list;
	GString *string;
};

static struct resource_object position_object;
static struct resource_object signal_object;

static void resource_obj_send_one(gpointer data, gpointer user_data)
{
	GString *string = user_data;
	struct client *c = data;

	respond_chunk(c, string);
}

static void resource_obj_send_all(struct resource_object *obj, GString *str)
{
	g_list_foreach(obj->client_list, resource_obj_send_one, str);
}

static void resource_obj_set_string(struct resource_object *obj, GString *str)
{
	GString *old;

	old = obj->string;
	obj->string = str;
	g_string_free(old, TRUE);

	resource_obj_send_all(obj, str);
}

static void resource_obj_add_client(struct resource_object *obj,
				    struct client *c)
{
	obj->client_list = g_list_append(obj->client_list, c);
}

static void resource_obj_del_client(struct resource_object *obj,
				    struct client *c)
{
	obj->client_list = g_list_remove(obj->client_list, c);
}

static GString *object_backend_status(struct resource *r)
{
	GString *str;

	str = g_string_new("event: backend\n");
	g_string_append_printf(str,
			"data: {\"status\":\"%s\",\"timestamp\":%ld}\n\n",
			r->updater ? "connected" : "disconnected",
			r->updater_time);

	return str;
}

static int object_v1_get(struct client *c, struct resource *r)
{
	struct resource_object *obj = r->data;
	GString *str;

	respond_header(c, 200, "OK",
		       "Cache-Control: no-cache\r\n"
		       "Connection: keep-alive\r\n"
		       "Content-Type: text/event-stream; charset=UTF-8\r\n"
		       "Transfer-Encoding: chunked\r\n");

	str = object_backend_status(r);
	g_string_append(str, "retry: 10000\n");
	if (obj->string)
		g_string_append_len(str, obj->string->str,
				    obj->string->len);
	respond_chunk(c, str);
	g_string_free(str, TRUE);

	// Add this client to the object list so it receives updates
	resource_obj_add_client(obj, c);

	return 1;
}

static void object_v1_close(struct client *c, struct resource *r)
{
	struct resource_object *obj = r->data;

	// Remove this client from the object list
	resource_obj_del_client(obj, c);
}

static void object_v1_update_open(struct client *c, struct resource *r)
{
	GString *str;

	// Keep track of the latest updater
	r->updater = c;
	time(&r->updater_time);

	str = object_backend_status(r);
	resource_obj_send_all(r->data, str);
	g_string_free(str, TRUE);
}

// Update all attached clients with the new resource object data
static int object_v1_update(struct client *c, struct resource *r, const char *m)
{
	struct resource_object *obj = r->data;
	GString *string;
	char *n;

	// Only allow the latest updater to provide updates, in case we
	// end up with multiple connections. This also allows us to quickly
	// release the resources of a stale connection.
	if (r->updater != c)
		return -1;

	// Remove any trailing whitespace.
	n = g_strchomp(g_strdup(m));

	// Format the text/event-stream response
	// We prefix the string with the "data:" tag in preparation for
	// sending to via the text/event-stream connection.
	// The double newline terminates this event
	// See https://www.html5rocks.com/en/tutorials/eventsource/basics/
	string = g_string_new(NULL);
	g_string_printf(string, "data:%s\n\n", n);
	g_free(n);

	resource_obj_set_string(obj, string);

	return 0;
}

static void object_v1_update_close(struct client *c, struct resource *r)
{
	GString *str;

	// If the updater goes away, clear the data so we don't serve
	// stale data.
	if (r->updater == c) {
		r->updater = NULL;
		time(&r->updater_time);

		str = object_backend_status(r);
		resource_obj_send_all(r->data, str);
		g_string_free(str, TRUE);
	}
}

static const struct resource_ops resource_v1_ops = {
	.get = object_v1_get,
	.close = object_v1_close,
	.update_open = object_v1_update_open,
	.update = object_v1_update,
	.update_close = object_v1_update_close,
};

static struct resource resource_position1 = {
	.ops = &resource_v1_ops,
	.data = &position_object,
};

static struct resource resource_signal1 = {
	.ops = &resource_v1_ops,
	.data = &signal_object,
};

void resource_init(GHashTable *hash)
{
	g_hash_table_insert(hash, "/api/1/position", &resource_position1);
	g_hash_table_insert(hash, "/api/1/signal", &resource_signal1);
}