~tristan957/harvest-almanac

efc35e315cb9133ba3df826d102bce109f9d41e8 — Tristan Partin 1 year, 11 months ago c2c8582
harvest-almanac: made first request
M harvest-almanac/hal-application.c => harvest-almanac/hal-application.c +19 -4
@@ 6,7 6,7 @@

#include <glib-object.h>
#include <gtk/gtk.h>
#include <harvest.h>
#include <harvest-glib/harvest.h>
#include <libsecret/secret.h>
#include <libsoup/soup.h>



@@ 34,6 34,21 @@ typedef struct HalApplicationPrivate
G_DEFINE_TYPE_WITH_PRIVATE(HalApplication, hal_application, GTK_TYPE_APPLICATION)

static void
validate_user(
	G_GNUC_UNUSED HarvestRequest *req, G_GNUC_UNUSED HarvestResponse *res, gpointer user_data)
{
	HalApplication *self		= HAL_APPLICATION(user_data);
	HalApplicationPrivate *priv = hal_application_get_instance_private(self);

	if (res->err == NULL) {
		hal_window_show_content(priv->main_window);
	} else {
		g_error("Error hitting /users/me (%u): %s", res->status_code, res->err->message);
		hal_window_hide_content(priv->main_window);
	}
}

static void
construct_client(HalApplication *self, const char *access_token, const char *account_id,
	const char *contact_email)
{


@@ 48,13 63,15 @@ construct_client(HalApplication *self, const char *access_token, const char *acc
	g_autoptr(SoupLogger) logger = soup_logger_new(logger_level, -1);

	g_autoptr(GString) user_agent = g_string_new(NULL);
	g_string_append_printf(user_agent, "Harvest Almanac (%s)", contact_email);
	g_string_printf(user_agent, "Harvest Almanac (%s)", contact_email);

	SoupSession *session = soup_session_new_with_options(SOUP_SESSION_MAX_CONNS, max_connections,
		SOUP_SESSION_USER_AGENT, user_agent->str, SOUP_SESSION_ADD_FEATURE_BY_TYPE,
		SOUP_TYPE_CONTENT_SNIFFER, SOUP_SESSION_ADD_FEATURE, SOUP_SESSION_FEATURE(logger), NULL);

	self->client = harvest_api_client_new(session, access_token, account_id);

	harvest_user_get_me_async(validate_user, self);
}

static void


@@ 82,11 99,9 @@ hal_application_activate(GApplication *app)
			g_error("Failed to look up Harvest API access token: %s", err->message);
		} else if (access_token == NULL) {
			g_debug("Harvest API access token is <null>");
			hal_window_hide_content(priv->main_window);
		} else {
			g_debug("Constructing Harvest API access token");
			construct_client(self, access_token, account_id, contact_email);
			hal_window_show_content(priv->main_window);
			secret_password_free(access_token);
		}
	}

M harvest-almanac/hal-window.c => harvest-almanac/hal-window.c +2 -2
@@ 44,6 44,8 @@ hal_window_show_content(HalWindow *self)
	 * valid using the me endpoint.
	 */

	self->user_validated = TRUE;

	gtk_stack_set_visible_child_name(GTK_STACK(priv->profile), "profile");
}



@@ 136,8 138,6 @@ hal_window_init(HalWindow *self)
	gtk_container_child_set(GTK_CONTAINER(priv->function_stack), GTK_WIDGET(priv->time_tracker),
		"icon-name", "document-open-recent-symbolic", NULL);

	g_autoptr(GSettings) settings = g_settings_new("io.partin.tristan.HarvestAlmanac");

	self->user_validated = FALSE;

	g_object_bind_property_full(priv->function_squeezer, "visible-child",

M harvest-glib/harvest-api-client.c => harvest-glib/harvest-api-client.c +11 -12
@@ 11,7 11,6 @@

#include "harvest-api-client.h"
#include "harvest-common.h"
#include "harvest-error.h"
#include "harvest-http.h"
#include "harvest-request.h"
#include "harvest-response-metadata.h"


@@ 26,11 25,9 @@ struct _HarvestApiClient
	GObject parent_instance;

	SoupSession *session;
	const char *server;
	char *server;
	char *access_token;
	char *account_id;
	char *application_name;
	char *contact_info;
};

G_DEFINE_TYPE(HarvestApiClient, harvest_api_client, G_TYPE_OBJECT)


@@ 83,7 80,8 @@ harvest_api_client_set_property(GObject *obj, guint prop_id, const GValue *val, 
		self->session = g_value_dup_object(val);
		break;
	case PROP_SERVER:
		self->server = g_value_get_string(val);
		g_free(self->server);
		self->server = g_value_dup_string(val);
		break;
	case PROP_ACCESS_TOKEN:
		g_free(self->access_token);


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

	g_free(self->server);
	g_free(self->access_token);
	g_free(self->account_id);
	g_free(self->application_name);
	g_free(self->contact_info);

	G_OBJECT_CLASS(harvest_api_client_parent_class)->finalize(obj);
}


@@ 125,7 122,7 @@ harvest_api_client_class_init(HarvestApiClientClass *klass)
		_("SoupSession object to use to make requests."), SOUP_TYPE_SESSION,
		G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_SERVER]
		= g_param_spec_string("server", _("Server"), _("Location of the server"), NULL,
		= g_param_spec_string("server", _("Server"), _("Base URL for the server."), NULL,
			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_ACCESS_TOKEN] = g_param_spec_string("access-token", _("Access Token"),
		_("Developer access token for the Harvest API."), NULL,


@@ 147,8 144,10 @@ harvest_api_client_new(SoupSession *session, const char *access_token, const cha
	g_return_val_if_fail(
		SOUP_IS_SESSION(session) && access_token != NULL && account_id != NULL, NULL);

	if (instance != NULL) {
	if (instance != NULL)
		g_object_unref(instance);

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


@@ 200,9 199,9 @@ harvest_api_client_execute_request_async(HarvestApiClient *self, HarvestRequest 
{
	g_return_if_fail(HARVEST_IS_API_CLIENT(self) && HARVEST_IS_REQUEST(req));

	g_autoptr(SoupMessage) msg = NULL;
	g_autoptr(GString) uri	   = g_string_new(NULL);
	g_string_append_printf(uri, "%s%s", self->server, harvest_request_get_endpoint(req));
	SoupMessage *msg	   = NULL;
	g_autoptr(GString) uri = g_string_new(self->server);
	g_string_append(uri, harvest_request_get_endpoint(req));
	gboolean response_has_body = harvest_request_get_data(req) != NULL;
	char *body				   = NULL;
	gsize len				   = 0;

D harvest-glib/harvest-error.h => harvest-glib/harvest-error.h +0 -19
@@ 1,19 0,0 @@
#pragma once

#if !defined(__HARVEST_HEADER_INTERNAL__) && !defined(__HARVEST_COMPILATION__)
#	error "Only <harvest-glib/harvest.h> can be included directly."
#endif

#include <glib-object.h>

G_BEGIN_DECLS

typedef enum HarvestErrorCode
{
	ERROR_MEM,
	ERROR_JSON,
} HarvestCpErrorCode;

G_DEFINE_QUARK("libharvest-glib", harvest_error)

G_END_DECLS

M harvest-glib/harvest-request.h => harvest-glib/harvest-request.h +2 -2
@@ 20,8 20,8 @@ struct _HarvestRequestClass
	GObjectClass parent_class;
};

typedef void (*HarvestQueuedCallback)(HarvestRequest *request, gpointer user_data);
typedef void (*HarvestCompletedCallback)(
typedef void(HarvestQueuedCallback)(HarvestRequest *request, gpointer user_data);
typedef void(HarvestCompletedCallback)(
	HarvestRequest *request, HarvestResponse *response, gpointer user_data);

HttpMethod harvest_request_get_http_method(HarvestRequest *self) G_GNUC_CONST;

M harvest-glib/harvest-response-metadata.c => harvest-glib/harvest-response-metadata.c +2 -2
@@ 89,8 89,8 @@ harvest_response_metadata_init(G_GNUC_UNUSED HarvestResponseMetadata *self)
HarvestResponseMetadata *
harvest_response_metadata_new(const GType body_type, const HttpStatusCode expected_status)
{
	return g_object_new(
		HARVEST_TYPE_RESPONSE_METADATA, "body-type", body_type, "expected-status", expected_status);
	return g_object_new(HARVEST_TYPE_RESPONSE_METADATA, "body-type", body_type, "expected-status",
		expected_status, NULL);
}

HttpStatusCode

M harvest-glib/harvest-time-entry.h => harvest-glib/harvest-time-entry.h +0 -1
@@ 5,7 5,6 @@
#endif

#include <glib-object.h>
#include <glib/gi18n-lib.h>

G_BEGIN_DECLS


M harvest-glib/harvest-user.c => harvest-glib/harvest-user.c +33 -35
@@ 78,7 78,7 @@ static gboolean
harvest_user_json_deserialize_property(JsonSerializable *serializable, const gchar *prop_name,
	GValue *val, GParamSpec *pspec, JsonNode *prop_node)
{
	if (g_strcmp0(prop_name, "created_at") == 0 || g_strcmp0(prop_name, "updated_at") == 0) {
	if (g_strcmp0(prop_name, "created-at") == 0 || g_strcmp0(prop_name, "updated-at") == 0) {
		const GDateTime *dt = g_date_time_new_from_iso8601(json_node_get_string(prop_node), NULL);
		g_value_set_boxed(val, dt);



@@ 315,71 315,69 @@ harvest_user_class_init(HarvestUserClass *klass)
	obj_class->get_property = harvest_user_get_property;
	obj_class->set_property = harvest_user_set_property;

	obj_properties[PROP_ID]
		= g_param_spec_int("id", _("ID"), _("User ID"), 0, INT_MAX, 0, G_PARAM_READWRITE);
	obj_properties[PROP_FIRST_NAME]
		= g_param_spec_string("first-name", _("First Name"), _("The first name of the user."), NULL,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_LAST_NAME]
		= g_param_spec_string("last-name", _("Last Name"), _("The last name of the user."), NULL,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_EMAIL]
		= g_param_spec_string("email", _("Email"), _("The email address of the user."), NULL,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_TELEPHONE]
		= g_param_spec_string("telephone", _("Telephone"), _("The telephone number for the user."),
			NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_TIMEZONE]
		= g_param_spec_string("timezone", _("Timezone"), _("The user’s timezone."), NULL,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_ID] = g_param_spec_int(
		"id", _("ID"), _("User ID"), 0, INT_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_FIRST_NAME] = g_param_spec_string("first-name", _("First Name"),
		_("The first name of the user."), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_LAST_NAME]	= g_param_spec_string("last-name", _("Last Name"),
		 _("The last name of the user."), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_EMAIL]		= g_param_spec_string("email", _("Email"),
		 _("The email address of the user."), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_TELEPHONE]	= g_param_spec_string("telephone", _("Telephone"),
		 _("The telephone number for the user."), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_TIMEZONE]	= g_param_spec_string("timezone", _("Timezone"),
		  _("The user’s timezone."), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_HAS_ACCESS_TO_ALL_FUTURE_PROJECTS] = g_param_spec_boolean(
		"has-access-to-all-future-projects", _("Has Access to All Future Projects"),
		_(" 	Whether the user should be automatically added to future projects."), FALSE,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_IS_CONTRACTOR] = g_param_spec_boolean("is-contractor", _("Is Contractor"),
		_("Whether the user is a contractor or an employee."), FALSE,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_IS_ADMIN]	   = g_param_spec_boolean("is-admin", _("Is Admin"),
		 _("Whether the user has admin permissions."), FALSE,
		 G_PARAM_READABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		 _("Whether the user has admin permissions."), FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_IS_PROJECT_MANAGER] = g_param_spec_boolean("is-project-manager",
		_("Is Project Manager"), _("Whether the user has project manager permissions."), FALSE,
		G_PARAM_READABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_CAN_SEE_RATES] = g_param_spec_boolean("can-see-rates", _("Can See Rates"),
		_("Whether the user can see billable rates on projects. Only applicable to project "
		  "managers."),
		FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_CAN_CREATE_PROJECTS]
		= g_param_spec_boolean("can-create-projects", _("Can Create Projects"),
			_("Whether the user can create projects. Only applicable to project managers."), FALSE,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_CAN_CREATE_INVOICES]
		= g_param_spec_boolean("can-create-invoices", _("Can Create Invoices"),
			_("Whether the user can create invoices. Only applicable to project managers."), FALSE,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
	obj_properties[PROP_IS_ACTIVE] = g_param_spec_boolean("is-active", _("Is Active"),
		_("Whether the user is active or archived."), FALSE,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_IS_ACTIVE] = g_param_spec_boolean("is_active", _("Is Active"),
		_("Whether the user is active or archived."), FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_WEEKLY_CAPACITY]
		= g_param_spec_double("weekly-capacity", _("Weekly Capacity"),
			_("The number of hours per week this person is available to work in seconds, in half "
			  "hour increments. For example, if a person’s capacity is 35 hours, the API will "
			  "return 126000 seconds."),
			0, DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
			0, DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_DEFAULT_HOURLY_RATE]
		= g_param_spec_double("default-hourly-rate", _("Default Hourly Rate"),
			_("The billable rate to use for this user when they are added to a project."), 0,
			DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
			DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_COST_RATE] = g_param_spec_double("cost-rate", _("Cost Rate"),
		_("The cost rate to use for this user when calculating a project’s "
		  "costs vs billable amount."),
		0, DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		0, DBL_MAX, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_ROLES]
		= g_param_spec_boxed("roles", _("Roles"), _("The role names assigned to this person."),
			G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
			G_TYPE_PTR_ARRAY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_AVATAR_URL] = g_param_spec_string("avatar-url", _("Avatar URL"),
		_("The URL to the user’s avatar image."), NULL,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
		_("The URL to the user’s avatar image."), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_CREATED_AT] = g_param_spec_boxed("created-at", _("Created At"),
		_("Date and time the user was created."), G_TYPE_DATE_TIME,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	obj_properties[PROP_UPDATED_AT] = g_param_spec_boxed("updated-at", _("Updated At"),
		_("Date and time the user was last updated."), G_TYPE_DATE_TIME,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT);

	g_object_class_install_properties(obj_class, N_PROPS, obj_properties);
}

M harvest-glib/harvest.h => harvest-glib/harvest.h +0 -1
@@ 6,7 6,6 @@
#include "harvest-client.h"
#include "harvest-common.h"
#include "harvest-creator.h"
#include "harvest-error.h"
#include "harvest-estimate-item-category.h"
#include "harvest-estimate-line-item.h"
#include "harvest-estimate.h"

M harvest-glib/meson.build => harvest-glib/meson.build +2 -3
@@ 43,7 43,6 @@ harvest_glib_public_headers = [
    'harvest-client.h',
    'harvest-common.h',
    'harvest-creator.h',
    'harvest-error.h',
    'harvest-estimate.h',
    'harvest-estimate-item-category.h',
    'harvest-estimate-line-item.h',


@@ 101,14 100,14 @@ harvest_glib = library(
)

harvest_glib_dep = declare_dependency(
    include_directories: include_directories('.'),
    include_directories: include_directories('..'),
    link_with: harvest_glib,
    version: harvest_glib_version
)

pkg.generate(
	harvest_glib,
	description: 'GLib-based library for interacting with the Harvest API',
	description: 'GLib-based library for interacting with the Harvest API.',
	filebase: 'harvest-glib-1.0.0',
	name: 'harvest-glib',
	version: harvest_glib_version,