1 /*
2 * This file is part of GNOME Twitch - 'Enjoy Twitch on your GNU/Linux desktop'
3 * Copyright © 2017 Vincent Szolnoky <vinszent@vinszent.com>
4 *
5 * GNOME Twitch is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GNOME Twitch 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
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GNOME Twitch. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <glib/gstdio.h>
20 #include <glib.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include "utils.h"
24 #include "config.h"
25 #include "gt-win.h"
26
27 #define TAG "Utils"
28 #include "gnome-twitch/gt-log.h"
29
30 gpointer
utils_value_ref_sink_object(const GValue * val)31 utils_value_ref_sink_object(const GValue* val)
32 {
33 if (val == NULL || !G_VALUE_HOLDS_OBJECT(val) || g_value_get_object(val) == NULL)
34 return NULL;
35 else
36 return g_object_ref_sink(g_value_get_object(val));
37 }
38
39 gchar*
utils_value_dup_string_allow_null(const GValue * val)40 utils_value_dup_string_allow_null(const GValue* val)
41 {
42 if (g_value_get_string(val))
43 return g_value_dup_string(val);
44
45 return NULL;
46 }
47
48 void
utils_container_clear(GtkContainer * cont)49 utils_container_clear(GtkContainer* cont)
50 {
51 for(GList* l = gtk_container_get_children(cont);
52 l != NULL; l = l->next)
53 {
54 gtk_container_remove(cont, GTK_WIDGET(l->data));
55 }
56 }
57
58 guint64
utils_timestamp_filename(const gchar * filename,GError ** error)59 utils_timestamp_filename(const gchar* filename, GError** error)
60 {
61 RETURN_VAL_IF_FAIL(!utils_str_empty(filename), 0);
62
63 GError* err = NULL; /* NOTE: Doesn't need to be freed because we propagate it */
64
65 if (g_file_test(filename, G_FILE_TEST_EXISTS))
66 {
67 GTimeVal time;
68
69 g_autoptr(GFile) file = g_file_new_for_path(filename);
70
71 return utils_timestamp_file(file, error);
72 }
73
74 RETURN_VAL_IF_REACHED(0);
75 }
76
77 guint64
utils_timestamp_file(GFile * file,GError ** error)78 utils_timestamp_file(GFile* file, GError** error)
79 {
80 g_autoptr(GFileInfo) info = g_file_query_info(file,
81 G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE,
82 NULL, error);
83 GTimeVal time;
84
85 if (error)
86 {
87 g_prefix_error(error, "Could not timestamp file because: ");
88
89 WARNING("%s", (*error)->message);
90
91 return 0;
92 }
93
94 g_file_info_get_modification_time(info, &time);
95
96 return time.tv_sec;
97 }
98
99 gint64
utils_timestamp_now(void)100 utils_timestamp_now(void)
101 {
102 gint64 timestamp;
103 GDateTime* now;
104
105 now = g_date_time_new_now_utc();
106 timestamp = g_date_time_to_unix(now);
107 g_date_time_unref(now);
108
109 return timestamp;
110 }
111
112 void
utils_pixbuf_scale_simple(GdkPixbuf ** pixbuf,gint width,gint height,GdkInterpType interp)113 utils_pixbuf_scale_simple(GdkPixbuf** pixbuf, gint width, gint height, GdkInterpType interp)
114 {
115 if (!*pixbuf)
116 return;
117
118 GdkPixbuf* tmp = gdk_pixbuf_scale_simple(*pixbuf, width, height, interp);
119 g_clear_object(pixbuf);
120 *pixbuf = tmp;
121 }
122
123 guint64
utils_http_full_date_to_timestamp(const char * string)124 utils_http_full_date_to_timestamp(const char* string)
125 {
126 gint64 ret = G_MAXINT64;
127 g_autoptr(SoupDate) date = NULL;
128
129 date = soup_date_new_from_string(string);
130
131 RETURN_VAL_IF_FAIL(date != NULL, ret);
132
133 ret = soup_date_to_time_t(date);
134
135 return ret;
136 }
137
138 const gchar*
utils_search_key_value_strv(gchar ** strv,const gchar * key)139 utils_search_key_value_strv(gchar** strv, const gchar* key)
140 {
141 if (!strv)
142 return NULL;
143
144 for (gchar** s = strv; *s != NULL; s += 2)
145 {
146 if (g_strcmp0(*s, key) == 0)
147 return *(s+1);
148 }
149
150 return NULL;
151 }
152
153 static gboolean
utils_mouse_hover_enter_cb(GtkWidget * widget,GdkEvent * evt,gpointer udata)154 utils_mouse_hover_enter_cb(GtkWidget* widget,
155 GdkEvent* evt,
156 gpointer udata)
157 {
158 GdkWindow* win;
159 GdkDisplay* disp;
160 GdkCursor* cursor;
161
162 win = ((GdkEventMotion*) evt)->window;
163 disp = gdk_window_get_display(win);
164 cursor = gdk_cursor_new_for_display(disp, GDK_HAND2);
165
166 gdk_window_set_cursor(win, cursor);
167
168 g_object_unref(cursor);
169
170 return FALSE;
171 }
172
173 static gboolean
utils_mouse_hover_leave_cb(GtkWidget * widget,GdkEvent * evt,gpointer udata)174 utils_mouse_hover_leave_cb(GtkWidget* widget,
175 GdkEvent* evt,
176 gpointer udata)
177 {
178 GdkWindow* win;
179 GdkDisplay* disp;
180 GdkCursor* cursor;
181
182 win = ((GdkEventMotion*) evt)->window;
183 disp = gdk_window_get_display(win);
184 cursor = gdk_cursor_new_for_display(disp, GDK_LEFT_PTR);
185
186 gdk_window_set_cursor(win, cursor);
187
188 g_object_unref(cursor);
189
190 return FALSE;
191 }
192
193 gboolean
utils_str_empty(const gchar * str)194 utils_str_empty(const gchar* str)
195 {
196 return !(str && strlen(str) > 0);
197 }
198
199 gchar*
utils_str_capitalise(const gchar * str)200 utils_str_capitalise(const gchar* str)
201 {
202 g_assert_false(utils_str_empty(str));
203
204 gchar* ret = g_strdup_printf("%c%s", g_ascii_toupper(*str), str+1);
205
206 return ret;
207 }
208
209 typedef struct
210 {
211 gpointer instance;
212 GCallback cb;
213 gpointer udata;
214 } OneshotData;
215
216 static void
oneshot_cb(OneshotData * data)217 oneshot_cb(OneshotData* data)
218 {
219 g_signal_handlers_disconnect_by_func(data->instance,
220 data->cb,
221 data->udata);
222 g_signal_handlers_disconnect_by_func(data->instance,
223 oneshot_cb,
224 data);
225 }
226
227 void
utils_signal_connect_oneshot(gpointer instance,const gchar * signal,GCallback cb,gpointer udata)228 utils_signal_connect_oneshot(gpointer instance,
229 const gchar* signal,
230 GCallback cb,
231 gpointer udata)
232 {
233 OneshotData* data = g_new(OneshotData, 1);
234
235 data->instance = instance;
236 data->cb = cb;
237 data->udata = udata;
238
239 g_signal_connect(instance, signal, cb, udata);
240
241 g_signal_connect_data(instance,
242 signal,
243 G_CALLBACK(oneshot_cb),
244 data,
245 (GClosureNotify) g_free,
246 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
247 }
248
249 void
utils_signal_connect_oneshot_swapped(gpointer instance,const gchar * signal,GCallback cb,gpointer udata)250 utils_signal_connect_oneshot_swapped(gpointer instance,
251 const gchar* signal, GCallback cb, gpointer udata)
252 {
253
254 OneshotData* data = g_new(OneshotData, 1);
255
256 data->instance = instance;
257 data->cb = cb;
258 data->udata = udata;
259
260 g_signal_connect_swapped(instance, signal, cb, udata);
261
262 g_signal_connect_data(instance,
263 signal,
264 G_CALLBACK(oneshot_cb),
265 data,
266 (GClosureNotify) g_free,
267 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
268 }
269
270 inline void
utils_refresh_cancellable(GCancellable ** cancel)271 utils_refresh_cancellable(GCancellable** cancel)
272 {
273 if (*cancel && !g_cancellable_is_cancelled(*cancel))
274 g_cancellable_cancel(*cancel);
275 g_clear_object(cancel);
276 *cancel = g_cancellable_new();
277 }
278
279 static void
weak_ref_notify_cb(gpointer udata,GObject * obj)280 weak_ref_notify_cb(gpointer udata, GObject* obj) /* NOTE: This object is no longer valid, can only use the addres */
281 {
282 RETURN_IF_FAIL(udata != NULL);
283
284 g_weak_ref_set(udata, NULL);
285 }
286
287 GWeakRef*
utils_weak_ref_new(gpointer obj)288 utils_weak_ref_new(gpointer obj)
289 {
290 GWeakRef* ref = g_malloc(sizeof(GWeakRef));
291
292 g_weak_ref_init(ref, obj);
293 g_object_weak_ref(obj, weak_ref_notify_cb, ref);
294
295 return ref;
296 }
297
298 void
utils_weak_ref_free(GWeakRef * ref)299 utils_weak_ref_free(GWeakRef* ref)
300 {
301 g_autoptr(GObject) obj = g_weak_ref_get(ref);
302
303 if (obj)
304 g_object_weak_unref(obj, weak_ref_notify_cb, ref);
305
306 g_weak_ref_clear(ref);
307 g_free(ref);
308 }
309
310 GDateTime*
utils_parse_time_iso_8601(const gchar * time,GError ** error)311 utils_parse_time_iso_8601(const gchar* time, GError** error)
312 {
313 GDateTime* ret = NULL;
314
315 gint year, month, day, hour, min, sec;
316
317 gint scanned = sscanf(time, "%d-%d-%dT%d:%d:%dZ", &year, &month, &day, &hour, &min, &sec);
318
319 if (scanned != 6)
320 {
321 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_PARSING_TIME,
322 "Unable to parse time from input '%s'", time);
323 }
324 else
325 ret = g_date_time_new_utc(year, month, day, hour, min, sec);
326
327 return ret;
328 }
329
330 GenericTaskData*
generic_task_data_new()331 generic_task_data_new()
332 {
333 return g_slice_new0(GenericTaskData);
334 }
335
336 void
generic_task_data_free(GenericTaskData * data)337 generic_task_data_free(GenericTaskData* data)
338 {
339 g_free(data->str_1);
340 g_free(data->str_2);
341 g_free(data->str_3);
342
343 g_slice_free(GenericTaskData, data);
344 }
345
346 JsonReader*
utils_parse_json(const gchar * data,GError ** error)347 utils_parse_json(const gchar* data, GError** error)
348 {
349 g_autoptr(JsonReader) reader = NULL;
350 g_autoptr(JsonParser) parser = NULL;
351 g_autoptr(GError) err = NULL;
352
353 parser = json_parser_new();
354
355 json_parser_load_from_data(parser, data, -1, &err);
356
357 if (err)
358 {
359 WARNING("Unable to parse JSON because: %s", err->message);
360
361 g_propagate_prefixed_error(error, g_steal_pointer(&err), "Unable to parse JSON because: ");
362 }
363 else
364 reader = json_reader_new(json_parser_get_root(parser));
365
366 return g_steal_pointer(&reader);
367 }
368
369 #define RETURN_JSON_ERROR \
370 G_STMT_START \
371 { \
372 WARNING("Coultn'd parse channel from JSON because: %s", \
373 json_reader_get_error(reader)-> message); \
374 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON, \
375 "Couldn't parse channel from JSON because: %s", \
376 json_reader_get_error(reader)->message); \
377 return NULL; \
378 } G_STMT_END
379
380 GtChannelData*
utils_parse_channel_from_json(JsonReader * reader,GError ** error)381 utils_parse_channel_from_json(JsonReader* reader, GError** error)
382 {
383 g_autoptr(GtChannelData) data = gt_channel_data_new();
384
385 if (!json_reader_read_member(reader, "_id")) RETURN_JSON_ERROR;
386 JsonNode* node = json_reader_get_value(reader);
387 if (STRING_EQUALS(json_node_type_name(node), "Integer"))
388 data->id = g_strdup_printf("%" G_GINT64_FORMAT, json_reader_get_int_value(reader));
389 else if (STRING_EQUALS(json_node_type_name(node), "String"))
390 data->id = g_strdup(json_reader_get_string_value(reader));
391 else
392 {
393 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON,
394 "Unable to parse game from JSON because: '_id' was of incorrect format");
395 return NULL;
396 }
397 json_reader_end_member(reader);
398
399 if (!json_reader_read_member(reader, "name")) RETURN_JSON_ERROR;
400 data->name = g_strdup(json_reader_get_string_value(reader));
401 json_reader_end_member(reader);
402
403 if (!json_reader_read_member(reader, "display_name")) RETURN_JSON_ERROR;
404 data->display_name = json_reader_get_null_value(reader) ?
405 NULL : g_strdup(json_reader_get_string_value(reader));
406 json_reader_end_member(reader);
407
408 if (!json_reader_read_member(reader, "status")) RETURN_JSON_ERROR;
409 data->status = json_reader_get_null_value(reader) ?
410 NULL : g_strdup(json_reader_get_string_value(reader));
411 json_reader_end_member(reader);
412
413 if (!json_reader_read_member(reader, "video_banner")) RETURN_JSON_ERROR;
414 data->video_banner_url = json_reader_get_null_value(reader) ?
415 NULL : g_strdup(json_reader_get_string_value(reader));
416 json_reader_end_member(reader);
417
418 if (!json_reader_read_member(reader, "logo")) RETURN_JSON_ERROR;
419 data->logo_url = json_reader_get_null_value(reader) ?
420 NULL : g_strdup(json_reader_get_string_value(reader));
421 json_reader_end_member(reader);
422
423 if (!json_reader_read_member(reader, "url")) RETURN_JSON_ERROR;
424 data->profile_url = g_strdup(json_reader_get_string_value(reader));
425 json_reader_end_member(reader);
426
427 data->online = FALSE;
428
429 return g_steal_pointer(&data);
430 }
431
432 #undef RETURN_JSON_ERROR
433 #define RETURN_JSON_ERROR \
434 G_STMT_START \
435 { \
436 WARNING("Coultn'd parse stream from JSON because: %s", \
437 json_reader_get_error(reader)-> message); \
438 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON, \
439 "Couldn't parse stream from JSON because: %s", \
440 json_reader_get_error(reader)->message); \
441 return NULL; \
442 } G_STMT_END
443
444 GtChannelData*
utils_parse_stream_from_json(JsonReader * reader,GError ** error)445 utils_parse_stream_from_json(JsonReader* reader, GError** error)
446 {
447 g_autoptr(GtChannelData) data = NULL;
448
449 if (!json_reader_read_member(reader, "channel")) RETURN_JSON_ERROR;
450 data = utils_parse_channel_from_json(reader, error);
451 json_reader_end_member(reader);
452
453 if (*error) return NULL;
454
455 if (!json_reader_read_member(reader, "game")) RETURN_JSON_ERROR;
456 data->game = json_reader_get_null_value(reader) ?
457 NULL : g_strdup(json_reader_get_string_value(reader));
458 json_reader_end_member(reader);
459
460 if (!json_reader_read_member(reader, "viewers")) RETURN_JSON_ERROR;
461 data->viewers = json_reader_get_null_value(reader) ?
462 0 : json_reader_get_int_value(reader);
463 json_reader_end_member(reader);
464
465 if (!json_reader_read_member(reader, "created_at")) RETURN_JSON_ERROR;
466 data->stream_started_time = json_reader_get_null_value(reader) ?
467 NULL : utils_parse_time_iso_8601(json_reader_get_string_value(reader), error);
468 json_reader_end_member(reader);
469
470 if (*error)
471 {
472 g_prefix_error(error, "Unable to parse stream from JSON because: ");
473 return NULL;
474 }
475
476 if (!json_reader_read_member(reader, "preview")) RETURN_JSON_ERROR;
477 if (!json_reader_read_member(reader, "large")) RETURN_JSON_ERROR;
478 data->preview_url = json_reader_get_null_value(reader) ?
479 NULL : g_strdup(json_reader_get_string_value(reader));
480 json_reader_end_member(reader);
481 json_reader_end_member(reader);
482
483 data->online = TRUE;
484
485 return g_steal_pointer(&data);
486 }
487
488 #undef RETURN_JSON_ERROR
489 #define RETURN_JSON_ERROR \
490 G_STMT_START \
491 { \
492 WARNING("Coultn'd parse game from JSON because: %s", \
493 json_reader_get_error(reader)-> message); \
494 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON, \
495 "Couldn't parse game from JSON because: %s", \
496 json_reader_get_error(reader)->message); \
497 return NULL; \
498 } G_STMT_END
499
500 GtGameData*
utils_parse_game_from_json(JsonReader * reader,GError ** error)501 utils_parse_game_from_json(JsonReader* reader, GError** error)
502 {
503 g_autoptr(GtGameData) data = gt_game_data_new();
504
505 if (!json_reader_read_member(reader, "_id")) RETURN_JSON_ERROR;
506 JsonNode* node = json_reader_get_value(reader);
507 if (STRING_EQUALS(json_node_type_name(node), "Integer"))
508 data->id = g_strdup_printf("%" G_GINT64_FORMAT, json_reader_get_int_value(reader));
509 else if (STRING_EQUALS(json_node_type_name(node), "String"))
510 data->id = g_strdup(json_reader_get_string_value(reader));
511 else
512 {
513 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON,
514 "Unable to parse game from JSON because: '_id' was of incorrect format");
515 return NULL;
516 }
517 json_reader_end_member(reader);
518
519 if (!json_reader_read_member(reader, "name")) RETURN_JSON_ERROR;
520 data->name = g_strdup(json_reader_get_string_value(reader));
521 json_reader_end_member(reader);
522
523 if (!json_reader_read_member(reader, "box")) RETURN_JSON_ERROR;
524 if (!json_reader_read_member(reader, "large")) RETURN_JSON_ERROR;
525 data->preview_url = g_strdup(json_reader_get_string_value(reader));
526 json_reader_end_member(reader);
527 json_reader_end_member(reader);
528
529 if (!json_reader_read_member(reader, "logo")) RETURN_JSON_ERROR;
530 if (!json_reader_read_member(reader, "large")) RETURN_JSON_ERROR;
531 data->logo_url = g_strdup(json_reader_get_string_value(reader));
532 json_reader_end_member(reader);
533 json_reader_end_member(reader);
534
535 return g_steal_pointer(&data);
536 }
537
538 #undef RETURN_JSON_ERROR
539 #define RETURN_JSON_ERROR \
540 G_STMT_START \
541 { \
542 WARNING("Couldn't parse VOD from JSON because: %s", \
543 json_reader_get_error(reader)-> message); \
544 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON, \
545 "Couldn't parse VOD from JSON because: %s", \
546 json_reader_get_error(reader)->message); \
547 return NULL; \
548 } G_STMT_END
549
550 GtVODData*
utils_parse_vod_from_json(JsonReader * reader,GError ** error)551 utils_parse_vod_from_json(JsonReader* reader, GError** error)
552 {
553 RETURN_VAL_IF_FAIL(JSON_IS_READER(reader), NULL);
554
555 g_autoptr(GtVODData) data = gt_vod_data_new();
556 GTimeVal time;
557 JsonNode* node = NULL;
558
559 if (!json_reader_read_member(reader, "_id")) RETURN_JSON_ERROR;
560 data->id = g_strdup(json_reader_get_string_value(reader));
561 json_reader_end_member(reader);
562
563 if (!json_reader_read_member(reader, "broadcast_id")) RETURN_JSON_ERROR;
564 node = json_reader_get_value(reader);
565 if (STRING_EQUALS(json_node_type_name(node), "Integer"))
566 data->broadcast_id = g_strdup_printf("%" G_GINT64_FORMAT, json_reader_get_int_value(reader));
567 else if (STRING_EQUALS(json_node_type_name(node), "String"))
568 data->broadcast_id = g_strdup(json_reader_get_string_value(reader));
569 else
570 {
571 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_JSON,
572 "Unable to parse vod from JSON because: '_id' was of incorrect format");
573 return NULL;
574 }
575 json_reader_end_member(reader);
576
577 if (!json_reader_read_member(reader, "created_at")) RETURN_JSON_ERROR;
578 if (!g_time_val_from_iso8601(json_reader_get_string_value(reader), &time))
579 {
580 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_PARSING_TIME,
581 "Couldn't parse time from string %s", json_reader_get_string_value(reader));
582 return NULL;
583 }
584 data->created_at = g_date_time_new_from_timeval_utc(&time);
585 json_reader_end_member(reader);
586
587 if (!json_reader_read_member(reader, "published_at")) RETURN_JSON_ERROR;
588 if (!g_time_val_from_iso8601(json_reader_get_string_value(reader), &time))
589 {
590 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_PARSING_TIME,
591 "Couldn't parse time from string %s", json_reader_get_string_value(reader));
592 return NULL;
593 }
594 data->published_at = g_date_time_new_from_timeval_utc(&time);
595 json_reader_end_member(reader);
596
597 if (!json_reader_read_member(reader, "description")) RETURN_JSON_ERROR;
598 data->description = json_reader_get_null_value(reader) ? NULL : g_strdup(json_reader_get_string_value(reader));
599 json_reader_end_member(reader);
600
601 if (!json_reader_read_member(reader, "game")) RETURN_JSON_ERROR;
602 data->game = json_reader_get_null_value(reader) ? NULL : g_strdup(json_reader_get_string_value(reader));
603 json_reader_end_member(reader);
604
605 if (!json_reader_read_member(reader, "language")) RETURN_JSON_ERROR;
606 data->language = json_reader_get_null_value(reader) ? NULL : g_strdup(json_reader_get_string_value(reader));
607 json_reader_end_member(reader);
608
609 if (!json_reader_read_member(reader, "length")) RETURN_JSON_ERROR;
610 data->length = json_reader_get_int_value(reader);
611 json_reader_end_member(reader);
612
613 /* NOTE: Uncomment when needed */
614 /* if (!json_reader_read_member(reader, "channel")) RETURN_JSON_ERROR; */
615 /* data->channel = utils_parse_channel_from_json(reader, error); */
616 /* if (*error) return NULL; */
617 /* json_reader_end_member(reader); */
618
619 if (!json_reader_read_member(reader, "preview")) RETURN_JSON_ERROR;
620
621 if (!json_reader_read_member(reader, "large")) RETURN_JSON_ERROR;
622 data->preview.large = g_strdup(json_reader_get_string_value(reader));
623 json_reader_end_member(reader);
624
625 if (!json_reader_read_member(reader, "medium")) RETURN_JSON_ERROR;
626 data->preview.medium = g_strdup(json_reader_get_string_value(reader));
627 json_reader_end_member(reader);
628
629 if (!json_reader_read_member(reader, "small")) RETURN_JSON_ERROR;
630 data->preview.small = g_strdup(json_reader_get_string_value(reader));
631 json_reader_end_member(reader);
632
633 if (!json_reader_read_member(reader, "template")) RETURN_JSON_ERROR;
634 data->preview.template = g_strdup(json_reader_get_string_value(reader));
635 json_reader_end_member(reader);
636
637 json_reader_end_member(reader);
638
639 if (!json_reader_read_member(reader, "title")) RETURN_JSON_ERROR;
640 data->title = g_strdup(json_reader_get_string_value(reader));
641 json_reader_end_member(reader);
642
643 if (!json_reader_read_member(reader, "url")) RETURN_JSON_ERROR;
644 data->url = g_strdup(json_reader_get_string_value(reader));
645 json_reader_end_member(reader);
646
647 if (!json_reader_read_member(reader, "views")) RETURN_JSON_ERROR;
648 data->views = json_reader_get_int_value(reader);
649 json_reader_end_member(reader);
650
651 if (!json_reader_read_member(reader, "tag_list")) RETURN_JSON_ERROR;
652 data->tag_list = g_strdup(json_reader_get_string_value(reader));
653 json_reader_end_member(reader);
654
655 return g_steal_pointer(&data);
656 }
657
658 void
gt_playlist_entry_free(GtPlaylistEntry * entry)659 gt_playlist_entry_free(GtPlaylistEntry* entry)
660 {
661 g_free(entry->name);
662 g_free(entry->resolution);
663 g_free(entry->uri);
664 g_slice_free(GtPlaylistEntry, entry);
665 }
666
667 void
gt_playlist_entry_list_free(GtPlaylistEntryList * list)668 gt_playlist_entry_list_free(GtPlaylistEntryList* list)
669 {
670 g_list_free_full(list, (GDestroyNotify) gt_playlist_entry_free);
671 }
672
673 GtPlaylistEntryList*
utils_parse_playlist(const gchar * str,GError ** error)674 utils_parse_playlist(const gchar* str, GError** error)
675 {
676 g_autoptr(GtPlaylistEntryList) entries = NULL;
677 g_auto(GStrv) lines = g_strsplit(str, "\n", 0);
678
679 for (gchar** l = lines; *l != NULL; l++)
680 {
681 if (strncmp(*l, "#EXT-X-STREAM-INF", 17) == 0)
682 {
683 g_autoptr(GtPlaylistEntry) entry = g_slice_new0(GtPlaylistEntry);
684 g_auto(GStrv) values = NULL;
685
686 if (strncmp(*(l - 1), "#EXT-X-MEDIA", 12) != 0)
687 {
688 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_PARSING_PLAYLIST,
689 "STREAM-INF entry wasn't preceded by a MEDIA entry");
690 return NULL;
691 }
692
693 values = g_strsplit(*(l - 1), ",", 0);
694
695 for (gchar** m = values; *m != NULL; m++)
696 {
697 g_auto(GStrv) split = g_strsplit(*m, "=", 0);
698
699 if (STRING_EQUALS(*split, "NAME"))
700 {
701 /* NOTE: This is to remove quotation marks */
702 const gchar* name = *(split + 1);
703 entry->name = g_strndup(name + 1, strlen(name) - 2);
704 break;
705 }
706 }
707
708 values = g_strsplit(*l, ",", 0);
709
710 for (gchar** m = values; *m != NULL; m++)
711 {
712 g_auto(GStrv) split = g_strsplit(*m, "=", 0);
713
714 if (STRING_EQUALS(*split, "RESOLUTION"))
715 {
716 const gchar* resolution = *(split + 1);
717 entry->resolution = g_strdup(resolution);
718 break;
719 }
720 }
721
722 if (!(g_str_has_prefix(*(l + 1), "https://") || g_str_has_prefix(*(l + 1), "http://")))
723 {
724 g_set_error(error, GT_UTILS_ERROR, GT_UTILS_ERROR_PARSING_PLAYLIST,
725 "STREAM-INF entry wasn't succeeded by a uri");
726 return NULL;
727 }
728
729 entry->uri = g_strdup(*(l + 1));
730
731 entries = g_list_append(entries, g_steal_pointer(&entry));
732 }
733 }
734
735 return g_steal_pointer(&entries);
736 }
737