~tristan957/harvest-almanac

c097fbd75859ab96a132b7376774c02106bc7649 — Tristan Partin 1 year, 9 months ago bfe5e77
harvest-glib: remove library restriction on # of clients
M data/ui/hal-window.ui => data/ui/hal-window.ui +67 -63
@@ 23,7 23,7 @@
      <headerbar name="sub_header_bar" />
    </headerbars>
  </object>
  <object class="GtkPopoverMenu" id="actions_popover">
  <object class="GtkPopoverMenu" id="user_actions_popover">
    <property name="can_focus">False</property>
    <child>
      <object class="GtkBox">


@@ 34,13 34,14 @@
        <property name="margin_top">3</property>
        <property name="margin_bottom">3</property>
        <property name="orientation">vertical</property>
        <property name="spacing">3</property>
        <child>
          <object class="GtkModelButton">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="action_name">app.preferences</property>
            <property name="text" translatable="yes">Preferences</property>
            <property name="action_name">win.show-details</property>
            <property name="text" translatable="yes">Details</property>
          </object>
          <packing>
            <property name="expand">False</property>


@@ 64,8 65,8 @@
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="action_name">app.about</property>
            <property name="text" translatable="yes">About</property>
            <property name="action_name">app.logout</property>
            <property name="text" translatable="yes">Logout</property>
          </object>
          <packing>
            <property name="expand">False</property>


@@ 76,7 77,6 @@
      </object>
      <packing>
        <property name="submenu">main</property>
        <property name="position">1</property>
      </packing>
    </child>
  </object>


@@ 161,7 161,7 @@
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="focus_on_click">False</property>
                    <property name="popover">actions_popover</property>
                    <property name="popover">user_actions_popover</property>
                    <child>
                      <object class="GtkImage">
                        <property name="visible">True</property>


@@ 243,103 243,107 @@
            <property name="orientation">vertical</property>
            <property name="hexpand">False</property>
            <child>
              <object class="GtkBox">
              <object class="GtkStack" id="user_status_stack">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="valign">start</property>
                <property name="vexpand">False</property>
                <property name="transition_type">crossfade</property>
                <property name="margin_left">15</property>
                <property name="margin_right">15</property>
                <property name="margin_top">15</property>
                <property name="margin_bottom">15</property>
                <property name="spacing">15</property>
                <child>
                  <object class="GtkImage">
                  <object class="GtkLabel">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <property name="pixel_size">40</property>
                    <property name="icon_name">user-info-symbolic</property>
                    <property name="label" translatable="yes">Logged Out</property>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">0</property>
                    <property name="name">logged-out</property>
                    <property name="title" translatable="yes">Logged Out</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkBox">
                  <object class="GtkGrid">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <property name="orientation">vertical</property>
                    <property name="spacing">3</property>
                    <property name="row_spacing">5</property>
                    <property name="column_spacing">15</property>
                    <child>
                      <object class="GtkLabel" id="username">
                      <object class="GtkImage">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="valign">end</property>
                        <property name="vexpand">True</property>
                        <property name="label" translatable="yes"></property>
                        <property name="xalign">0</property>
                        <property name="halign">center</property>
                        <property name="valign">center</property>
                        <property name="pixel_size">40</property>
                        <property name="icon_name">user-info-symbolic</property>
                        <property name="icon_size">2</property>
                      </object>
                      <packing>
                        <property name="expand">False</property>
                        <property name="fill">True</property>
                        <property name="position">0</property>
                        <property name="left_attach">0</property>
                        <property name="top_attach">0</property>
                        <property name="height">2</property>
                      </packing>
                    </child>
                    <child>
                      <object class="GtkLabel" id="company">
                      <object class="GtkLabel" id="user_name_label">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="valign">start</property>
                        <property name="vexpand">True</property>
                        <property name="label" translatable="yes"></property>
                        <property name="label" translatable="yes">Tristan Partin</property>
                        <property name="xalign">0</property>
                        <property name="yalign">1</property>
                      </object>
                      <packing>
                        <property name="expand">False</property>
                        <property name="fill">True</property>
                        <property name="position">1</property>
                        <property name="left_attach">1</property>
                        <property name="top_attach">0</property>
                      </packing>
                    </child>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="position">1</property>
                  </packing>
                </child>
                <child>
                  <object class="GtkMenuButton">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="receives_default">True</property>
                    <property name="valign">center</property>
                    <property name="popover">account_popover</property>
                    <child>
                      <object class="GtkImage">
                      <object class="GtkLabel" id="company_label">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="icon_name">view-more-symbolic</property>
                        <property name="label" translatable="yes">Expero</property>
                        <property name="xalign">0</property>
                        <property name="yalign">0</property>
                      </object>
                      <packing>
                        <property name="left_attach">1</property>
                        <property name="top_attach">1</property>
                      </packing>
                    </child>
                    <child>
                      <object class="GtkMenuButton">
                        <property name="visible">True</property>
                        <property name="can_focus">True</property>
                        <property name="receives_default">True</property>
                        <property name="halign">end</property>
                        <property name="valign">center</property>
                        <property name="hexpand">True</property>
                        <property name="popover">user_actions_popover</property>
                        <child>
                          <object class="GtkImage">
                            <property name="visible">True</property>
                            <property name="can_focus">False</property>
                            <property name="icon_name">view-more-symbolic</property>
                          </object>
                        </child>
                        <style>
                          <class name="flat" />
                        </style>
                      </object>
                      <packing>
                        <property name="left_attach">2</property>
                        <property name="top_attach">0</property>
                        <property name="height">2</property>
                      </packing>
                    </child>
                    <style>
                      <class name="flat" />
                    </style>
                  </object>
                  <packing>
                    <property name="expand">False</property>
                    <property name="fill">True</property>
                    <property name="pack_type">end</property>
                    <property name="position">2</property>
                    <property name="name">logged-in</property>
                    <property name="title" translatable="yes">Logged In</property>
                    <property name="position">1</property>
                  </packing>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">False</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkSeparator">

M harvest-almanac/hal-application.c => harvest-almanac/hal-application.c +9 -4
@@ 23,6 23,8 @@ extern HalContext *CONTEXT;
extern HarvestApiClient *API_CLIENT;
extern HalTimeEntry *CURRENTLY_RUNNING_TIME_ENTRY;

static HarvestApiClient *CLIENT;

struct _HalApplication
{
	GtkApplication parent_instance;


@@ 57,7 59,7 @@ validate_user(G_GNUC_UNUSED HarvestRequest *req, HarvestResponse *res, gpointer 
	HalApplicationPrivate *priv = hal_application_get_instance_private(self);

	if (res->err == NULL) {
		harvest_company_get_company_async(set_company, self);
		harvest_company_get_company_async(CLIENT, set_company, self);
		hal_context_set_user(g_value_get_object(res->body));
		hal_window_show_content(priv->main_window);
	} else {


@@ 86,9 88,12 @@ construct_client(HalApplication *self, const char *access_token, const char *acc
		max_connections, SOUP_SESSION_USER_AGENT, user_agent, SOUP_SESSION_ADD_FEATURE_BY_TYPE,
		SOUP_TYPE_CONTENT_SNIFFER, SOUP_SESSION_ADD_FEATURE, SOUP_SESSION_FEATURE(logger), NULL);

	harvest_api_client_initialize(session, access_token, account_id);
	if (CLIENT != NULL)
		g_object_unref(CLIENT);

	CLIENT = harvest_api_client_new(session, access_token, account_id);

	harvest_user_get_me_async(validate_user, self);
	harvest_user_get_me_async(CLIENT, validate_user, self);
}

static void


@@ 205,7 210,7 @@ hal_application_finalize(GObject *obj)
{
	HalApplication *self = HAL_APPLICATION(obj);

	harvest_api_client_free();
	g_clear_object(&CLIENT);
	g_clear_object(&self->settings);
	g_clear_object(&CONTEXT);


M harvest-almanac/hal-window.c => harvest-almanac/hal-window.c +10 -7
@@ 28,8 28,9 @@ typedef struct HalWindowPrivate
	GtkStackSidebar *stack_sidebar;
	GtkStack *stack;
	GtkButton *back_button;
	GtkLabel *username;
	GtkLabel *company;
	GtkStack *user_status_stack;
	GtkLabel *user_name_label;
	GtkLabel *company_label;
	HalTimeTracker *time_tracker;
	HalProfile *profile;
} HalWindowPrivate;


@@ 45,8 46,9 @@ on_context_notify_user(
	HarvestUser *user	   = hal_context_get_user();

	if (user != NULL) {
		gtk_label_set_text(priv->username, g_strconcat(harvest_user_get_first_name(user), " ",
											   harvest_user_get_last_name(user), NULL));
		gtk_label_set_text(priv->user_name_label, g_strconcat(harvest_user_get_first_name(user),
													  " ", harvest_user_get_last_name(user), NULL));
		gtk_stack_set_visible_child_name(priv->user_status_stack, "logged-in");
	}
}



@@ 59,7 61,7 @@ on_contect_notify_computer(
	HarvestCompany *company = hal_context_get_company();

	if (company != NULL) {
		gtk_label_set_text(priv->company, harvest_company_get_name(company));
		gtk_label_set_text(priv->company_label, harvest_company_get_name(company));
	}
}



@@ 166,8 168,9 @@ hal_window_class_init(HalWindowClass *klass)
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, stack_sidebar);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, stack);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, back_button);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, username);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, company);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, user_status_stack);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, user_name_label);
	gtk_widget_class_bind_template_child_private(wid_class, HalWindow, company_label);
	gtk_widget_class_bind_template_callback(wid_class, header_leaflet_notify_fold_cb);
	gtk_widget_class_bind_template_callback(wid_class, header_leaflet_notify_visible_child_cb);
	gtk_widget_class_bind_template_callback(wid_class, stack_notify_visible_child_cb);

M harvest-glib/harvest-api-client.c => harvest-glib/harvest-api-client.c +26 -32
@@ 18,8 18,6 @@

#define HARVEST_API_URL "https://api.harvestapp.com/v2"

static HarvestApiClient *API_CLIENT;

struct _HarvestApiClient
{
	GObject parent_instance;


@@ 75,8 73,10 @@ harvest_api_client_set_property(GObject *obj, guint prop_id, const GValue *val, 

	switch (prop_id) {
	case PROP_SESSION:
		if (self->session != NULL)
		if (self->session != NULL) {
			soup_session_abort(self->session);
			g_object_unref(self->session);
		}
		self->session = g_value_dup_object(val);
		break;
	case PROP_SERVER:


@@ 102,6 102,10 @@ harvest_api_client_finalize(GObject *obj)
{
	HarvestApiClient *self = HARVEST_API_CLIENT(obj);

	if (self->session != NULL) {
		soup_session_abort(self->session);
		g_object_unref(self->session);
	}
	g_free(self->server);
	g_free(self->access_token);
	g_free(self->account_id);


@@ 138,24 142,14 @@ static void
harvest_api_client_init(G_GNUC_UNUSED HarvestApiClient *self)
{}

void
harvest_api_client_initialize(
	SoupSession *session, const char *access_token, const char *account_id)
HarvestApiClient *
harvest_api_client_new(SoupSession *session, const char *access_token, const char *account_id)
{
	g_return_if_fail(SOUP_IS_SESSION(session) && access_token != NULL && account_id != NULL);

	if (API_CLIENT != NULL)
		g_object_unref(API_CLIENT);
	g_return_val_if_fail(
		SOUP_IS_SESSION(session) && access_token != NULL && account_id != NULL, NULL);

	API_CLIENT = g_object_new(HARVEST_TYPE_API_CLIENT, "session", session, "server",
		HARVEST_API_URL, "access-token", access_token, "account-id", account_id, NULL);
}

void
harvest_api_client_free()
{
	if (API_CLIENT != NULL)
		g_object_unref(API_CLIENT);
	return g_object_new(HARVEST_TYPE_API_CLIENT, "session", session, "server", HARVEST_API_URL,
		"access-token", access_token, "account-id", account_id, NULL);
}

static void


@@ 178,7 172,8 @@ create_response(HarvestRequest *req, SoupMessage *msg)

	if (msg->status_code != expected) {
		g_set_error(&err, HARVEST_API_CLIENT_ERROR, HARVEST_API_CLIENT_ERROR_UNEXPECTED_STATUS,
			"unexpected status code of %u, expected %u", expected, msg->status_code);
			"create_response: unexpected status code of %u, expected %u", expected,
			msg->status_code);
	}

	if (err == NULL && body_type != G_TYPE_NONE) {


@@ 266,11 261,10 @@ harvest_api_client_async_callback(
}

static SoupMessage *
create_message(HarvestRequest *req)
create_message(HarvestApiClient *self, HarvestRequest *req)
{
	SoupMessage *msg = NULL;
	g_autofree char *uri
		= g_strdup_printf("%s%s", API_CLIENT->server, harvest_request_get_endpoint(req));
	SoupMessage *msg	 = NULL;
	g_autofree char *uri = g_strdup_printf("%s%s", self->server, harvest_request_get_endpoint(req));
	gboolean response_has_body = harvest_request_get_data(req) != NULL;
	char *body				   = NULL;
	gsize len				   = 0;


@@ 296,19 290,19 @@ create_message(HarvestRequest *req)
		g_return_val_if_reached(NULL);
	}

	soup_message_headers_append(msg->request_headers, "Authorization", API_CLIENT->access_token);
	soup_message_headers_append(msg->request_headers, "Harvest-Account-Id", API_CLIENT->account_id);
	soup_message_headers_append(msg->request_headers, "Authorization", self->access_token);
	soup_message_headers_append(msg->request_headers, "Harvest-Account-Id", self->account_id);

	return msg;
}

HarvestResponse *
harvest_api_client_execute_request_sync(HarvestRequest *req)
harvest_api_client_execute_request_sync(HarvestApiClient *self, HarvestRequest *req)
{
	g_return_val_if_fail(HARVEST_IS_REQUEST(req), NULL);

	g_autoptr(SoupMessage) msg = create_message(req);
	soup_session_send_message(API_CLIENT->session, msg);
	g_autoptr(SoupMessage) msg = create_message(self, req);
	soup_session_send_message(self->session, msg);
	HarvestResponse *res = create_response(req, msg);
	g_signal_emit_by_name(req, "completed", res);



@@ 316,13 310,13 @@ harvest_api_client_execute_request_sync(HarvestRequest *req)
}

void
harvest_api_client_execute_request_async(HarvestRequest *req)
harvest_api_client_execute_request_async(HarvestApiClient *self, HarvestRequest *req)
{
	g_return_if_fail(HARVEST_IS_REQUEST(req));

	SoupMessage *msg = create_message(req);
	SoupMessage *msg = create_message(self, req);

	soup_session_queue_message(API_CLIENT->session, msg, harvest_api_client_async_callback, req);
	soup_session_queue_message(self->session, msg, harvest_api_client_async_callback, req);
}

G_DEFINE_QUARK(harvest_api_client_error, harvest_api_client_error)

M harvest-glib/harvest-api-client.h => harvest-glib/harvest-api-client.h +5 -5
@@ 23,10 23,10 @@ typedef enum
	HARVEST_API_CLIENT_ERROR_UNABLE_TO_DESERIALIZE_OBJECT,
} HarevestApiClientError;

void harvest_api_client_initialize(
	SoupSession *session, const char *access_token, const char *account_id);
void harvest_api_client_free(void);
HarvestResponse *harvest_api_client_execute_request_sync(HarvestRequest *req);
void harvest_api_client_execute_request_async(HarvestRequest *req);
HarvestApiClient *harvest_api_client_new(SoupSession *session, const char *access_token,
	const char *account_id) G_GNUC_WARN_UNUSED_RESULT;
HarvestResponse *harvest_api_client_execute_request_sync(
	HarvestApiClient *self, HarvestRequest *req);
void harvest_api_client_execute_request_async(HarvestApiClient *self, HarvestRequest *req);

G_END_DECLS

M harvest-glib/harvest-company.c => harvest-glib/harvest-company.c +7 -4
@@ 372,18 372,21 @@ harvest_company_get_name(HarvestCompany *self)
}

HarvestResponse *
harvest_company_get_company()
harvest_company_get_company(HarvestApiClient *client)
{
	g_return_val_if_fail(HARVEST_IS_API_CLIENT(client), NULL);

	g_autoptr(HarvestGetCompanyRequest) request = harvest_get_company_request_new();

	return harvest_api_client_execute_request_sync(HARVEST_REQUEST(request));
	return harvest_api_client_execute_request_sync(client, HARVEST_REQUEST(request));
}

void
harvest_company_get_company_async(HarvestCompletedCallback *callback, gpointer user_data)
harvest_company_get_company_async(
	HarvestApiClient *client, HarvestCompletedCallback *callback, gpointer user_data)
{
	HarvestGetCompanyRequest *request = harvest_get_company_request_new();
	g_signal_connect(HARVEST_REQUEST(request), "completed", G_CALLBACK(callback), user_data);

	harvest_api_client_execute_request_async(HARVEST_REQUEST(request));
	harvest_api_client_execute_request_async(client, HARVEST_REQUEST(request));
}

M harvest-glib/harvest-company.h => harvest-glib/harvest-company.h +3 -2
@@ 44,7 44,8 @@ typedef enum
const char *harvest_company_get_full_domain(HarvestCompany *self);
const char *harvest_company_get_name(HarvestCompany *self);

HarvestResponse *harvest_company_get_company(void) G_GNUC_WARN_UNUSED_RESULT;
void harvest_company_get_company_async(HarvestCompletedCallback *callback, gpointer user_data);
HarvestResponse *harvest_company_get_company(HarvestApiClient *client) G_GNUC_WARN_UNUSED_RESULT;
void harvest_company_get_company_async(
	HarvestApiClient *client, HarvestCompletedCallback *callback, gpointer user_data);

G_END_DECLS

M harvest-glib/harvest-user.c => harvest-glib/harvest-user.c +20 -5
@@ 10,6 10,7 @@
#include <json-glib/json-glib.h>

#include "harvest-api-client.h"
#include "harvest-response.h"
#include "harvest-user.h"
#include "harvest-users-me-request.h"



@@ 98,10 99,18 @@ harvest_user_json_deserialize_property(JsonSerializable *serializable, const gch
		serializable, prop_name, val, pspec, prop_node);
}

static JsonNode *
harvest_user_json_serialize_property(
	JsonSerializable *serializable, const gchar *prop_name, const GValue *val, GParamSpec *pspec)
{
	return json_serializable_default_serialize_property(serializable, prop_name, val, pspec);
}

static void
harvest_user_json_serializable_init(JsonSerializableIface *iface)
{
	iface->deserialize_property = harvest_user_json_deserialize_property;
	iface->serialize_property	= harvest_user_json_serialize_property;
}

static void


@@ 387,20 396,26 @@ harvest_user_init(G_GNUC_UNUSED HarvestUser *self)
{}

HarvestResponse *
harvest_user_get_me()
harvest_user_get_me(HarvestApiClient *client)

{
	HarvestUsersMeRequest *request = harvest_users_me_request_new();
	g_return_val_if_fail(HARVEST_IS_API_CLIENT(client), NULL);

	g_autoptr(HarvestUsersMeRequest) request = harvest_users_me_request_new();

	return harvest_api_client_execute_request_sync(HARVEST_REQUEST(request));
	return harvest_api_client_execute_request_sync(client, HARVEST_REQUEST(request));
}

void
harvest_user_get_me_async(HarvestCompletedCallback *callback, gpointer user_data)
harvest_user_get_me_async(
	HarvestApiClient *client, HarvestCompletedCallback *callback, gpointer user_data)
{
	g_return_if_fail(HARVEST_IS_API_CLIENT(client));

	HarvestUsersMeRequest *request = harvest_users_me_request_new();
	g_signal_connect(HARVEST_REQUEST(request), "completed", G_CALLBACK(callback), user_data);

	harvest_api_client_execute_request_async(HARVEST_REQUEST(request));
	harvest_api_client_execute_request_async(client, HARVEST_REQUEST(request));
}

const char *

M harvest-glib/harvest-user.h => harvest-glib/harvest-user.h +3 -2
@@ 15,8 15,9 @@ G_BEGIN_DECLS
#define HARVEST_TYPE_USER (harvest_user_get_type())
G_DECLARE_FINAL_TYPE(HarvestUser, harvest_user, HARVEST, USER, GObject)

HarvestResponse *harvest_user_get_me();
void harvest_user_get_me_async(HarvestCompletedCallback *callback, gpointer user_data);
HarvestResponse *harvest_user_get_me(HarvestApiClient *client) G_GNUC_WARN_UNUSED_RESULT;
void harvest_user_get_me_async(
	HarvestApiClient *client, HarvestCompletedCallback *callback, gpointer user_data);
const char *harvest_user_get_first_name(HarvestUser *user);
const char *harvest_user_get_last_name(HarvestUser *user);


M harvest-glib/tests/init.h => harvest-glib/tests/init.h +2 -2
@@ 7,7 7,7 @@

#include "harvest.h"

void
HarvestApiClient *
initialize_environment(void)
{
	char **env				  = g_get_environ();


@@ 25,5 25,5 @@ initialize_environment(void)
		max_connections, SOUP_SESSION_USER_AGENT, user_agent, SOUP_SESSION_ADD_FEATURE_BY_TYPE,
		SOUP_TYPE_CONTENT_SNIFFER, SOUP_SESSION_ADD_FEATURE, SOUP_SESSION_FEATURE(logger), NULL);

	harvest_api_client_initialize(session, access_token, account_id);
	return harvest_api_client_new(session, access_token, account_id);
}

M harvest-glib/tests/user.c => harvest-glib/tests/user.c +4 -2
@@ 4,10 4,12 @@
#include "harvest.h"
#include "init.h"

static HarvestApiClient *CLIENT;

static void
test_harvest_get_me(void)
{
	HarvestResponse *res = harvest_user_get_me();
	HarvestResponse *res = harvest_user_get_me(CLIENT);

	g_assert_null(res->err);
}


@@ 17,7 19,7 @@ main(int argc, char **argv)
{
	g_test_init(&argc, &argv, NULL);

	initialize_environment();
	CLIENT = initialize_environment();

	g_test_add_func("/user/me", test_harvest_get_me);