Codebase list gnome-twitch / debian/0.4.1-1 src / gt-game.c
debian/0.4.1-1

Tree @debian/0.4.1-1 (Download .tar.gz)

gt-game.c @debian/0.4.1-1raw · history · blame

/*
 *  This file is part of GNOME Twitch - 'Enjoy Twitch on your GNU/Linux desktop'
 *  Copyright © 2017 Vincent Szolnoky <vinszent@vinszent.com>
 *
 *  GNOME Twitch is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GNOME Twitch is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNOME Twitch. If not, see <http://www.gnu.org/licenses/>.
 */

#include "gt-game.h"
#include "gt-app.h"
#include "gt-win.h"
#include "gt-resource-downloader.h"
#include "utils.h"
#include <glib/gi18n.h>

#define TAG "GtGame"
#include "gnome-twitch/gt-log.h"

typedef struct
{
    GtGameData* data;

    gboolean updating;

    GdkPixbuf* preview;

    GdkPixbuf* logo;

    GCancellable* cancel;

    guint notify_source_id;
} GtGamePrivate;

G_DEFINE_TYPE_WITH_PRIVATE(GtGame, gt_game, G_TYPE_INITIALLY_UNOWNED)

enum
{
    PROP_0,
    PROP_ID,
    PROP_NAME,
    PROP_UPDATING,
    PROP_PREVIEW_URL,
    PROP_PREVIEW,
    PROP_LOGO,
    PROP_VIEWERS,
    PROP_CHANNELS,
    NUM_PROPS
};

static GtResourceDownloader* res_downloader;

static GParamSpec* props[NUM_PROPS];

static gboolean
notify_preview_cb(gpointer udata)
{
    RETURN_VAL_IF_FAIL(GT_IS_GAME(udata), G_SOURCE_REMOVE);

    GtGame* self = GT_GAME(udata);
    GtGamePrivate* priv = gt_game_get_instance_private(self);

    g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PREVIEW]);

    priv->updating = FALSE;
    g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UPDATING]);

    priv->notify_source_id = 0;

    return G_SOURCE_REMOVE;
}

static void
download_image_cb(GdkPixbuf* pixbuf, gpointer udata, GError* error)
{
    RETURN_IF_FAIL(GT_IS_GAME(udata));

    GtGame* self = GT_GAME(udata);
    GtGamePrivate* priv = gt_game_get_instance_private(self);

    /* FIXME: Propagate error */
    RETURN_IF_FAIL(error == NULL);

    if (pixbuf)
    {
        priv->preview = pixbuf;

        utils_pixbuf_scale_simple(&priv->preview,
            200, 270, GDK_INTERP_BILINEAR);
    }

    if (priv->notify_source_id == 0)
    {
        /* NOTE: Don't need to ref ourselves because we are in a async
         * cb */
        priv->notify_source_id = g_idle_add_full(G_PRIORITY_LOW,
            notify_preview_cb, self, g_object_unref);
    }
}

static void
update_preview(GtGame* self)
{
    RETURN_IF_FAIL(GT_IS_GAME(self));

    GtGamePrivate* priv = gt_game_get_instance_private(self);

    /* utils_refresh_cancellable(&priv->cancel); */

    /* FIXME: Handle error below */
    priv->preview = gt_resource_downloader_download_image_immediately(res_downloader,
        priv->data->preview_url, priv->data->id, download_image_cb, g_object_ref(self), NULL);

    if (priv->preview)
    {
        utils_pixbuf_scale_simple(&priv->preview,
            200, 270, GDK_INTERP_BILINEAR);

        if (priv->notify_source_id == 0)
        {
            priv->notify_source_id = g_idle_add_full(G_PRIORITY_LOW,
                notify_preview_cb, g_object_ref(self), g_object_unref);
        }
    }
}

static void
update_from_data(GtGame* self, GtGameData* data)
{
    RETURN_IF_FAIL(GT_IS_GAME(self));
    RETURN_IF_FAIL(data != NULL);

    GtGamePrivate* priv = gt_game_get_instance_private(self);

    priv->updating = TRUE;
    g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UPDATING]);

    g_autoptr(GtGameData) old_data = priv->data;

    priv->data = data;

    if (old_data)
    {
        if (!STRING_EQUALS(old_data->id, data->id))
        {
            GtWin* win = GT_WIN_ACTIVE;

            WARNING("Unable to update game with id '%s' and name '%s' because: "
                "New data with id '%s' does not match the current one",
                old_data->id, old_data->name, data->id);

            /* FIXME: Put GtGame in a error state like GtChannel */
            if (GT_IS_WIN(win))
            {
                /* Translators: %s will be filled with the game name */
                gt_win_show_error_message(win, _("Unable to update game '%s'"),
                    "Unable to update game with id '%s' and name '%s' because: "
                    "New data with id '%s' does not match the current one",
                    old_data->id, old_data->name, data->id);
            }

            return;
        }
    }

    if (!old_data || !STRING_EQUALS(old_data->preview_url, data->preview_url))
        g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PREVIEW_URL]);
    if (!old_data || old_data->viewers != data->viewers)
        g_object_notify_by_pspec(G_OBJECT(self), props[PROP_VIEWERS]);
    if (!old_data || old_data->channels != data->channels)
        g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CHANNELS]);

    update_preview(self);
}

static void
dispose(GObject* object)
{
    GtGame* self = GT_GAME(object);
    GtGamePrivate* priv = gt_game_get_instance_private(self);

    g_clear_object(&priv->preview);
    g_clear_object(&priv->logo);

    G_OBJECT_CLASS(gt_game_parent_class)->dispose(object);
}

static void
finalize(GObject* object)
{
    GtGame* self = (GtGame*) object;
    GtGamePrivate* priv = gt_game_get_instance_private(self);

    gt_game_data_free(priv->data);

    if (priv->notify_source_id > 0)
        g_source_remove(priv->notify_source_id);

    G_OBJECT_CLASS(gt_game_parent_class)->finalize(object);
}

static void
get_property (GObject*    obj,
              guint       prop,
              GValue*     val,
              GParamSpec* pspec)
{
    GtGame* self = GT_GAME(obj);
    GtGamePrivate* priv = gt_game_get_instance_private(self);

    switch (prop)
    {
        case PROP_ID:
            g_value_set_string(val, priv->data->id);
            break;
        case PROP_NAME:
            g_value_set_string(val, priv->data->name);
            break;
        case PROP_UPDATING:
            g_value_set_boolean(val, priv->updating);
            break;
        case PROP_PREVIEW_URL:
            g_value_set_string(val, priv->data->preview_url);
            break;
        case PROP_PREVIEW:
            g_value_set_object(val, priv->preview);
            break;
        case PROP_LOGO:
            g_value_set_object(val, priv->logo);
            break;
        case PROP_VIEWERS:
            g_value_set_int64(val, priv->data->viewers);
            break;
        case PROP_CHANNELS:
            g_value_set_int64(val, priv->data->channels);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
    }
}

static void
set_property(GObject*      obj,
             guint         prop,
             const GValue* val,
             GParamSpec*   pspec)
{
    switch (prop)
    {
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
    }
}

static void
gt_game_class_init(GtGameClass* klass)
{
    GObjectClass* object_class = G_OBJECT_CLASS(klass);

    object_class->finalize = finalize;
    object_class->dispose = dispose;
    object_class->get_property = get_property;
    object_class->set_property = set_property;

    props[PROP_ID] = g_param_spec_string("id", "Id", "Id of game",
        NULL, G_PARAM_READABLE);

    props[PROP_NAME] = g_param_spec_string("name", "Name", "Name of game",
        NULL, G_PARAM_READABLE);

    props[PROP_UPDATING] = g_param_spec_boolean("updating", "Updating", "Whether updating",
        FALSE, G_PARAM_READABLE);

    props[PROP_PREVIEW_URL] = g_param_spec_string("preview-url", "Preview Url", "Current preview url",
        NULL, G_PARAM_READABLE);

    props[PROP_PREVIEW] = g_param_spec_object("preview", "Preview", "Preview of game",
        GDK_TYPE_PIXBUF, G_PARAM_READABLE);

    props[PROP_LOGO] = g_param_spec_object("logo", "Logo", "Logo of game",
        GDK_TYPE_PIXBUF, G_PARAM_READABLE);

    props[PROP_VIEWERS] = g_param_spec_int64("viewers", "Viewers", "Total viewers of game",
        0, G_MAXINT64, 0, G_PARAM_READABLE);

    props[PROP_CHANNELS] = g_param_spec_int64("channels", "Channels", "Total channels of game",
        0, G_MAXINT64, 0, G_PARAM_READABLE);

    g_object_class_install_properties(object_class,
                                      NUM_PROPS,
                                      props);

    g_autofree gchar* filepath = g_build_filename(g_get_user_cache_dir(), "gnome-twitch", "games", NULL);

    res_downloader = gt_resource_downloader_new_with_cache(filepath);
    gt_resource_downloader_set_image_filetype(res_downloader, GT_IMAGE_FILETYPE_JPEG);

    g_signal_connect_swapped(main_app, "shutdown", G_CALLBACK(g_object_unref), res_downloader);
}

static void
gt_game_init(GtGame* self)
{
    g_assert(GT_IS_GAME(self));

    GtGamePrivate* priv = gt_game_get_instance_private(self);

    priv->updating = FALSE;
    priv->preview = NULL;
    priv->logo = NULL;
}

GtGame*
gt_game_new(GtGameData* data)
{
    RETURN_VAL_IF_FAIL(data != NULL, NULL);

    GtGame* game = g_object_new(GT_TYPE_GAME, NULL);

    update_from_data(game, data);

    return game;
}

void
gt_game_list_free(GList* list)
{
    g_list_free_full(list, g_object_unref);
}

const gchar*
gt_game_get_name(GtGame* self)
{
    RETURN_VAL_IF_FAIL(GT_IS_GAME(self), NULL);

    GtGamePrivate* priv = gt_game_get_instance_private(self);

    return priv->data->name;
}

gboolean
gt_game_get_updating(GtGame* self)
{
    RETURN_VAL_IF_FAIL(GT_IS_GAME(self), FALSE);

    GtGamePrivate* priv = gt_game_get_instance_private(self);

    return priv->updating;
}

gint64
gt_game_get_viewers(GtGame* self)
{
    RETURN_VAL_IF_FAIL(GT_IS_GAME(self), -1);

    GtGamePrivate* priv = gt_game_get_instance_private(self);

    return priv->data->viewers;
}

GtGameData*
gt_game_data_new()
{
    GtGameData* ret = g_slice_new0(GtGameData);
    ret->viewers = -1;

    return ret;
}

void
gt_game_data_free(GtGameData* data)
{
    if (!data) return;

    g_free(data->name);
    g_free(data->preview_url);
    g_free(data->logo_url);
    g_slice_free(GtGameData, data);
}