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 "gt-app.h"
20 #include "gt-win.h"
21 #include "gt-http-soup.h"
22 #include "config.h"
23 #include <glib/gi18n.h>
24 #include <glib/gstdio.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <json-glib/json-glib.h>
28 #include <stdlib.h>
29 #include "utils.h"
30 #include "version.h"
31
32 #define TAG "GtApp"
33 #include "gnome-twitch/gt-log.h"
34
35 struct _GtAppPrivate
36 {
37 GtWin* win;
38
39 GtUserInfo* user_info;
40 GtOAuthInfo* oauth_info;
41 GMenuItem* login_item;
42 GMenuModel* app_menu;
43
44 gchar* language_filter;
45
46 GCancellable* open_channel_cancel;
47 GHashTable* soup_inflight_table;
48 GQueue* soup_message_queue;
49 };
50
51 gint LOG_LEVEL = GT_LOG_LEVEL_MESSAGE;
52 gboolean NO_FANCY_LOGGING = FALSE;
53 gboolean VERSION = FALSE;
54
55 const gchar* TWITCH_AUTH_SCOPES[] =
56 {
57 "chat_login",
58 "user_follows_edit",
59 "user_read",
60 NULL
61 };
62
63
64 G_DEFINE_TYPE_WITH_PRIVATE(GtApp, gt_app, GTK_TYPE_APPLICATION)
65
66 enum
67 {
68 PROP_0,
69 PROP_LOGGED_IN,
70 PROP_LANGUAGE_FILTER,
71 NUM_PROPS
72 };
73
74 static GParamSpec* props[NUM_PROPS];
75
76 static gboolean
set_log_level(const gchar * name,const gchar * arg,gpointer data,GError ** err)77 set_log_level(const gchar* name,
78 const gchar* arg,
79 gpointer data,
80 GError** err)
81 {
82 if (g_strcmp0(arg, "error") == 0)
83 LOG_LEVEL = GT_LOG_LEVEL_ERROR;
84 else if (g_strcmp0(arg, "critical") == 0)
85 LOG_LEVEL = GT_LOG_LEVEL_CRITICAL;
86 else if (g_strcmp0(arg, "warning") == 0)
87 LOG_LEVEL = GT_LOG_LEVEL_WARNING;
88 else if (g_strcmp0(arg, "message") == 0)
89 LOG_LEVEL = GT_LOG_LEVEL_MESSAGE;
90 else if (g_strcmp0(arg, "info") == 0)
91 LOG_LEVEL = GT_LOG_LEVEL_INFO;
92 else if (g_strcmp0(arg, "debug") == 0)
93 LOG_LEVEL = GT_LOG_LEVEL_DEBUG;
94 else if (g_strcmp0(arg, "trace") == 0)
95 LOG_LEVEL = GT_LOG_LEVEL_TRACE;
96 else
97 WARNING("Invalid logging level"); //TODO: Use g_set_error and return false
98
99 return TRUE;
100 }
101
102 static GOptionEntry
103 cli_options[] =
104 {
105 {"log-level", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_log_level, "Set logging level", "level"},
106 {"no-fancy-logging", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &NO_FANCY_LOGGING, "Don't print pretty log messages", NULL},
107 {"version", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &VERSION, "Display version", NULL},
108 {NULL}
109 };
110
111 GtApp*
gt_app_new(void)112 gt_app_new(void)
113 {
114 return g_object_new(GT_TYPE_APP,
115 "application-id", "com.vinszent.GnomeTwitch",
116 NULL);
117 }
118
119 static void
oauth_info_cb(GObject * source,GAsyncResult * res,gpointer udata)120 oauth_info_cb(GObject* source,
121 GAsyncResult* res, gpointer udata)
122 {
123 g_assert(GT_IS_APP(udata));
124 g_assert(GT_IS_TWITCH(source));
125
126 GtApp* self = GT_APP(udata);
127 GError* err = NULL;
128
129 GtOAuthInfo* oauth_info = gt_twitch_fetch_oauth_info_finish(GT_TWITCH(source), res, &err);
130
131 if (err)
132 {
133 WARNING("Unable to update oauth info because: %s", err->message);
134
135 GtWin* win = GT_WIN_ACTIVE;
136
137 g_assert(GT_IS_WIN(win));
138
139 gt_win_show_error_message(win, "Unable to update oauth info",
140 "Unable to update oauth info because: %s", err->message);
141
142 g_error_free(err);
143
144 return;
145 }
146
147 gt_app_set_oauth_info(self, oauth_info);
148
149 MESSAGE("Successfully fetched oauth info with name '%s', id '%s' and oauth token '%s'",
150 oauth_info->user_name, oauth_info->user_id, oauth_info->oauth_token);
151 }
152
153 static void
user_info_cb(GObject * source,GAsyncResult * res,gpointer udata)154 user_info_cb(GObject* source,
155 GAsyncResult* res, gpointer udata)
156 {
157 g_assert(GT_IS_APP(udata));
158 g_assert(GT_IS_TWITCH(source));
159
160 GtApp* self = GT_APP(udata);
161 GtAppPrivate* priv = gt_app_get_instance_private(self);
162 GError* err = NULL;
163
164 GtUserInfo* user_info = gt_twitch_fetch_user_info_finish(GT_TWITCH(source), res, &err);
165
166 if (err)
167 {
168 WARNING("Unable to update user info because: %s", err->message);
169
170 GtWin* win = GT_WIN_ACTIVE;
171
172 g_assert(GT_IS_WIN(win));
173
174 gt_win_show_error_message(win, "Unable to update user info",
175 "Unable to update user info because: %s", err->message);
176
177 g_error_free(err);
178
179 return;
180 }
181
182 priv->user_info = user_info;
183
184 MESSAGE("Successfully fetched user info with name '%s', id '%s' and oauth token '%s'",
185 user_info->name, user_info->id, user_info->oauth_token);
186 }
187
188 static void
logged_in_cb(GObject * src,GParamSpec * pspec,gpointer udata)189 logged_in_cb(GObject* src,
190 GParamSpec* pspec, gpointer udata)
191 {
192 g_assert(GT_IS_APP(src));
193
194 GtApp* self = GT_APP(src);
195 GtAppPrivate* priv = gt_app_get_instance_private(self);
196
197 if (gt_app_is_logged_in(self))
198 {
199 g_menu_remove(G_MENU(priv->app_menu), 0);
200 g_menu_item_set_label(priv->login_item, _("Refresh login"));
201 g_menu_prepend_item(G_MENU(priv->app_menu), priv->login_item);
202 }
203 }
204
205 /* FIXME: Move this into GtResourceDownloader */
206 static inline void
init_dirs()207 init_dirs()
208 {
209 gchar* fp;
210 int err;
211
212 fp = g_build_filename(g_get_user_data_dir(), "gnome-twitch", NULL);
213 err = g_mkdir_with_parents(fp, 0777);
214 if (err != 0 && g_file_error_from_errno(errno) != G_FILE_ERROR_EXIST)
215 WARNING("Error creating data directory");
216 g_free(fp);
217
218 fp = g_build_filename(g_get_user_cache_dir(), "gnome-twitch", "channels", NULL);
219 err = g_mkdir_with_parents(fp, 0777);
220 if (err != 0 && g_file_error_from_errno(errno) != G_FILE_ERROR_EXIST)
221 WARNING("Error creating channel cache directory");
222 g_free(fp);
223
224 fp = g_build_filename(g_get_user_cache_dir(), "gnome-twitch", "games", NULL);
225 err = g_mkdir_with_parents(fp, 0777);
226 if (err != 0 && g_file_error_from_errno(errno) != G_FILE_ERROR_EXIST)
227 WARNING("Error creating game cache directory");
228 g_free(fp);
229
230 fp = g_build_filename(g_get_user_cache_dir(), "gnome-twitch", "emotes", NULL);
231 err = g_mkdir_with_parents(fp, 0777);
232 if (err != 0 && g_file_error_from_errno(errno) != G_FILE_ERROR_EXIST)
233 WARNING("Error creating game cache directory");
234 g_free(fp);
235
236 fp = g_build_filename(g_get_user_cache_dir(), "gnome-twitch", "badges", NULL);
237 err = g_mkdir_with_parents(fp, 0777);
238 if (err != 0 && g_file_error_from_errno(errno) != G_FILE_ERROR_EXIST)
239 WARNING("Error creating game cache directory");
240 g_free(fp);
241 }
242
243 static void
open_channel_after_fetch_cb(GObject * source,GAsyncResult * res,gpointer udata)244 open_channel_after_fetch_cb(GObject* source,
245 GAsyncResult* res, gpointer udata)
246 {
247 g_assert(GT_IS_APP(udata));
248 g_assert(GT_IS_TWITCH(source));
249 g_assert(G_IS_ASYNC_RESULT(res));
250
251 GtApp* self = udata;
252 g_autoptr(GError) err = NULL;
253 //NOTE: autoptr because we want this unrefed as we won't be using it anymore
254 g_autoptr(GtChannel) channel = NULL;
255 GtWin* win = NULL;
256
257 channel = gt_twitch_fetch_channel_finish(GT_TWITCH(source),
258 res, &err);
259
260 win = GT_WIN_ACTIVE;
261
262 g_assert(GT_IS_WIN(win));
263
264 if (err)
265 {
266 WARNING("Couldn't open channel because: %s", err->message);
267
268 gt_win_show_error_message(win, "Couldn't open channel",
269 "Couldn't open channel because: %s", err->message);
270
271 }
272 else if (!gt_channel_is_online(channel))
273 {
274 gt_win_show_info_message(win, _("Unable to open channel %s because it’s not online"),
275 gt_channel_get_name(channel));
276 }
277 else
278 gt_win_open_channel(win, channel); //NOTE: Win will take a reference to channel
279
280 g_object_unref(self);
281 }
282
283 static void
open_channel_from_id_cb(GSimpleAction * action,GVariant * var,gpointer udata)284 open_channel_from_id_cb(GSimpleAction* action,
285 GVariant* var, gpointer udata)
286 {
287 g_assert(G_IS_SIMPLE_ACTION(action));
288 g_assert(GT_IS_APP(udata));
289 g_assert(g_variant_is_of_type(var, G_VARIANT_TYPE_STRING));
290
291 GtApp* self = GT_APP(udata);
292 GtAppPrivate* priv = gt_app_get_instance_private(self);
293 const gchar* id = g_variant_get_string(var, NULL);
294
295 utils_refresh_cancellable(&priv->open_channel_cancel);
296
297 MESSAGE("Opening channel from id '%s'", id);
298
299 gt_twitch_fetch_channel_async(self->twitch, id,
300 open_channel_after_fetch_cb, priv->open_channel_cancel,
301 g_object_ref(self));
302 }
303
304 static void
quit_cb(GSimpleAction * action,GVariant * par,gpointer udata)305 quit_cb(GSimpleAction* action,
306 GVariant* par, gpointer udata)
307 {
308 MESSAGE("Quitting");
309
310 GtApp* self = GT_APP(udata);
311
312 g_application_quit(G_APPLICATION(self));
313 }
314
315 static gint
handle_command_line_cb(GApplication * self,GVariantDict * options,gpointer udata)316 handle_command_line_cb(GApplication* self,
317 GVariantDict* options, gpointer udata)
318 {
319 if (VERSION)
320 {
321 g_print("GNOME Twitch - Version %s\n", GT_VERSION);
322 return 0;
323 }
324
325 return -1;
326 }
327
328 static GActionEntry app_actions[] =
329 {
330 {"open-channel-from-id", open_channel_from_id_cb, "s", NULL, NULL},
331 {"quit", quit_cb, NULL, NULL, NULL}
332 };
333
334 static void
activate(GApplication * app)335 activate(GApplication* app)
336 {
337 GtApp* self = GT_APP(app);
338 GtAppPrivate* priv = gt_app_get_instance_private(self);
339
340 G_APPLICATION_CLASS(gt_app_parent_class)->activate(app);
341
342 MESSAGE("Activate");
343
344 priv->win = gt_win_new(self);
345
346 gtk_window_present(GTK_WINDOW(priv->win));
347 }
348
349 static void
gt_app_prefer_dark_theme_changed_cb(GSettings * settings,const char * key,GtkSettings * gtk_settings)350 gt_app_prefer_dark_theme_changed_cb(GSettings* settings,
351 const char* key,
352 GtkSettings* gtk_settings)
353 {
354 gboolean prefer_dark_theme = g_settings_get_boolean(settings, key);
355
356 g_object_set(gtk_settings,
357 "gtk-application-prefer-dark-theme",
358 prefer_dark_theme,
359 NULL);
360 }
361
362 //Only called once
363 static void
startup(GApplication * app)364 startup(GApplication* app)
365 {
366 GtApp* self = GT_APP(app);
367 GtAppPrivate* priv = gt_app_get_instance_private(self);
368 GtkSettings* gtk_settings;
369 g_autoptr(GtkBuilder) menu_bld;
370
371 G_APPLICATION_CLASS(gt_app_parent_class)->startup(app);
372
373 MESSAGE("Startup, running version '%s'", GT_VERSION);
374
375 self->fav_mgr = gt_follows_manager_new();
376 self->twitch = gt_twitch_new();
377
378 init_dirs();
379
380 g_action_map_add_action_entries(G_ACTION_MAP(self),
381 app_actions, G_N_ELEMENTS(app_actions), self);
382
383 menu_bld = gtk_builder_new_from_resource("/com/vinszent/GnomeTwitch/ui/app-menu.ui");
384 priv->app_menu = G_MENU_MODEL(gtk_builder_get_object(menu_bld, "app_menu"));
385 g_object_ref(priv->app_menu);
386 priv->login_item = g_menu_item_new(_("Log in to Twitch"), "win.show_twitch_login");
387 g_menu_prepend_item(G_MENU(priv->app_menu), priv->login_item);
388
389 gtk_application_set_app_menu(GTK_APPLICATION(app), G_MENU_MODEL(priv->app_menu));
390
391 gtk_settings = gtk_settings_get_default();
392
393 gt_app_prefer_dark_theme_changed_cb(self->settings,
394 "prefer-dark-theme", gtk_settings);
395
396 g_signal_connect(self->settings, "changed::prefer-dark-theme",
397 G_CALLBACK(gt_app_prefer_dark_theme_changed_cb), gtk_settings);
398
399 //NOTE: This is to refresh oauth info
400 if (gt_app_is_logged_in(self))
401 {
402 gt_twitch_fetch_oauth_info_async(self->twitch, priv->oauth_info->oauth_token,
403 oauth_info_cb, NULL, self);
404 }
405 else
406 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_LOGGED_IN]);
407 }
408
409 static void
shutdown(GApplication * app)410 shutdown(GApplication* app)
411 {
412 GtApp* self = GT_APP(app);
413
414 MESSAGE("Shutting down");
415
416 //TODO: Add a setting to allow user to use local follows even when logged in
417 if (!gt_app_is_logged_in(self))
418 gt_follows_manager_save(self->fav_mgr);
419
420 G_APPLICATION_CLASS(gt_app_parent_class)->shutdown(app);
421 }
422
423 static void
dispose(GObject * object)424 dispose(GObject* object)
425 {
426 g_assert(GT_IS_APP(object));
427
428 GtApp* self = GT_APP(object);
429 GtAppPrivate* priv = gt_app_get_instance_private(self);
430
431 g_hash_table_unref(priv->soup_inflight_table);
432 g_queue_free_full(priv->soup_message_queue, g_object_unref);
433 g_object_unref(self->http);
434
435 G_OBJECT_CLASS(gt_app_parent_class)->dispose(object);
436 }
437
438 static void
get_property(GObject * obj,guint prop,GValue * val,GParamSpec * pspec)439 get_property (GObject* obj, guint prop,
440 GValue* val, GParamSpec* pspec)
441 {
442 GtApp* self = GT_APP(obj);
443 GtAppPrivate* priv = gt_app_get_instance_private(self);
444
445 switch (prop)
446 {
447 case PROP_LOGGED_IN:
448 g_value_set_boolean(val, priv->oauth_info != NULL);
449 break;
450 case PROP_LANGUAGE_FILTER:
451 g_value_set_string(val, priv->language_filter);
452 break;
453 default:
454 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
455 }
456 }
457
458 static void
set_property(GObject * obj,guint prop,const GValue * val,GParamSpec * pspec)459 set_property(GObject* obj, guint prop,
460 const GValue* val, GParamSpec* pspec)
461 {
462 GtApp* self = GT_APP(obj);
463 GtAppPrivate* priv = gt_app_get_instance_private(self);
464
465 switch (prop)
466 {
467 case PROP_LANGUAGE_FILTER:
468 g_free(priv->language_filter);
469 priv->language_filter = g_value_dup_string(val);
470 break;
471 default:
472 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
473 }
474 }
475
476 static void
gt_app_class_init(GtAppClass * klass)477 gt_app_class_init(GtAppClass* klass)
478 {
479 GObjectClass* object_class = G_OBJECT_CLASS(klass);
480
481 G_OBJECT_CLASS(klass)->dispose = dispose;
482
483 G_APPLICATION_CLASS(klass)->activate = activate;
484 G_APPLICATION_CLASS(klass)->startup = startup;
485 G_APPLICATION_CLASS(klass)->shutdown = shutdown;
486
487 object_class->get_property = get_property;
488 object_class->set_property = set_property;
489
490 props[PROP_LOGGED_IN] = g_param_spec_boolean("logged-in",
491 "Logged in", "Whether logged in", FALSE, G_PARAM_READABLE);
492
493 props[PROP_LANGUAGE_FILTER] = g_param_spec_string("language-filter",
494 "Language filter", "Current language filter", "", G_PARAM_READWRITE);
495
496 g_object_class_install_properties(object_class, NUM_PROPS, props);
497 }
498
499 static void
gt_app_init(GtApp * self)500 gt_app_init(GtApp* self)
501 {
502 GtAppPrivate* priv = gt_app_get_instance_private(self);
503
504 priv->oauth_info = gt_oauth_info_new();
505 priv->soup_inflight_table = g_hash_table_new(g_str_hash, g_str_equal);
506 priv->soup_message_queue = g_queue_new();
507
508 g_application_add_main_option_entries(G_APPLICATION(self), cli_options);
509
510 self->settings = g_settings_new("com.vinszent.GnomeTwitch");
511 self->players_engine = peas_engine_get_default();
512 peas_engine_enable_loader(self->players_engine, "python3");
513 self->soup = soup_session_new();
514 self->http = GT_HTTP(gt_http_soup_new());
515
516 gchar* plugin_dir;
517
518 #ifdef G_OS_WIN32
519 plugin_dir = g_build_filename(
520 g_win32_get_package_installation_directory_of_module(NULL),
521 "lib", "gnome-twitch", "player-backends", NULL);
522
523 peas_engine_add_search_path(self->players_engine, plugin_dir, NULL);
524
525 g_free(plugin_dir);
526 #else
527 plugin_dir = g_build_filename(GT_LIB_DIR, "gnome-twitch",
528 "player-backends", NULL);
529
530 peas_engine_add_search_path(self->players_engine, plugin_dir, NULL);
531
532 g_free(plugin_dir);
533
534 plugin_dir = g_build_filename(g_get_user_data_dir(), "gnome-twitch",
535 "player-backends", NULL);
536
537 peas_engine_add_search_path(self->players_engine, plugin_dir, NULL);
538
539 g_free(plugin_dir);
540 #endif
541
542 g_settings_bind(self->settings, "player-backend",
543 self->players_engine, "loaded-plugins",
544 G_SETTINGS_BIND_DEFAULT);
545 g_settings_bind(self->settings, "language-filter",
546 self, "language-filter",
547 G_SETTINGS_BIND_GET);
548
549 priv->oauth_info->oauth_token = g_settings_get_string(self->settings, "oauth-token");
550 priv->oauth_info->user_id = g_settings_get_string(self->settings, "user-id");
551 priv->oauth_info->user_name = g_settings_get_string(self->settings, "user-name");
552
553 g_signal_connect(self, "notify::logged-in", G_CALLBACK(logged_in_cb), self);
554 g_signal_connect(self, "handle-local-options", G_CALLBACK(handle_command_line_cb), self);
555
556 }
557
558 //TODO: Turn this into a property
559 void
gt_app_set_oauth_info(GtApp * self,GtOAuthInfo * info)560 gt_app_set_oauth_info(GtApp* self, GtOAuthInfo* info)
561 {
562 GtAppPrivate* priv = gt_app_get_instance_private(self);
563
564 if (priv->oauth_info)
565 gt_oauth_info_free(priv->oauth_info);
566
567 priv->oauth_info = info;
568
569 for (gchar** s = (gchar**) TWITCH_AUTH_SCOPES; *s != NULL; s++)
570 {
571 if (!g_strv_contains((const gchar**) info->scopes, *s))
572 {
573 GtWin* win = GT_WIN_ACTIVE;
574
575 g_assert(GT_IS_WIN(win));
576
577 WARNING("Unable to update oauth info because the auth scope '%s' was not included", *s);
578
579 gt_win_show_error_message(win, "Unable to update oauth info, try refreshing your login",
580 "Unable to update oauth info because the auth scope '%s' was not included", *s);
581
582 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_LOGGED_IN]);
583
584 return;
585 }
586 }
587
588 g_settings_set_string(self->settings, "oauth-token", info->oauth_token);
589 g_settings_set_string(self->settings, "user-id", info->user_id);
590 g_settings_set_string(self->settings, "user-name", info->user_name);
591
592 gt_twitch_fetch_user_info_async(self->twitch, priv->oauth_info->oauth_token,
593 user_info_cb, NULL, self);
594
595 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_LOGGED_IN]);
596 }
597
598 const GtUserInfo*
gt_app_get_user_info(GtApp * self)599 gt_app_get_user_info(GtApp* self)
600 {
601 g_assert(GT_IS_APP(self));
602
603 GtAppPrivate* priv = gt_app_get_instance_private(self);
604
605 return priv->user_info;
606 }
607
608 const GtOAuthInfo*
gt_app_get_oauth_info(GtApp * self)609 gt_app_get_oauth_info(GtApp* self)
610 {
611 g_assert(GT_IS_APP(self));
612
613 GtAppPrivate* priv = gt_app_get_instance_private(self);
614
615 return priv->oauth_info;
616 }
617
618 gboolean
gt_app_is_logged_in(GtApp * self)619 gt_app_is_logged_in(GtApp* self)
620 {
621 GtAppPrivate* priv = gt_app_get_instance_private(self);
622
623 return
624 priv->oauth_info &&
625 !utils_str_empty(priv->oauth_info->oauth_token) &&
626 !utils_str_empty(priv->oauth_info->user_name) &&
627 !utils_str_empty(priv->oauth_info->user_id);
628 }
629
630 const gchar*
gt_app_get_language_filter(GtApp * self)631 gt_app_get_language_filter(GtApp* self)
632 {
633 g_assert(GT_IS_APP(self));
634
635 GtAppPrivate* priv = gt_app_get_instance_private(self);
636
637 return priv->language_filter;
638 }
639
640 gboolean
gt_app_should_show_notifications(GtApp * self)641 gt_app_should_show_notifications(GtApp* self)
642 {
643 g_assert(GT_IS_APP(self));
644
645 return g_settings_get_boolean(main_app->settings, "show-notifications");
646 }
647
648 GtUserInfo*
gt_user_info_new()649 gt_user_info_new()
650 {
651 return g_slice_new0(GtUserInfo);
652 }
653
654 void
gt_user_info_free(GtUserInfo * info)655 gt_user_info_free(GtUserInfo* info)
656 {
657 if (!info) return;
658
659 g_free(info->name);
660 g_free(info->oauth_token);
661 g_free(info->display_name);
662 g_free(info->bio);
663 g_free(info->logo_url);
664 g_free(info->type);
665 g_free(info->email);
666
667 if (info->created_at) g_date_time_unref(info->created_at);
668 if (info->updated_at) g_date_time_unref(info->updated_at);
669
670 g_slice_free(GtUserInfo, info);
671 }
672
673 GtOAuthInfo*
gt_oauth_info_new()674 gt_oauth_info_new()
675 {
676 return g_slice_new0(GtOAuthInfo);
677 }
678
679 void
gt_oauth_info_free(GtOAuthInfo * info)680 gt_oauth_info_free(GtOAuthInfo* info)
681 {
682 if (!info) return;
683
684 g_free(info->user_name);
685 g_free(info->user_id);
686 g_free(info->client_id);
687 g_strfreev(info->scopes);
688
689 if (info->created_at) g_date_time_unref(info->created_at);
690 if (info->updated_at) g_date_time_unref(info->updated_at);
691
692 g_slice_free(GtOAuthInfo, info);
693 }
694