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-vod.h"
20 #include "gt-app.h"
21 #include "gt-http.h"
22 #include "utils.h"
23 #include <gdk-pixbuf/gdk-pixbuf.h>
24
25 #define TAG "GtVOD"
26 #include "gnome-twitch/gt-log.h"
27
28 #define PREVIEW_WIDTH 320
29 #define PREVIEW_HEIGHT 180
30
31 typedef struct
32 {
33 GtVODData* data;
34
35 gchar* preview_filepath;
36
37 gboolean updating;
38
39 GdkPixbuf* preview;
40 GCancellable* cancel;
41 } GtVODPrivate;
42
43 G_DEFINE_TYPE_WITH_PRIVATE(GtVOD, gt_vod, G_TYPE_OBJECT);
44
45 enum
46 {
47 PROP_0,
48 PROP_CREATED_AT,
49 PROP_PUBLISHED_AT,
50 PROP_TITLE,
51 PROP_DESCRIPTION,
52 PROP_GAME,
53 PROP_LANGUAGE,
54 PROP_LENGTH,
55 PROP_PREVIEW,
56 PROP_VIEWS,
57 PROP_URL,
58 PROP_TAG_LIST,
59 PROP_UPDATING,
60 NUM_PROPS,
61 };
62
63 static GParamSpec* props[NUM_PROPS];
64
65 static void
handle_preview_download_cb(GObject * source,GAsyncResult * res,gpointer udata)66 handle_preview_download_cb(GObject* source,
67 GAsyncResult* res, gpointer udata)
68 {
69 RETURN_IF_FAIL(G_IS_ASYNC_RESULT(res));
70 RETURN_IF_FAIL(udata != NULL);
71
72 g_autoptr(GWeakRef) ref = udata;
73 g_autoptr(GtVOD) self = g_weak_ref_get(ref);
74
75 if (!self)
76 {
77 TRACE("Unreffed while wating");
78 return;
79 }
80
81 GtVODPrivate* priv = gt_vod_get_instance_private(self);
82 g_autoptr(GError) err = NULL;
83
84 priv->preview = gdk_pixbuf_new_from_stream_finish(res, &err);
85
86 RETURN_IF_ERROR(err); /* FIXME: Handle error */
87
88 if (g_object_steal_data(G_OBJECT(self), "save-preview"))
89 {
90 gdk_pixbuf_save(priv->preview, priv->preview_filepath, "jpeg",
91 &err, "quality", "100", NULL);
92
93 /* NOTE: Don't need to show the user this error as it just
94 * means we couldn't cache the image */
95 RETURN_IF_ERROR(err);
96 }
97
98 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PREVIEW]);
99
100 priv->updating = FALSE;
101 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UPDATING]);
102 }
103
104 static void
handle_preview_response_cb(GtHTTP * http,gpointer res,GError * error,gpointer udata)105 handle_preview_response_cb(GtHTTP* http,
106 gpointer res, GError* error, gpointer udata)
107 {
108 RETURN_IF_FAIL(GT_IS_HTTP(http));
109 RETURN_IF_FAIL(udata != NULL);
110
111 g_autoptr(GWeakRef) ref = udata;
112 g_autoptr(GtVOD) self = g_weak_ref_get(ref);
113
114 if (!self) {TRACE("Unreffed while waiting"); return;}
115
116 GtVODPrivate* priv = gt_vod_get_instance_private(self);
117
118 RETURN_IF_ERROR(error); /* FIXME: Handle error */
119
120 RETURN_IF_FAIL(G_IS_INPUT_STREAM(res));
121
122 gdk_pixbuf_new_from_stream_at_scale_async(res, PREVIEW_WIDTH, PREVIEW_HEIGHT,
123 FALSE, priv->cancel, handle_preview_download_cb, g_steal_pointer(&ref));
124 }
125 static void
update_preview(GtVOD * self)126 update_preview(GtVOD* self)
127 {
128 g_assert(GT_IS_VOD(self));
129
130 GtVODPrivate* priv = gt_vod_get_instance_private(self);
131
132 gt_http_get_with_category(main_app->http, priv->data->preview.large, "gt-vod", DEFAULT_TWITCH_HEADERS,
133 priv->cancel, G_CALLBACK(handle_preview_response_cb), utils_weak_ref_new(self), GT_HTTP_FLAG_RETURN_STREAM | GT_HTTP_FLAG_CACHE_RESPONSE);
134 }
135
136 static void
update_from_data(GtVOD * self,GtVODData * data)137 update_from_data(GtVOD* self, GtVODData* data)
138 {
139 g_assert(GT_IS_VOD(self));
140 g_assert(data != NULL);
141
142 GtVODPrivate* priv = gt_vod_get_instance_private(self);
143
144 RETURN_IF_FAIL(priv->data == NULL);
145
146 priv->updating = TRUE;
147 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UPDATING]);
148
149 utils_refresh_cancellable(&priv->cancel);
150
151 priv->data = data;
152 priv->preview_filepath = g_build_filename(g_get_user_cache_dir(),
153 "gnome-twitch", "vods", data->id, NULL);
154
155 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CREATED_AT]);
156 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PUBLISHED_AT]);
157 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_TITLE]);
158 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]);
159 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_GAME]);
160 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_LANGUAGE]);
161 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_LENGTH]);
162 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_VIEWS]);
163 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_URL]);
164 g_object_notify_by_pspec(G_OBJECT(self), props[PROP_TAG_LIST]);
165
166 update_preview(self);
167 }
168
169 static void
get_property(GObject * obj,guint prop,GValue * val,GParamSpec * pspec)170 get_property(GObject* obj,
171 guint prop, GValue* val, GParamSpec* pspec)
172 {
173 GtVOD* self = GT_VOD(obj);
174 GtVODPrivate* priv = gt_vod_get_instance_private(self);
175
176 switch (prop)
177 {
178 case PROP_CREATED_AT:
179 g_value_set_boxed(val, priv->data->created_at);
180 break;
181 case PROP_PUBLISHED_AT:
182 g_value_set_boxed(val, priv->data->published_at);
183 break;
184 case PROP_TITLE:
185 g_value_set_string(val, priv->data->title);
186 break;
187 case PROP_GAME:
188 g_value_set_string(val, priv->data->game);
189 break;
190 case PROP_PREVIEW:
191 g_value_set_object(val, priv->preview);
192 break;
193 case PROP_UPDATING:
194 g_value_set_boolean(val, priv->updating);
195 break;
196 default:
197 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
198 }
199 }
200
201 static void
gt_vod_class_init(GtVODClass * klass)202 gt_vod_class_init(GtVODClass* klass)
203 {
204 G_OBJECT_CLASS(klass)->get_property = get_property;
205
206 props[PROP_CREATED_AT] = g_param_spec_boxed("created-at", "Created at", "Date and time when VOD was created",
207 G_TYPE_DATE_TIME, G_PARAM_READABLE);
208
209 props[PROP_PUBLISHED_AT] = g_param_spec_boxed("published-at", "Published at", "Date and time when VOD was published",
210 G_TYPE_DATE_TIME, G_PARAM_READABLE);
211
212 props[PROP_TITLE] = g_param_spec_string("title", "Title", "Title of the VOD",
213 NULL, G_PARAM_READABLE);
214
215 props[PROP_DESCRIPTION] = g_param_spec_string("description", "Description", "Description of the VOD",
216 NULL, G_PARAM_READABLE);
217
218 props[PROP_LANGUAGE] = g_param_spec_string("language", "Language", "Language of the VOD",
219 NULL, G_PARAM_READABLE);
220
221 props[PROP_GAME] = g_param_spec_string("game", "Game", "Game being played in the VOD",
222 NULL, G_PARAM_READABLE);
223
224 props[PROP_URL] = g_param_spec_string("url", "URL", "URL to the VOD",
225 NULL, G_PARAM_READABLE);
226
227 props[PROP_TAG_LIST] = g_param_spec_string("tag-list", "Tag list", "Tag list of the VOD",
228 NULL, G_PARAM_READABLE);
229
230 props[PROP_LENGTH] = g_param_spec_int64("length", "Length", "Length of the VOD in seconds",
231 0, G_MAXINT64, 0, G_PARAM_READABLE);
232
233 props[PROP_VIEWS] = g_param_spec_int64("views", "Views", "Number of views the VOD has",
234 0, G_MAXINT64, 0, G_PARAM_READABLE);
235
236 props[PROP_PREVIEW] = g_param_spec_object("preview", "Preview", "Preview pixbuf of the VOD",
237 GDK_TYPE_PIXBUF, G_PARAM_READABLE);
238
239 props[PROP_UPDATING] = g_param_spec_boolean("updating", "Updating", "Whether updating",
240 FALSE, G_PARAM_READABLE);
241
242 g_object_class_install_properties(G_OBJECT_CLASS(klass), NUM_PROPS, props);
243 }
244
245 static void
gt_vod_init(GtVOD * self)246 gt_vod_init(GtVOD* self)
247 {
248 g_assert(GT_IS_VOD(self));
249
250 GtVODPrivate* priv = gt_vod_get_instance_private(self);
251
252 priv->data = NULL;
253 }
254
255 GtVOD*
gt_vod_new(GtVODData * data)256 gt_vod_new(GtVODData* data)
257 {
258 RETURN_VAL_IF_FAIL(data != NULL, NULL);
259
260 GtVOD* self = g_object_new(GT_TYPE_VOD, NULL);
261
262 update_from_data(self, data);
263
264 return self;
265 }
266
267 const gchar*
gt_vod_get_id(GtVOD * self)268 gt_vod_get_id(GtVOD* self)
269 {
270 RETURN_VAL_IF_FAIL(GT_IS_VOD(self), NULL);
271
272 GtVODPrivate* priv = gt_vod_get_instance_private(self);
273
274 return priv->data->id;
275 }
276
277 gboolean
gt_vod_get_updating(GtVOD * self)278 gt_vod_get_updating(GtVOD* self)
279 {
280 RETURN_VAL_IF_FAIL(GT_IS_VOD(self), FALSE);
281
282 GtVODPrivate* priv = gt_vod_get_instance_private(self);
283
284 return priv->updating;
285 }
286
287 GtVODData*
gt_vod_data_new()288 gt_vod_data_new()
289 {
290 return g_slice_new(GtVODData);
291 }
292
293 void
gt_vod_data_free(GtVODData * data)294 gt_vod_data_free(GtVODData* data)
295 {
296 g_free(data->id);
297 g_free(data->broadcast_id);
298 g_free(data->description);
299 g_free(data->game);
300 g_free(data->language);
301 /* NOTE: Uncomment when needed */
302 /* gt_channel_data_free(data->channel); */
303 g_free(data->preview.large);
304 g_free(data->preview.medium);
305 g_free(data->preview.small);
306 g_free(data->preview.template);
307 g_free(data->title);
308 g_free(data->url);
309 g_free(data->tag_list);
310 g_date_time_unref(data->created_at);
311 g_date_time_unref(data->published_at);
312
313 g_slice_free(GtVODData, data);
314 }
315