1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2003-2020 Shaun McCance <shaunm@gnome.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Author: Shaun McCance <shaunm@gnome.org>
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <gio/gio.h>
28
29 #include "yelp-document.h"
30 #include "yelp-simple-document.h"
31
32 typedef struct _Request Request;
33 struct _Request {
34 YelpDocument *document;
35 GCancellable *cancellable;
36 YelpDocumentCallback callback;
37 gpointer user_data;
38
39 gint idle_funcs;
40 };
41
42 struct _YelpSimpleDocumentPrivate {
43 GFile *file;
44 GInputStream *stream;
45 gchar *page_id;
46
47 gchar *contents;
48 gssize contents_len;
49 gssize contents_read;
50 gchar *mime_type;
51 gboolean started;
52 gboolean finished;
53
54 GSList *reqs;
55 };
56
57 #define BUFFER_SIZE 4096
58
59 G_DEFINE_TYPE_WITH_PRIVATE (YelpSimpleDocument, yelp_simple_document, YELP_TYPE_DOCUMENT)
60
61 static void yelp_simple_document_dispose (GObject *object);
62 static void yelp_simple_document_finalize (GObject *object);
63
64 static gboolean document_request_page (YelpDocument *document,
65 const gchar *page_id,
66 GCancellable *cancellable,
67 YelpDocumentCallback callback,
68 gpointer user_data,
69 GDestroyNotify notify);
70 static const gchar * document_read_contents (YelpDocument *document,
71 const gchar *page_id);
72 static void document_finish_read (YelpDocument *document,
73 const gchar *contents);
74 static gchar * document_get_mime_type (YelpDocument *document,
75 const gchar *mime_type);
76 static gboolean document_signal_all (YelpSimpleDocument *document);
77
78 static void file_info_cb (GFile *file,
79 GAsyncResult *result,
80 YelpSimpleDocument *document);
81 static void file_read_cb (GFile *file,
82 GAsyncResult *result,
83 YelpSimpleDocument *document);
84 static void stream_read_cb (GInputStream *stream,
85 GAsyncResult *result,
86 YelpSimpleDocument *document);
87 static void stream_close_cb (GInputStream *stream,
88 GAsyncResult *result,
89 YelpSimpleDocument *document);
90
91 static void request_cancel (GCancellable *cancellable,
92 Request *request);
93 static gboolean request_try_free (Request *request);
94 static void request_free (Request *request);
95
96 static void
yelp_simple_document_class_init(YelpSimpleDocumentClass * klass)97 yelp_simple_document_class_init (YelpSimpleDocumentClass *klass)
98 {
99 GObjectClass *object_class = G_OBJECT_CLASS (klass);
100 YelpDocumentClass *document_class = YELP_DOCUMENT_CLASS (klass);
101
102 object_class->dispose = yelp_simple_document_dispose;
103 object_class->finalize = yelp_simple_document_finalize;
104
105 document_class->request_page = document_request_page;
106 document_class->read_contents = document_read_contents;
107 document_class->finish_read = document_finish_read;
108 document_class->get_mime_type = document_get_mime_type;
109 }
110
111 static void
yelp_simple_document_init(YelpSimpleDocument * document)112 yelp_simple_document_init (YelpSimpleDocument *document)
113 {
114 document->priv = yelp_simple_document_get_instance_private (document);
115
116 document->priv->file = NULL;
117 document->priv->stream = NULL;
118
119 document->priv->started = FALSE;
120 document->priv->finished = FALSE;
121 document->priv->contents = NULL;
122 document->priv->mime_type = NULL;
123 }
124
125 static void
yelp_simple_document_dispose(GObject * object)126 yelp_simple_document_dispose (GObject *object)
127 {
128 YelpSimpleDocument *document = YELP_SIMPLE_DOCUMENT (object);
129
130 if (document->priv->reqs) {
131 g_slist_foreach (document->priv->reqs, (GFunc)(GCallback) request_try_free, NULL);
132 g_slist_free (document->priv->reqs);
133 document->priv->reqs = NULL;
134 }
135
136 if (document->priv->file) {
137 g_object_unref (document->priv->file);
138 document->priv->file = NULL;
139 }
140
141 if (document->priv->stream) {
142 g_object_unref (document->priv->stream);
143 document->priv->stream = NULL;
144 }
145
146 G_OBJECT_CLASS (yelp_simple_document_parent_class)->dispose (object);
147 }
148
149 static void
yelp_simple_document_finalize(GObject * object)150 yelp_simple_document_finalize (GObject *object)
151 {
152 YelpSimpleDocument *document = YELP_SIMPLE_DOCUMENT (object);
153
154 g_free (document->priv->contents);
155 g_free (document->priv->mime_type);
156 g_free (document->priv->page_id);
157
158 G_OBJECT_CLASS (yelp_simple_document_parent_class)->finalize (object);
159 }
160
161 YelpDocument *
yelp_simple_document_new(YelpUri * uri)162 yelp_simple_document_new (YelpUri *uri)
163 {
164 YelpSimpleDocument *document;
165
166 document = (YelpSimpleDocument *) g_object_new (YELP_TYPE_SIMPLE_DOCUMENT,
167 "document-uri", uri,
168 NULL);
169 document->priv->file = yelp_uri_get_file (uri);
170 document->priv->page_id = yelp_uri_get_page_id (uri);
171
172 return (YelpDocument *) document;
173 }
174
175 /******************************************************************************/
176
177 static gboolean
document_request_page(YelpDocument * document,const gchar * page_id,GCancellable * cancellable,YelpDocumentCallback callback,gpointer user_data,GDestroyNotify notify)178 document_request_page (YelpDocument *document,
179 const gchar *page_id,
180 GCancellable *cancellable,
181 YelpDocumentCallback callback,
182 gpointer user_data,
183 GDestroyNotify notify)
184 {
185 YelpSimpleDocument *simple = YELP_SIMPLE_DOCUMENT (document);
186 Request *request;
187 gboolean ret = FALSE;
188
189 request = g_slice_new0 (Request);
190
191 request->document = g_object_ref (document);
192 request->callback = callback;
193 request->user_data = user_data;
194
195 request->cancellable = g_object_ref (cancellable);
196 g_signal_connect (cancellable, "cancelled",
197 G_CALLBACK (request_cancel), request);
198
199 simple->priv->reqs = g_slist_prepend (simple->priv->reqs, request);
200
201 if (simple->priv->finished) {
202 g_idle_add ((GSourceFunc) document_signal_all, simple);
203 ret = TRUE;
204 }
205 else if (!simple->priv->started) {
206 simple->priv->started = TRUE;
207 g_file_query_info_async (simple->priv->file,
208 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
209 G_FILE_QUERY_INFO_NONE,
210 G_PRIORITY_DEFAULT,
211 NULL,
212 (GAsyncReadyCallback) file_info_cb,
213 document);
214 }
215
216 return ret;
217 }
218
219 static const gchar *
document_read_contents(YelpDocument * document,const gchar * page_id)220 document_read_contents (YelpDocument *document,
221 const gchar *page_id)
222 {
223 YelpSimpleDocument *simple = YELP_SIMPLE_DOCUMENT (document);
224
225 return (const gchar*) simple->priv->contents;
226 }
227
228 static void
document_finish_read(YelpDocument * document,const gchar * contents)229 document_finish_read (YelpDocument *document,
230 const gchar *contents)
231 {
232 YelpSimpleDocument *simple = YELP_SIMPLE_DOCUMENT (document);
233
234 if (simple->priv->reqs == NULL) {
235 g_free (simple->priv->contents);
236 simple->priv->contents = NULL;
237
238 g_free (simple->priv->mime_type);
239 simple->priv->mime_type = NULL;
240 }
241 }
242
243 static gchar *
document_get_mime_type(YelpDocument * document,const gchar * mime_type)244 document_get_mime_type (YelpDocument *document,
245 const gchar *mime_type)
246 {
247 YelpSimpleDocument *simple = YELP_SIMPLE_DOCUMENT (document);
248
249 if (simple->priv->mime_type)
250 return g_strdup (simple->priv->mime_type);
251 else
252 return NULL;
253 }
254
255 static gboolean
document_signal_all(YelpSimpleDocument * document)256 document_signal_all (YelpSimpleDocument *document)
257 {
258 GSList *cur;
259 for (cur = document->priv->reqs; cur != NULL; cur = cur->next) {
260 Request *request = (Request *) cur->data;
261 if (request->callback) {
262 request->callback (request->document,
263 YELP_DOCUMENT_SIGNAL_INFO,
264 request->user_data,
265 NULL);
266 request->callback (request->document,
267 YELP_DOCUMENT_SIGNAL_CONTENTS,
268 request->user_data,
269 NULL);
270 }
271 }
272 return FALSE;
273 }
274
275 /******************************************************************************/
276
277 static void
file_info_cb(GFile * file,GAsyncResult * result,YelpSimpleDocument * document)278 file_info_cb (GFile *file,
279 GAsyncResult *result,
280 YelpSimpleDocument *document)
281 {
282 GFileInfo *info = g_file_query_info_finish (file, result, NULL);
283 const gchar *type = g_file_info_get_attribute_string (info,
284 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
285 if (g_str_equal (type, "text/x-readme"))
286 document->priv->mime_type = g_strdup ("text/plain");
287 else
288 document->priv->mime_type = g_strdup (type);
289 g_object_unref (info);
290
291 if (document->priv->page_id) {
292 yelp_document_set_page_id (YELP_DOCUMENT (document),
293 document->priv->page_id,
294 document->priv->page_id);
295 yelp_document_set_page_id (YELP_DOCUMENT (document),
296 "//index",
297 document->priv->page_id);
298 yelp_document_set_page_id (YELP_DOCUMENT (document),
299 NULL,
300 document->priv->page_id);
301 }
302 else {
303 yelp_document_set_page_id (YELP_DOCUMENT (document), "//index", "//index");
304 yelp_document_set_page_id (YELP_DOCUMENT (document), NULL, "//index");
305 }
306
307 if (g_str_equal (document->priv->mime_type, "text/plain")) {
308 gchar *basename = g_file_get_basename (document->priv->file);
309 yelp_document_set_page_title (YELP_DOCUMENT (document), "//index", basename);
310 g_free (basename);
311 }
312
313 yelp_document_set_page_icon (YELP_DOCUMENT (document), "//index", "yelp-page-symbolic");
314
315 g_file_read_async (document->priv->file,
316 G_PRIORITY_DEFAULT,
317 NULL,
318 (GAsyncReadyCallback) file_read_cb,
319 document);
320 }
321
322 static void
file_read_cb(GFile * file,GAsyncResult * result,YelpSimpleDocument * document)323 file_read_cb (GFile *file,
324 GAsyncResult *result,
325 YelpSimpleDocument *document)
326 {
327 GError *error = NULL;
328
329 document->priv->stream = (GInputStream *) g_file_read_finish (file, result, &error);
330
331 if (document->priv->stream == NULL) {
332 GSList *cur;
333 for (cur = document->priv->reqs; cur != NULL; cur = cur->next) {
334 Request *request = (Request *) cur->data;
335 if (request->callback) {
336 GError *err = g_error_copy (error);
337 request->callback (request->document,
338 YELP_DOCUMENT_SIGNAL_ERROR,
339 request->user_data,
340 err);
341 g_error_free (err);
342 }
343 }
344 g_error_free (error);
345 return;
346 }
347
348 g_assert (document->priv->contents == NULL);
349 document->priv->contents_len = BUFFER_SIZE;
350 document->priv->contents = g_realloc (document->priv->contents,
351 document->priv->contents_len);
352 document->priv->contents[0] = '\0';
353 document->priv->contents_read = 0;
354 g_input_stream_read_async (document->priv->stream,
355 document->priv->contents,
356 BUFFER_SIZE,
357 G_PRIORITY_DEFAULT,
358 NULL,
359 (GAsyncReadyCallback) stream_read_cb,
360 document);
361 }
362
363 static void
stream_read_cb(GInputStream * stream,GAsyncResult * result,YelpSimpleDocument * document)364 stream_read_cb (GInputStream *stream,
365 GAsyncResult *result,
366 YelpSimpleDocument *document)
367 {
368 gssize bytes;
369
370 bytes = g_input_stream_read_finish (stream, result, NULL);
371 document->priv->contents_read += bytes;
372
373 if (bytes == 0) {
374 /* If the preceding read filled contents, it was extended before the
375 read that gave us zero bytes. Otherwise, there's room for this
376 byte. I'm 99.99% certain I'm right.
377 */
378 g_assert (document->priv->contents_read < document->priv->contents_len);
379 document->priv->contents[document->priv->contents_read + 1] = '\0';
380 g_input_stream_close_async (document->priv->stream,
381 G_PRIORITY_DEFAULT,
382 NULL,
383 (GAsyncReadyCallback) stream_close_cb,
384 document);
385 return;
386 }
387
388 if (document->priv->contents_read == document->priv->contents_len) {
389 document->priv->contents_len = document->priv->contents_read + BUFFER_SIZE;
390 document->priv->contents = g_realloc (document->priv->contents,
391 document->priv->contents_len);
392 }
393 g_input_stream_read_async (document->priv->stream,
394 document->priv->contents + document->priv->contents_read,
395 document->priv->contents_len - document->priv->contents_read,
396 G_PRIORITY_DEFAULT,
397 NULL,
398 (GAsyncReadyCallback) stream_read_cb,
399 document);
400 }
401
402 static void
stream_close_cb(GInputStream * stream,GAsyncResult * result,YelpSimpleDocument * document)403 stream_close_cb (GInputStream *stream,
404 GAsyncResult *result,
405 YelpSimpleDocument *document)
406 {
407 document->priv->finished = TRUE;
408 document_signal_all (document);
409 }
410
411 /******************************************************************************/
412
413 static void
request_cancel(GCancellable * cancellable,Request * request)414 request_cancel (GCancellable *cancellable, Request *request)
415 {
416 GSList *cur;
417 YelpSimpleDocument *document = (YelpSimpleDocument *) request->document;
418
419 g_assert (document != NULL && YELP_IS_SIMPLE_DOCUMENT (document));
420
421 for (cur = document->priv->reqs; cur != NULL; cur = cur->next) {
422 if (cur->data == request) {
423 document->priv->reqs = g_slist_delete_link (document->priv->reqs, cur);
424 break;
425 }
426 }
427 request_try_free (request);
428 }
429
430 static gboolean
request_try_free(Request * request)431 request_try_free (Request *request)
432 {
433 if (!g_cancellable_is_cancelled (request->cancellable))
434 g_cancellable_cancel (request->cancellable);
435
436 if (request->idle_funcs == 0)
437 request_free (request);
438 else
439 g_idle_add ((GSourceFunc) request_try_free, request);
440 return FALSE;
441 }
442
443 static void
request_free(Request * request)444 request_free (Request *request)
445 {
446 g_object_unref (request->document);
447 g_object_unref (request->cancellable);
448
449 g_slice_free (Request, request);
450 }
451