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