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