Gadu-Gadu: setting public profile information. Fixes #6918 soc.2012.gg
authorTomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
Mon, 20 Aug 2012 20:28:27 +0200
branchsoc.2012.gg
changesetcc3f8295618a pushlog
parent 3b4cbb0e10d6
child 2394cd23ce8f
Gadu-Gadu: setting public profile information. Fixes #6918
libpurple/protocols/gg/gg.c
libpurple/protocols/gg/pubdir-prpl.c
libpurple/protocols/gg/pubdir-prpl.h
libpurple/protocols/gg/utils.c
libpurple/protocols/gg/utils.h
     1.1 --- a/libpurple/protocols/gg/gg.c
     1.2 +++ b/libpurple/protocols/gg/gg.c
     1.3 @@ -1301,6 +1301,11 @@
     1.4  	ggp_pubdir_search((PurpleConnection *)action->context, NULL);
     1.5  }
     1.6  
     1.7 +static void ggp_action_set_info(PurplePluginAction *action)
     1.8 +{
     1.9 +	ggp_pubdir_set_info((PurpleConnection *)action->context);
    1.10 +}
    1.11 +
    1.12  static GList *ggp_actions(PurplePlugin *plugin, gpointer context)
    1.13  {
    1.14  	GList *m = NULL;
    1.15 @@ -1310,12 +1315,18 @@
    1.16  		ggp_action_chpass);
    1.17  	m = g_list_append(m, act);
    1.18  
    1.19 +	act = purple_plugin_action_new(_("Show status only for buddies"),
    1.20 +		ggp_action_status_broadcasting);
    1.21 +	m = g_list_append(m, act);
    1.22 +
    1.23 +	m = g_list_append(m, NULL);
    1.24 +
    1.25  	act = purple_plugin_action_new(_("Find buddies..."),
    1.26  		ggp_action_search);
    1.27  	m = g_list_append(m, act);
    1.28  
    1.29 -	act = purple_plugin_action_new(_("Show status only for buddies"),
    1.30 -		ggp_action_status_broadcasting);
    1.31 +	act = purple_plugin_action_new(_("Set User Info"),
    1.32 +		ggp_action_set_info);
    1.33  	m = g_list_append(m, act);
    1.34  
    1.35  	m = g_list_append(m, NULL);
     2.1 --- a/libpurple/protocols/gg/pubdir-prpl.c
     2.2 +++ b/libpurple/protocols/gg/pubdir-prpl.c
     2.3 @@ -87,6 +87,42 @@
     2.4  static void ggp_pubdir_search_results_new(PurpleConnection *gc, GList *row,
     2.5  	gpointer _form);
     2.6  
     2.7 +// Own profile.
     2.8 +
     2.9 +static void ggp_pubdir_set_info_dialog(PurpleConnection *gc, int records_count,
    2.10 +	const ggp_pubdir_record *records, int next_offset, void *user_data);
    2.11 +static void ggp_pubdir_set_info_request(PurpleConnection *gc,
    2.12 +	PurpleRequestFields *fields);
    2.13 +static void ggp_pubdir_set_info_got_token(PurpleConnection *gc,
    2.14 +	const gchar *token, gpointer _record);
    2.15 +static void ggp_pubdir_set_info_got_response(PurpleUtilFetchUrlData *url_data,
    2.16 +	gpointer user_data, const gchar *url_text, gsize len,
    2.17 +	const gchar *error_message);
    2.18 +
    2.19 +/******************************************************************************/
    2.20 +
    2.21 +static const gchar *ggp_pubdir_provinces[] =
    2.22 +{
    2.23 +	"dolnośląskie",
    2.24 +	"kujawsko-pomorskie",
    2.25 +	"lubelskie",
    2.26 +	"lubuskie",
    2.27 +	"łódzkie",
    2.28 +	"małopolskie",
    2.29 +	"mazowieckie",
    2.30 +	"opolskie",
    2.31 +	"podkarpackie",
    2.32 +	"podlaskie",
    2.33 +	"pomorskie",
    2.34 +	"śląskie",
    2.35 +	"świętokrzyskie",
    2.36 +	"warmińsko-mazurskie",
    2.37 +	"wielkopolskie",
    2.38 +	"zachodniopomorskie",
    2.39 +};
    2.40 +
    2.41 +static int ggp_pubdir_provinces_count = sizeof(ggp_pubdir_provinces)/sizeof(gchar*);
    2.42 +
    2.43  /******************************************************************************/
    2.44  
    2.45  void ggp_pubdir_record_free(ggp_pubdir_record *records, int count)
    2.46 @@ -95,6 +131,9 @@
    2.47  	for (i = 0; i < count; i++)
    2.48  	{
    2.49  		g_free(records[i].label);
    2.50 +		g_free(records[i].nickname);
    2.51 +		g_free(records[i].first_name);
    2.52 +		g_free(records[i].last_name);
    2.53  		g_free(records[i].city);
    2.54  	}
    2.55  	g_free(records);
    2.56 @@ -203,10 +242,10 @@
    2.57  	while (xml)
    2.58  	{
    2.59  		ggp_pubdir_record *record = &records[i++];
    2.60 -		gchar *label = NULL, *nick = NULL, *name = NULL,
    2.61 -			*surname = NULL, *city = NULL, *birth_s = NULL;
    2.62 +		gchar *city = NULL, *birth_s = NULL;
    2.63  		unsigned int gender = 0;
    2.64 -		GTimeVal birth_g;
    2.65 +		const gchar *uin_s;
    2.66 +		
    2.67  		g_assert(i <= record_count);
    2.68  		
    2.69  		record->uin = ggp_str_to_uin(xmlnode_get_attrib(xml, "uin"));
    2.70 @@ -215,35 +254,39 @@
    2.71  		if (record->uin == 0)
    2.72  			purple_debug_error("gg", "ggp_pubdir_got_data:"
    2.73  				" invalid uin\n");
    2.74 +		uin_s = ggp_uin_to_str(record->uin);
    2.75  		
    2.76 -		ggp_xml_get_string(xml, "label", &label);
    2.77 -		ggp_xml_get_string(xml, "nick", &nick);
    2.78 -		ggp_xml_get_string(xml, "name", &name);
    2.79 -		ggp_xml_get_string(xml, "surname", &surname);
    2.80 +		ggp_xml_get_string(xml, "label", &record->label);
    2.81 +		ggp_xml_get_string(xml, "nick", &record->nickname);
    2.82 +		ggp_xml_get_string(xml, "name", &record->first_name);
    2.83 +		ggp_xml_get_string(xml, "surname", &record->last_name);
    2.84  		ggp_xml_get_string(xml, "city", &city);
    2.85  		ggp_xml_get_string(xml, "birth", &birth_s);
    2.86  		ggp_xml_get_uint(xml, "gender", &gender);
    2.87  		ggp_xml_get_uint(xml, "age", &record->age);
    2.88 +		ggp_xml_get_uint(xml, "province", &record->province);
    2.89  		
    2.90 -		if (label)
    2.91 -			record->label = g_strdup(label);
    2.92 -		else if (nick)
    2.93 -			record->label = g_strdup(nick);
    2.94 -		else if (name && surname)
    2.95 -			record->label = g_strdup_printf("%s %s", name, surname);
    2.96 -		else if (name)
    2.97 -			record->label = g_strdup(name);
    2.98 -		else if (surname)
    2.99 -			record->label = g_strdup(surname);
   2.100 +		record->label = ggp_free_if_equal(record->label, uin_s);
   2.101 +		record->label = ggp_free_if_equal(record->label, "");
   2.102 +		record->nickname = ggp_free_if_equal(record->nickname, uin_s);
   2.103 +		record->nickname = ggp_free_if_equal(record->nickname, "");
   2.104 +		record->first_name = ggp_free_if_equal(record->first_name, "");
   2.105 +		record->last_name = ggp_free_if_equal(record->last_name, "");
   2.106 +		
   2.107 +		if (record->label) {}
   2.108 +		else if (record->nickname)
   2.109 +			record->label = g_strdup(record->nickname);
   2.110 +		else if (record->first_name && record->last_name)
   2.111 +			record->label = g_strdup_printf("%s %s",
   2.112 +				record->first_name, record->last_name);
   2.113 +		else if (record->first_name)
   2.114 +			record->label = g_strdup(record->first_name);
   2.115 +		else if (record->last_name)
   2.116 +			record->label = g_strdup(record->last_name);
   2.117  		if (record->label)
   2.118  			g_strstrip(record->label);
   2.119 -		
   2.120 -		if (g_strcmp0(record->label, ggp_uin_to_str(record->uin)) == 0 ||
   2.121 -			g_strcmp0(record->label, "") == 0)
   2.122 -		{
   2.123 -			g_free(record->label);
   2.124 -			record->label = NULL;
   2.125 -		}
   2.126 +		if (record->nickname)
   2.127 +			g_strstrip(record->nickname);
   2.128  		
   2.129  		if (gender == 1)
   2.130  			record->gender = GGP_PUBDIR_GENDER_FEMALE;
   2.131 @@ -262,18 +305,17 @@
   2.132  			record->city = NULL;
   2.133  		}
   2.134  		
   2.135 -		if (birth_s && g_time_val_from_iso8601(birth_s, &birth_g))
   2.136 -			record->birth = birth_g.tv_sec;
   2.137 +		record->birth = ggp_date_from_iso8601(birth_s);
   2.138  		//TODO: calculate age from birth
   2.139  		
   2.140  		//TODO: verbose
   2.141 -		//purple_debug_misc("gg", "ggp_pubdir_got_data: [%d][%s][%s][%d][%d][%lu]\n",
   2.142 -		//	record->uin, record->label, record->city, record->gender, record->age, record->birth);
   2.143 +		purple_debug_misc("gg", "ggp_pubdir_got_data: [uin:%d] "
   2.144 +			"[label:%s] [nick:%s] [first name:%s] [last name:%s] "
   2.145 +			"[city:%s] [gender:%d] [age:%d] [birth:%lu]\n",
   2.146 +			record->uin, record->label, record->nickname,
   2.147 +			record->first_name, record->last_name, record->city,
   2.148 +			record->gender, record->age, record->birth);
   2.149  		
   2.150 -		g_free(label);
   2.151 -		g_free(nick);
   2.152 -		g_free(name);
   2.153 -		g_free(surname);
   2.154  		g_free(city);
   2.155  		
   2.156  		xml = xmlnode_get_next_twin(xml);
   2.157 @@ -342,9 +384,15 @@
   2.158  				_("Message"), status_message);
   2.159  	}
   2.160  	
   2.161 -	if (record->label)
   2.162 -		purple_notify_user_info_add_pair_plaintext(info, _("Name"),
   2.163 -			record->label);
   2.164 +	if (record->nickname)
   2.165 +		purple_notify_user_info_add_pair_plaintext(info,
   2.166 +			_("Nickname"), record->nickname);
   2.167 +	if (record->first_name)
   2.168 +		purple_notify_user_info_add_pair_plaintext(info,
   2.169 +			_("First name"), record->first_name);
   2.170 +	if (record->last_name)
   2.171 +		purple_notify_user_info_add_pair_plaintext(info,
   2.172 +			_("Last name"), record->last_name);
   2.173  	if (record->gender != GGP_PUBDIR_GENDER_UNSPECIFIED)
   2.174  		purple_notify_user_info_add_pair_plaintext(info, _("Gender"),
   2.175  			record->gender == GGP_PUBDIR_GENDER_FEMALE ?
   2.176 @@ -353,14 +401,8 @@
   2.177  		purple_notify_user_info_add_pair_plaintext(info, _("City"),
   2.178  			record->city);
   2.179  	if (record->birth)
   2.180 -	{
   2.181 -		GDate date;
   2.182 -		gchar buff[15];
   2.183 -		g_date_set_time(&date, record->birth);
   2.184 -		g_date_strftime(buff, sizeof(buff), "%Y-%m-%d", &date);
   2.185  		purple_notify_user_info_add_pair_plaintext(info, _("Birthday"),
   2.186 -			buff);
   2.187 -	}
   2.188 +			ggp_date_strftime("%Y-%m-%d", record->birth));
   2.189  	else if (record->age)
   2.190  	{
   2.191  		gchar *age_s = g_strdup_printf("%d", record->age);
   2.192 @@ -732,3 +774,191 @@
   2.193  	ggp_pubdir_search_form *form = _form;
   2.194  	ggp_pubdir_search(gc, form);
   2.195  }
   2.196 +
   2.197 +/*******************************************************************************
   2.198 + * Own profile.
   2.199 + ******************************************************************************/
   2.200 +
   2.201 +void ggp_pubdir_set_info(PurpleConnection *gc)
   2.202 +{
   2.203 +	ggp_pubdir_get_info(gc, ggp_str_to_uin(purple_account_get_username(
   2.204 +		purple_connection_get_account(gc))),
   2.205 +		ggp_pubdir_set_info_dialog, NULL);
   2.206 +}
   2.207 +
   2.208 +static void ggp_pubdir_set_info_dialog(PurpleConnection *gc, int records_count,
   2.209 +	const ggp_pubdir_record *records, int next_offset, void *user_data)
   2.210 +{
   2.211 +	PurpleRequestFields *fields;
   2.212 +	PurpleRequestFieldGroup *group;
   2.213 +	PurpleRequestField *field;
   2.214 +	int default_gender, i;
   2.215 +	const ggp_pubdir_record *record;
   2.216 +	
   2.217 +	purple_debug_info("gg", "ggp_pubdir_set_info_dialog (record: %d)\n",
   2.218 +		records_count);
   2.219 +	
   2.220 +	record = (records_count == 1 ? &records[0] : NULL);
   2.221 +	
   2.222 +	fields = purple_request_fields_new();
   2.223 +	group = purple_request_field_group_new(NULL);
   2.224 +	purple_request_fields_add_group(fields, group);
   2.225 +	
   2.226 +	field = purple_request_field_string_new("first_name", _("First name"),
   2.227 +		record ? record->first_name : NULL, FALSE);
   2.228 +	purple_request_field_group_add_field(group, field);
   2.229 +
   2.230 +	field = purple_request_field_string_new("last_name", _("Last name"),
   2.231 +		record ? record->last_name : NULL, FALSE);
   2.232 +	purple_request_field_group_add_field(group, field);
   2.233 +	
   2.234 +	default_gender = -1;
   2.235 +	if (record && record->gender == GGP_PUBDIR_GENDER_MALE)
   2.236 +		default_gender = 0;
   2.237 +	else if (record && record->gender == GGP_PUBDIR_GENDER_FEMALE)
   2.238 +		default_gender = 1;
   2.239 +	
   2.240 +	field = purple_request_field_choice_new("gender", _("Gender"),
   2.241 +		default_gender);
   2.242 +	purple_request_field_set_required(field, TRUE);
   2.243 +	purple_request_field_choice_add(field, _("Male"));
   2.244 +	purple_request_field_choice_add(field, _("Female"));
   2.245 +	purple_request_field_group_add_field(group, field);
   2.246 +
   2.247 +	field = purple_request_field_string_new("birth_date", _("Birth Day"),
   2.248 +		(record && record->birth) ?
   2.249 +		ggp_date_strftime("%Y-%m-%d", record->birth) : NULL, FALSE);
   2.250 +	purple_request_field_set_required(field, TRUE);
   2.251 +	purple_request_field_group_add_field(group, field);
   2.252 +
   2.253 +	field = purple_request_field_string_new("city", _("City"),
   2.254 +		record ? record->city : NULL, FALSE);
   2.255 +	purple_request_field_group_add_field(group, field);
   2.256 +	
   2.257 +	field = purple_request_field_choice_new("province", _("Voivodeship"), 0);
   2.258 +	purple_request_field_group_add_field(group, field);
   2.259 +	purple_request_field_choice_add(field, _("Not specified"));
   2.260 +	for (i = 0; i < ggp_pubdir_provinces_count; i++)
   2.261 +	{
   2.262 +		purple_request_field_choice_add(field, ggp_pubdir_provinces[i]);
   2.263 +		if (record && i + 1 == record->province)
   2.264 +		{
   2.265 +			purple_request_field_choice_set_value(field, i + 1);
   2.266 +			purple_request_field_choice_set_default_value(field,
   2.267 +				i + 1); // TODO: libpurple bug?
   2.268 +		}
   2.269 +	}
   2.270 +	
   2.271 +	purple_request_fields(gc, _("Set User Info"), _("Set User Info"),
   2.272 +		NULL, fields,
   2.273 +		_("OK"), G_CALLBACK(ggp_pubdir_set_info_request),
   2.274 +		_("Cancel"), NULL,
   2.275 +		purple_connection_get_account(gc), NULL, NULL, gc);
   2.276 +	
   2.277 +}
   2.278 +
   2.279 +static void ggp_pubdir_set_info_request(PurpleConnection *gc,
   2.280 +	PurpleRequestFields *fields)
   2.281 +{
   2.282 +	gchar *url;
   2.283 +	uin_t uin = ggp_str_to_uin(purple_account_get_username(
   2.284 +		purple_connection_get_account(gc)));
   2.285 +	ggp_pubdir_record *record = g_new0(ggp_pubdir_record, 1);
   2.286 +	gchar *birth_s;
   2.287 +	
   2.288 +	purple_debug_info("gg", "ggp_pubdir_set_info_request\n");
   2.289 +
   2.290 +	record->uin = uin;
   2.291 +	record->first_name = g_strdup(purple_request_fields_get_string(fields,
   2.292 +		"first_name"));
   2.293 +	record->last_name = g_strdup(purple_request_fields_get_string(fields,
   2.294 +		"last_name"));
   2.295 +	if (purple_request_fields_get_choice(fields, "gender") == 0)
   2.296 +		record->gender = GGP_PUBDIR_GENDER_MALE;
   2.297 +	else
   2.298 +		record->gender = GGP_PUBDIR_GENDER_FEMALE;
   2.299 +	record->city = g_strdup(purple_request_fields_get_string(fields,
   2.300 +		"city"));
   2.301 +	record->province = purple_request_fields_get_choice(fields, "province");
   2.302 +	
   2.303 +	birth_s = g_strdup_printf("%sT10:00:00+00:00",
   2.304 +		purple_request_fields_get_string(fields, "birth_date"));
   2.305 +	record->birth = ggp_date_from_iso8601(birth_s);
   2.306 +	g_free(birth_s);
   2.307 +	purple_debug_info("gg", "ggp_pubdir_set_info_request: birth [%lu][%s]\n",
   2.308 +		record->birth, purple_request_fields_get_string(
   2.309 +		fields, "birth_date"));
   2.310 +
   2.311 +	url = g_strdup_printf("http://api.gadu-gadu.pl/users/%u.xml", uin);
   2.312 +	ggp_oauth_request(gc, ggp_pubdir_set_info_got_token, record,
   2.313 +		"PUT", url);
   2.314 +	g_free(url);
   2.315 +}
   2.316 +
   2.317 +static void ggp_pubdir_set_info_got_token(PurpleConnection *gc,
   2.318 +	const gchar *token, gpointer _record)
   2.319 +{
   2.320 +	ggp_pubdir_record *record = _record;
   2.321 +	gchar *request, *request_data, *url;
   2.322 +	gchar *name, *surname, *city;
   2.323 +	uin_t uin = record->uin;
   2.324 +
   2.325 +	if (!token || !PURPLE_CONNECTION_IS_VALID(gc))
   2.326 +	{
   2.327 +		// TODO: notify about failure
   2.328 +		ggp_pubdir_record_free(record, 1);
   2.329 +		return;
   2.330 +	}
   2.331 +	
   2.332 +	name = g_uri_escape_string(record->first_name, NULL, FALSE);
   2.333 +	surname = g_uri_escape_string(record->last_name, NULL, FALSE);
   2.334 +	city = g_uri_escape_string(record->city, NULL, FALSE);
   2.335 +	
   2.336 +	request_data = g_strdup_printf(
   2.337 +		"name=%s&"
   2.338 +		"surname=%s&"
   2.339 +		"birth=%sT10:00:00%%2B00:00&"
   2.340 +		"birth_priv=2&"
   2.341 +		"gender=%d&"
   2.342 +		"gender_priv=2&"
   2.343 +		"city=%s&"
   2.344 +		"province=%d",
   2.345 +		name, surname,
   2.346 +		ggp_date_strftime("%Y-%m-%d", record->birth),
   2.347 +		record->gender,
   2.348 +		city,
   2.349 +		record->province);
   2.350 +	
   2.351 +	//TODO: verbose
   2.352 +	//purple_debug_misc("gg", "ggp_pubdir_set_info_got_token: query [%s]\n",
   2.353 +	//	request_data);
   2.354 +
   2.355 +	url = g_strdup_printf("http://api.gadu-gadu.pl/users/%u.xml", uin);
   2.356 +	request = g_strdup_printf(
   2.357 +		"PUT /users/%u.xml HTTP/1.1\r\n"
   2.358 +		"Host: api.gadu-gadu.pl\r\n"
   2.359 +		"%s\r\n"
   2.360 +		"Content-Length: %d\r\n"
   2.361 +		"Content-Type: application/x-www-form-urlencoded\r\n"
   2.362 +		"\r\n%s",
   2.363 +		uin, token, strlen(request_data), request_data);
   2.364 +
   2.365 +	purple_util_fetch_url_request(purple_connection_get_account(gc), url,
   2.366 +		FALSE, NULL, TRUE, request, FALSE, -1,
   2.367 +		ggp_pubdir_set_info_got_response, NULL);
   2.368 +
   2.369 +	g_free(request);
   2.370 +	g_free(request_data);
   2.371 +	g_free(url);
   2.372 +	ggp_pubdir_record_free(record, 1);
   2.373 +}
   2.374 +
   2.375 +static void ggp_pubdir_set_info_got_response(PurpleUtilFetchUrlData *url_data,
   2.376 +	gpointer user_data, const gchar *url_text, gsize len,
   2.377 +	const gchar *error_message)
   2.378 +{
   2.379 +	purple_debug_info("gg", "ggp_pubdir_set_info_got_response: [%s]\n", url_text);
   2.380 +	//<result><status>0</status></result>
   2.381 +	
   2.382 +	//TODO: notify about failure
   2.383 +}
     3.1 --- a/libpurple/protocols/gg/pubdir-prpl.h
     3.2 +++ b/libpurple/protocols/gg/pubdir-prpl.h
     3.3 @@ -15,8 +15,12 @@
     3.4  {
     3.5  	uin_t uin;
     3.6  	gchar *label;
     3.7 +	gchar *nickname;
     3.8 +	gchar *first_name;
     3.9 +	gchar *last_name;
    3.10  	ggp_pubdir_gender gender;
    3.11  	gchar *city;
    3.12 +	unsigned int province;
    3.13  	time_t birth;
    3.14  	unsigned int age;
    3.15  } ggp_pubdir_record;
    3.16 @@ -35,4 +39,6 @@
    3.17  void ggp_pubdir_search(PurpleConnection *gc,
    3.18  	const ggp_pubdir_search_form *form);
    3.19  
    3.20 +void ggp_pubdir_set_info(PurpleConnection *gc);
    3.21 +
    3.22  #endif /* _GGP_PUBDIR_PRPL_H */
     4.1 --- a/libpurple/protocols/gg/utils.c
     4.2 +++ b/libpurple/protocols/gg/utils.c
     4.3 @@ -209,3 +209,35 @@
     4.4  	}
     4.5  	return list;
     4.6  }
     4.7 +
     4.8 +gchar * ggp_free_if_equal(gchar *str, const gchar *pattern)
     4.9 +{
    4.10 +	if (g_strcmp0(str, pattern) == 0)
    4.11 +	{
    4.12 +		g_free(str);
    4.13 +		return NULL;
    4.14 +	}
    4.15 +	return str;
    4.16 +}
    4.17 +
    4.18 +const gchar * ggp_date_strftime(const gchar *format, time_t date)
    4.19 +{
    4.20 +	GDate g_date;
    4.21 +	static gchar buff[30];
    4.22 +	
    4.23 +	g_date_set_time(&g_date, date);
    4.24 +	if (0 == g_date_strftime(buff, sizeof(buff), format, &g_date))
    4.25 +		return NULL;
    4.26 +	return buff;
    4.27 +}
    4.28 +
    4.29 +time_t ggp_date_from_iso8601(const gchar *str)
    4.30 +{
    4.31 +	GTimeVal g_timeval;
    4.32 +	
    4.33 +	if (!str)
    4.34 +		return 0;
    4.35 +	if (!g_time_val_from_iso8601(str, &g_timeval))
    4.36 +		return 0;
    4.37 +	return g_timeval.tv_sec;
    4.38 +}
     5.1 --- a/libpurple/protocols/gg/utils.h
     5.2 +++ b/libpurple/protocols/gg/utils.h
     5.3 @@ -82,4 +82,10 @@
     5.4  
     5.5  GList * ggp_list_truncate(GList *list, gint length, GDestroyNotify free_func);
     5.6  
     5.7 +gchar * ggp_free_if_equal(gchar *str, const gchar *pattern);
     5.8 +
     5.9 +const gchar * ggp_date_strftime(const gchar *format, time_t date);
    5.10 +
    5.11 +time_t ggp_date_from_iso8601(const gchar *str);
    5.12 +
    5.13  #endif /* _GGP_UTILS_H */