pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
closing merged branch
iterate-on-receiving-data-in-jabber-callback
2019-11-11, Gary Kramlich
5b3f596d1947
closing merged branch
/*
* Contact Availability Prediction plugin for Purple
*
* Copyright (C) 2006 Geoffrey Foster.
*
* This program 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 2 of the
* License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02111-1301, USA.
*/
#include
"cap.h"
static
void
generate_prediction
(
CapStatistics
*
statistics
)
{
if
(
statistics
->
buddy
)
{
if
(
statistics
->
prediction
==
NULL
)
statistics
->
prediction
=
g_new0
(
CapPrediction
,
1
);
statistics
->
prediction
->
probability
=
generate_prediction_for
(
statistics
->
buddy
);
statistics
->
prediction
->
generated_at
=
time
(
NULL
);
}
}
static
double
generate_prediction_for
(
PurpleBuddy
*
buddy
)
{
double
prediction
=
1.0f
;
gboolean
generated
=
FALSE
;
PurpleAccount
*
account
=
purple_buddy_get_account
(
buddy
);
const
gchar
*
buddy_name
=
purple_buddy_get_name
(
buddy
);
const
gchar
*
protocol_id
=
purple_account_get_protocol_id
(
account
);
const
gchar
*
account_id
=
purple_account_get_username
(
account
);
const
gchar
*
status_id
=
purple_status_get_id
(
get_status_for
(
buddy
));
time_t
t
=
time
(
NULL
);
struct
tm
*
current_time
=
localtime
(
&
t
);
int
current_minute
=
current_time
->
tm_min
+
current_time
->
tm_hour
*
60
;
int
threshold
=
purple_prefs_get_int
(
"/plugins/gtk/cap/threshold"
);
int
min_minute
=
(
current_minute
-
threshold
)
%
1440
;
int
max_minute
=
(
current_minute
+
threshold
)
%
1440
;
gchar
*
sql
;
sqlite3_stmt
*
stmt
=
NULL
;
const
char
*
tail
=
NULL
;
int
rc
;
sql
=
sqlite3_mprintf
(
"select sum(success_count) as successes, sum(failed_count) as failures "
"from cap_msg_count where "
"buddy=%Q and account=%Q and protocol=%Q and minute_val>=%d and minute_val<=%d;"
,
buddy_name
,
account_id
,
protocol_id
,
min_minute
,
max_minute
);
rc
=
sqlite3_prepare
(
_db
,
sql
,
-1
,
&
stmt
,
&
tail
);
if
(
rc
==
SQLITE_OK
)
{
int
successes
=
0
;
int
failures
=
0
;
if
(
stmt
!=
NULL
)
{
if
(
sqlite3_step
(
stmt
)
==
SQLITE_ROW
)
{
successes
=
sqlite3_column_int
(
stmt
,
0
);
failures
=
sqlite3_column_int
(
stmt
,
1
);
if
(
failures
+
successes
>
0
)
{
prediction
*=
((
double
)
successes
/
((
double
)(
successes
+
failures
)));
generated
=
TRUE
;
}
}
sqlite3_finalize
(
stmt
);
}
}
sqlite3_free
(
sql
);
sql
=
sqlite3_mprintf
(
"select sum(success_count) as successes, sum(failed_count) as failures "
"from cap_status_count where "
"buddy=%Q and account=%Q and protocol=%Q and status=%Q;"
,
buddy_name
,
account_id
,
protocol_id
,
status_id
);
rc
=
sqlite3_prepare
(
_db
,
sql
,
-1
,
&
stmt
,
&
tail
);
if
(
rc
==
SQLITE_OK
)
{
int
successes
=
0
;
int
failures
=
0
;
if
(
stmt
!=
NULL
)
{
if
(
sqlite3_step
(
stmt
)
==
SQLITE_ROW
)
{
successes
=
sqlite3_column_int
(
stmt
,
0
);
failures
=
sqlite3_column_int
(
stmt
,
1
);
if
(
failures
+
successes
>
0
)
{
prediction
*=
((
double
)
successes
/
((
double
)(
successes
+
failures
)));
generated
=
TRUE
;
}
}
sqlite3_finalize
(
stmt
);
}
}
sqlite3_free
(
sql
);
if
(
generated
)
return
prediction
;
else
return
-1
;
}
static
CapStatistics
*
get_stats_for
(
PurpleBuddy
*
buddy
)
{
CapStatistics
*
stats
;
g_return_val_if_fail
(
buddy
!=
NULL
,
NULL
);
stats
=
g_hash_table_lookup
(
_buddy_stats
,
purple_buddy_get_name
(
buddy
));
if
(
!
stats
)
{
stats
=
g_malloc0
(
sizeof
(
CapStatistics
));
stats
->
last_message
=
-1
;
stats
->
buddy
=
buddy
;
stats
->
last_seen
=
-1
;
stats
->
last_status_id
=
""
;
g_hash_table_insert
(
_buddy_stats
,
g_strdup
(
purple_buddy_get_name
(
buddy
)),
stats
);
}
else
{
/* This may actually be a different PurpleBuddy than what is in stats.
* We replace stats->buddy to make sure we're looking at a valid pointer. */
stats
->
buddy
=
buddy
;
}
generate_prediction
(
stats
);
return
stats
;
}
static
void
destroy_stats
(
gpointer
data
)
{
CapStatistics
*
stats
=
data
;
g_free
(
stats
->
prediction
);
/* g_free(stats->hourly_usage); */
/* g_free(stats->daily_usage); */
if
(
stats
->
timeout_source_id
!=
0
)
g_source_remove
(
stats
->
timeout_source_id
);
g_free
(
stats
);
}
static
void
insert_cap_msg_count_success
(
const
char
*
buddy_name
,
const
char
*
account
,
const
char
*
protocol
,
int
minute
)
{
int
rc
;
sqlite3_stmt
*
stmt
;
const
char
*
tail
;
char
*
sql_select
=
sqlite3_mprintf
(
"SELECT * FROM cap_msg_count WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND minute_val=%d;"
,
buddy_name
,
account
,
protocol
,
minute
);
char
*
sql_ins_up
=
NULL
;
purple_debug_info
(
"cap"
,
"%s
\n
"
,
sql_select
);
sqlite3_prepare
(
_db
,
sql_select
,
-1
,
&
stmt
,
&
tail
);
rc
=
sqlite3_step
(
stmt
);
if
(
rc
==
SQLITE_DONE
)
{
sql_ins_up
=
sqlite3_mprintf
(
"INSERT INTO cap_msg_count VALUES (%Q, %Q, %Q, %d, %d, %d);"
,
buddy_name
,
account
,
protocol
,
minute
,
1
,
0
);
}
else
if
(
rc
==
SQLITE_ROW
)
{
sql_ins_up
=
sqlite3_mprintf
(
"UPDATE cap_msg_count SET success_count=success_count+1 WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND minute_val=%d;"
,
buddy_name
,
account
,
protocol
,
minute
);
}
else
{
purple_debug_info
(
"cap"
,
"%d
\n
"
,
rc
);
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
return
;
}
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
sqlite3_exec
(
_db
,
sql_ins_up
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql_ins_up
);
}
static
void
insert_cap_status_count_success
(
const
char
*
buddy_name
,
const
char
*
account
,
const
char
*
protocol
,
const
char
*
status_id
)
{
int
rc
;
sqlite3_stmt
*
stmt
;
const
char
*
tail
;
char
*
sql_select
=
sqlite3_mprintf
(
"SELECT * FROM cap_status_count WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND status=%Q;"
,
buddy_name
,
account
,
protocol
,
status_id
);
char
*
sql_ins_up
=
NULL
;
purple_debug_info
(
"cap"
,
"%s
\n
"
,
sql_select
);
sqlite3_prepare
(
_db
,
sql_select
,
-1
,
&
stmt
,
&
tail
);
rc
=
sqlite3_step
(
stmt
);
if
(
rc
==
SQLITE_DONE
)
{
sql_ins_up
=
sqlite3_mprintf
(
"INSERT INTO cap_status_count VALUES (%Q, %Q, %Q, %Q, %d, %d);"
,
buddy_name
,
account
,
protocol
,
status_id
,
1
,
0
);
}
else
if
(
rc
==
SQLITE_ROW
)
{
sql_ins_up
=
sqlite3_mprintf
(
"UPDATE cap_status_count SET success_count=success_count+1 WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND status=%Q;"
,
buddy_name
,
account
,
protocol
,
status_id
);
}
else
{
purple_debug_info
(
"cap"
,
"%d
\n
"
,
rc
);
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
return
;
}
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
sqlite3_exec
(
_db
,
sql_ins_up
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql_ins_up
);
}
static
void
insert_cap_msg_count_failed
(
const
char
*
buddy_name
,
const
char
*
account
,
const
char
*
protocol
,
int
minute
)
{
int
rc
;
sqlite3_stmt
*
stmt
;
const
char
*
tail
;
char
*
sql_select
=
sqlite3_mprintf
(
"SELECT * FROM cap_msg_count WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND minute_val=%d;"
,
buddy_name
,
account
,
protocol
,
minute
);
char
*
sql_ins_up
=
NULL
;
purple_debug_info
(
"cap"
,
"%s
\n
"
,
sql_select
);
sqlite3_prepare
(
_db
,
sql_select
,
-1
,
&
stmt
,
&
tail
);
rc
=
sqlite3_step
(
stmt
);
if
(
rc
==
SQLITE_DONE
)
{
sql_ins_up
=
sqlite3_mprintf
(
"INSERT INTO cap_msg_count VALUES (%Q, %Q, %Q, %d, %d, %d);"
,
buddy_name
,
account
,
protocol
,
minute
,
0
,
1
);
}
else
if
(
rc
==
SQLITE_ROW
)
{
sql_ins_up
=
sqlite3_mprintf
(
"UPDATE cap_msg_count SET failed_count=failed_count+1 WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND minute_val=%d;"
,
buddy_name
,
account
,
protocol
,
minute
);
}
else
{
purple_debug_info
(
"cap"
,
"%d
\n
"
,
rc
);
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
return
;
}
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
sqlite3_exec
(
_db
,
sql_ins_up
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql_ins_up
);
}
static
void
insert_cap_status_count_failed
(
const
char
*
buddy_name
,
const
char
*
account
,
const
char
*
protocol
,
const
char
*
status_id
)
{
int
rc
;
sqlite3_stmt
*
stmt
;
const
char
*
tail
;
char
*
sql_select
=
sqlite3_mprintf
(
"SELECT * FROM cap_status_count WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND status=%Q;"
,
buddy_name
,
account
,
protocol
,
status_id
);
char
*
sql_ins_up
=
NULL
;
purple_debug_info
(
"cap"
,
"%s
\n
"
,
sql_select
);
sqlite3_prepare
(
_db
,
sql_select
,
-1
,
&
stmt
,
&
tail
);
rc
=
sqlite3_step
(
stmt
);
if
(
rc
==
SQLITE_DONE
)
{
sql_ins_up
=
sqlite3_mprintf
(
"INSERT INTO cap_status_count VALUES (%Q, %Q, %Q, %Q, %d, %d);"
,
buddy_name
,
account
,
protocol
,
status_id
,
0
,
1
);
}
else
if
(
rc
==
SQLITE_ROW
)
{
sql_ins_up
=
sqlite3_mprintf
(
"UPDATE cap_status_count SET failed_count=failed_count+1 WHERE "
"buddy=%Q AND account=%Q AND protocol=%Q AND status=%Q;"
,
buddy_name
,
account
,
protocol
,
status_id
);
}
else
{
purple_debug_info
(
"cap"
,
"%d
\n
"
,
rc
);
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
return
;
}
sqlite3_finalize
(
stmt
);
sqlite3_free
(
sql_select
);
sqlite3_exec
(
_db
,
sql_ins_up
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql_ins_up
);
}
static
void
insert_cap_success
(
CapStatistics
*
stats
)
{
PurpleAccount
*
account
=
purple_buddy_get_account
(
stats
->
buddy
);
const
gchar
*
buddy_name
=
purple_buddy_get_name
(
stats
->
buddy
);
const
gchar
*
protocol_id
=
purple_account_get_protocol_id
(
account
);
const
gchar
*
account_id
=
purple_account_get_username
(
account
);
const
gchar
*
status_id
=
(
stats
->
last_message_status_id
)
?
stats
->
last_message_status_id
:
purple_status_get_id
(
get_status_for
(
stats
->
buddy
));
struct
tm
*
current_time
;
int
minute
;
if
(
stats
->
last_message
==
-1
)
{
time_t
now
=
time
(
NULL
);
current_time
=
localtime
(
&
now
);
}
else
{
current_time
=
localtime
(
&
stats
->
last_message
);
}
minute
=
current_time
->
tm_min
+
current_time
->
tm_hour
*
60
;
insert_cap_msg_count_success
(
buddy_name
,
account_id
,
protocol_id
,
minute
);
insert_cap_status_count_success
(
buddy_name
,
account_id
,
protocol_id
,
status_id
);
stats
->
last_message
=
-1
;
stats
->
last_message_status_id
=
NULL
;
}
static
void
insert_cap_failure
(
CapStatistics
*
stats
)
{
PurpleAccount
*
account
=
purple_buddy_get_account
(
stats
->
buddy
);
const
gchar
*
buddy_name
=
purple_buddy_get_name
(
stats
->
buddy
);
const
gchar
*
protocol_id
=
purple_account_get_protocol_id
(
account
);
const
gchar
*
account_id
=
purple_account_get_username
(
account
);
const
gchar
*
status_id
=
(
stats
->
last_message_status_id
)
?
stats
->
last_message_status_id
:
purple_status_get_id
(
get_status_for
(
stats
->
buddy
));
struct
tm
*
current_time
=
localtime
(
&
stats
->
last_message
);
int
minute
=
current_time
->
tm_min
+
current_time
->
tm_hour
*
60
;
insert_cap_msg_count_failed
(
buddy_name
,
account_id
,
protocol_id
,
minute
);
insert_cap_status_count_failed
(
buddy_name
,
account_id
,
protocol_id
,
status_id
);
stats
->
last_message
=
-1
;
stats
->
last_message_status_id
=
NULL
;
}
static
gboolean
max_message_difference_cb
(
gpointer
data
)
{
CapStatistics
*
stats
=
data
;
purple_debug_info
(
"cap"
,
"Max Message Difference timeout occurred
\n
"
);
insert_cap_failure
(
stats
);
stats
->
timeout_source_id
=
0
;
return
FALSE
;
}
/* Purple Signal Handlers */
/* sent-im-msg */
static
void
sent_im_msg
(
PurpleAccount
*
account
,
PurpleMessage
*
msg
,
gpointer
_unused
)
{
PurpleBuddy
*
buddy
;
guint
interval
,
words
;
CapStatistics
*
stats
=
NULL
;
buddy
=
purple_blist_find_buddy
(
account
,
purple_message_get_recipient
(
msg
));
if
(
buddy
==
NULL
)
return
;
interval
=
purple_prefs_get_int
(
"/plugins/gtk/cap/max_msg_difference"
)
*
60
;
words
=
word_count
(
purple_message_get_contents
(
msg
));
stats
=
get_stats_for
(
buddy
);
insert_word_count
(
purple_account_get_username
(
account
),
purple_message_get_recipient
(
msg
),
words
);
stats
->
last_message
=
time
(
NULL
);
stats
->
last_message_status_id
=
purple_status_get_id
(
get_status_for
(
buddy
));
if
(
stats
->
timeout_source_id
!=
0
)
g_source_remove
(
stats
->
timeout_source_id
);
stats
->
timeout_source_id
=
g_timeout_add_seconds
(
interval
,
max_message_difference_cb
,
stats
);
}
/* received-im-msg */
static
void
received_im_msg
(
PurpleAccount
*
account
,
char
*
sender
,
char
*
message
,
PurpleConversation
*
conv
,
PurpleMessageFlags
flags
)
{
PurpleBuddy
*
buddy
;
CapStatistics
*
stats
;
/* guint words = word_count(message); */
if
(
flags
&
PURPLE_MESSAGE_AUTO_RESP
)
return
;
buddy
=
purple_blist_find_buddy
(
account
,
sender
);
if
(
buddy
==
NULL
)
return
;
stats
=
get_stats_for
(
buddy
);
/* insert_word_count(sender, buddy_name, words); */
/* If we are waiting for a response from a prior message
* then cancel the timeout callback. */
if
(
stats
->
timeout_source_id
!=
0
)
{
purple_debug_info
(
"cap"
,
"Cancelling timeout callback
\n
"
);
g_source_remove
(
stats
->
timeout_source_id
);
stats
->
timeout_source_id
=
0
;
}
insert_cap_success
(
stats
);
/* Reset the last_message value */
stats
->
last_message
=
-1
;
/* Reset the last status id value */
stats
->
last_message_status_id
=
NULL
;
}
/* buddy-status-changed */
static
void
buddy_status_changed
(
PurpleBuddy
*
buddy
,
PurpleStatus
*
old_status
,
PurpleStatus
*
status
)
{
CapStatistics
*
stats
=
get_stats_for
(
buddy
);
insert_status_change_from_purple_status
(
stats
,
status
);
}
/* buddy-signed-on */
static
void
buddy_signed_on
(
PurpleBuddy
*
buddy
)
{
CapStatistics
*
stats
=
get_stats_for
(
buddy
);
/* If the statistic object existed but doesn't have a buddy pointer associated
* with it then reassociate one with it. The pointer being null is a result
* of a buddy with existing stats signing off and Purple sticking around. */
if
(
!
stats
->
buddy
)
{
stats
->
buddy
=
buddy
;
}
insert_status_change
(
stats
);
}
/* buddy-signed-off */
static
void
buddy_signed_off
(
PurpleBuddy
*
buddy
)
{
CapStatistics
*
stats
=
get_stats_for
(
buddy
);
/* We don't necessarily want to delete a buddies generated statistics every time they go offline.
* Instead we just set the buddy pointer to null so that when they come back online we can look
* them up again and continue using their statistics.
*/
insert_status_change
(
stats
);
/* stats->buddy = NULL; */
stats
->
last_seen
=
time
(
NULL
);
}
/* drawing-tooltip */
static
void
drawing_tooltip
(
PurpleBlistNode
*
node
,
GString
*
text
,
gboolean
full
)
{
if
(
PURPLE_IS_BUDDY
(
node
))
{
PurpleBuddy
*
buddy
=
PURPLE_BUDDY
(
node
);
CapStatistics
*
stats
=
get_stats_for
(
buddy
);
/* get the probability that this buddy will respond and add to the tooltip */
if
(
stats
->
prediction
->
probability
>=
0.0
)
{
g_string_append_printf
(
text
,
"
\n
<b>%s</b> %3.0f %%"
,
_
(
"Response Probability:"
),
100
*
stats
->
prediction
->
probability
);
}
else
{
g_string_append_printf
(
text
,
"
\n
<b>%s</b> ???"
,
_
(
"Response Probability:"
));
}
}
}
/* signed-on */
static
void
signed_on
(
PurpleConnection
*
gc
)
{
PurpleAccount
*
account
=
purple_connection_get_account
(
gc
);
const
char
*
my_purple_name
=
purple_account_get_username
(
account
);
gchar
*
my_name
=
g_strdup
(
my_purple_name
);
time_t
*
last_offline
=
g_hash_table_lookup
(
_my_offline_times
,
my_name
);
const
gchar
*
account_id
=
purple_account_get_username
(
account
);
const
gchar
*
protocol_id
=
purple_account_get_protocol_id
(
account
);
char
*
sql
;
sql
=
sqlite3_mprintf
(
"insert into cap_my_usage values(%Q, %Q, %d, now());"
,
account_id
,
protocol_id
,
1
);
sqlite3_exec
(
_db
,
sql
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql
);
if
(
last_offline
)
{
if
(
difftime
(
*
last_offline
,
time
(
NULL
))
>
purple_prefs_get_int
(
"/plugins/gtk/cap/max_seen_difference"
)
*
60
)
{
/* reset all of the last_message times to -1 */
g_hash_table_foreach
(
_my_offline_times
,
reset_all_last_message_times
,
NULL
);
}
g_hash_table_remove
(
_my_offline_times
,
my_name
);
}
g_free
(
my_name
);
}
/* signed-off */
static
void
signed_off
(
PurpleConnection
*
gc
)
{
/* Here we record the time you (the user) sign off of an account.
* The account username is the key in the hashtable and the sign off time_t
* (equal to the sign off time) is the value. */
PurpleAccount
*
account
=
purple_connection_get_account
(
gc
);
const
char
*
my_purple_name
=
purple_account_get_username
(
account
);
gchar
*
my_name
=
g_strdup
(
my_purple_name
);
time_t
*
offline_time
=
g_new0
(
time_t
,
1
);
const
gchar
*
account_id
=
purple_account_get_username
(
account
);
const
gchar
*
protocol_id
=
purple_account_get_protocol_id
(
account
);
char
*
sql
;
sql
=
sqlite3_mprintf
(
"insert into cap_my_usage values(%Q, %Q, %d, now());"
,
account_id
,
protocol_id
,
0
);
sqlite3_exec
(
_db
,
sql
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql
);
time
(
offline_time
);
g_hash_table_insert
(
_my_offline_times
,
my_name
,
offline_time
);
}
static
void
reset_all_last_message_times
(
gpointer
key
,
gpointer
value
,
gpointer
user_data
)
{
CapStatistics
*
stats
=
value
;
stats
->
last_message
=
-1
;
}
static
PurpleStatus
*
get_status_for
(
PurpleBuddy
*
buddy
)
{
PurplePresence
*
presence
=
purple_buddy_get_presence
(
buddy
);
PurpleStatus
*
status
=
purple_presence_get_active_status
(
presence
);
return
status
;
}
static
void
create_tables
()
{
sqlite3_exec
(
_db
,
"CREATE TABLE IF NOT EXISTS cap_status ("
" buddy varchar(60) not null,"
" account varchar(60) not null,"
" protocol varchar(60) not null,"
" status varchar(60) not null,"
" event_time datetime not null,"
" primary key (buddy, account, protocol, event_time)"
");"
,
NULL
,
NULL
,
NULL
);
sqlite3_exec
(
_db
,
"create table if not exists cap_message ("
" sender varchar(60) not null,"
" receiver varchar(60) not null,"
" account varchar(60) not null,"
" protocol varchar(60) not null,"
" word_count integer not null,"
" event_time datetime not null,"
" primary key (sender, account, protocol, receiver, event_time)"
");"
,
NULL
,
NULL
,
NULL
);
sqlite3_exec
(
_db
,
"create table if not exists cap_msg_count ("
" buddy varchar(60) not null,"
" account varchar(60) not null,"
" protocol varchar(60) not null,"
" minute_val int not null,"
" success_count int not null,"
" failed_count int not null,"
" primary key (buddy, account, protocol, minute_val)"
");"
,
NULL
,
NULL
,
NULL
);
sqlite3_exec
(
_db
,
"create table if not exists cap_status_count ("
" buddy varchar(60) not null,"
" account varchar(60) not null,"
" protocol varchar(60) not null,"
" status varchar(60) not null,"
" success_count int not null,"
" failed_count int not null,"
" primary key (buddy, account, protocol, status)"
");"
,
NULL
,
NULL
,
NULL
);
sqlite3_exec
(
_db
,
"create table if not exists cap_my_usage ("
" account varchar(60) not null,"
" protocol varchar(60) not null,"
" online tinyint not null,"
" event_time datetime not null,"
" primary key(account, protocol, online, event_time)"
");"
,
NULL
,
NULL
,
NULL
);
}
static
gboolean
create_database_connection
()
{
gchar
*
path
;
int
rc
;
if
(
_db
)
return
TRUE
;
/* build the path */
path
=
g_build_filename
(
purple_data_dir
(),
"cap.db"
,
(
gchar
*
)
NULL
);
/* make database connection here */
rc
=
sqlite3_open
(
path
,
&
_db
);
g_free
(
path
);
if
(
rc
!=
SQLITE_OK
)
return
FALSE
;
/* Add tables here */
create_tables
();
purple_debug_info
(
"cap"
,
"Database connection successfully made.
\n
"
);
return
TRUE
;
}
static
void
destroy_database_connection
()
{
if
(
_db
)
sqlite3_close
(
_db
);
_db
=
NULL
;
}
static
guint
word_count
(
const
gchar
*
string
)
{
/*TODO: doesn't really work, should use regex instead (#include <regex.h>)*/
gchar
**
result
=
g_strsplit_set
(
string
,
" "
,
-1
);
guint
count
=
g_strv_length
(
result
);
g_strfreev
(
result
);
return
count
;
}
static
void
insert_status_change
(
CapStatistics
*
statistics
)
{
insert_status_change_from_purple_status
(
statistics
,
get_status_for
(
statistics
->
buddy
));
}
static
void
insert_status_change_from_purple_status
(
CapStatistics
*
statistics
,
PurpleStatus
*
status
)
{
PurpleAccount
*
account
=
purple_buddy_get_account
(
statistics
->
buddy
);
char
*
sql
;
const
gchar
*
status_id
;
const
gchar
*
buddy_name
;
const
gchar
*
protocol_id
;
const
gchar
*
account_id
;
/* It would seem that some protocols receive periodic updates of the buddies status.
* Check to make sure the last status is not the same as current status to prevent
* to many duplicated useless database entries. */
if
(
purple_strequal
(
statistics
->
last_status_id
,
purple_status_get_id
(
status
)))
return
;
status_id
=
purple_status_get_id
(
status
);
buddy_name
=
purple_buddy_get_name
(
statistics
->
buddy
);
protocol_id
=
purple_account_get_protocol_id
(
account
);
account_id
=
purple_account_get_username
(
account
);
statistics
->
last_status_id
=
purple_status_get_id
(
status
);
purple_debug_info
(
"cap"
,
"Executing: insert into cap_status (buddy, account, protocol, status, event_time) values(%s, %s, %s, %s, now());
\n
"
,
buddy_name
,
account_id
,
protocol_id
,
status_id
);
sql
=
sqlite3_mprintf
(
"insert into cap_status values (%Q, %Q, %Q, %Q, now());"
,
buddy_name
,
account_id
,
protocol_id
,
status_id
);
sqlite3_exec
(
_db
,
sql
,
NULL
,
NULL
,
NULL
);
sqlite3_free
(
sql
);
}
static
void
insert_word_count
(
const
char
*
sender
,
const
char
*
receiver
,
guint
count
)
{
/* TODO! */
/* dbi_result result; */
/* result = dbi_conn_queryf(_conn, "insert into cap_message values(\'%s\', \'%s\', %d, now());", sender, receiver, count); */
}
/* Purple plugin specific code */
static
void
add_plugin_functionality
(
PurplePlugin
*
plugin
)
{
if
(
_signals_connected
)
return
;
purple_debug_info
(
"cap"
,
"Adding plugin functionality.
\n
"
);
/* Connect all the signals */
purple_signal_connect
(
purple_conversations_get_handle
(),
"sent-im-msg"
,
plugin
,
PURPLE_CALLBACK
(
sent_im_msg
),
NULL
);
purple_signal_connect
(
purple_conversations_get_handle
(),
"received-im-msg"
,
plugin
,
PURPLE_CALLBACK
(
received_im_msg
),
NULL
);
purple_signal_connect
(
purple_blist_get_handle
(),
"buddy-status-changed"
,
plugin
,
PURPLE_CALLBACK
(
buddy_status_changed
),
NULL
);
purple_signal_connect
(
purple_blist_get_handle
(),
"buddy-signed-on"
,
plugin
,
PURPLE_CALLBACK
(
buddy_signed_on
),
NULL
);
purple_signal_connect
(
purple_blist_get_handle
(),
"buddy-signed-off"
,
plugin
,
PURPLE_CALLBACK
(
buddy_signed_off
),
NULL
);
purple_signal_connect
(
pidgin_blist_get_handle
(),
"drawing-tooltip"
,
plugin
,
PURPLE_CALLBACK
(
drawing_tooltip
),
NULL
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-on"
,
plugin
,
PURPLE_CALLBACK
(
signed_on
),
NULL
);
purple_signal_connect
(
purple_connections_get_handle
(),
"signed-off"
,
plugin
,
PURPLE_CALLBACK
(
signed_off
),
NULL
);
_signals_connected
=
TRUE
;
}
static
void
cancel_conversation_timeouts
(
gpointer
key
,
gpointer
value
,
gpointer
user_data
)
{
CapStatistics
*
stats
=
value
;
if
(
stats
->
timeout_source_id
!=
0
)
{
g_source_remove
(
stats
->
timeout_source_id
);
stats
->
timeout_source_id
=
0
;
}
}
static
void
remove_plugin_functionality
(
PurplePlugin
*
plugin
)
{
if
(
!
_signals_connected
)
return
;
purple_debug_info
(
"cap"
,
"Removing plugin functionality.
\n
"
);
/* If there are any timeouts waiting to be processed then cancel them */
g_hash_table_foreach
(
_buddy_stats
,
cancel_conversation_timeouts
,
NULL
);
/* Connect all the signals */
purple_signal_disconnect
(
purple_conversations_get_handle
(),
"sent-im-msg"
,
plugin
,
PURPLE_CALLBACK
(
sent_im_msg
));
purple_signal_disconnect
(
purple_conversations_get_handle
(),
"received-im-msg"
,
plugin
,
PURPLE_CALLBACK
(
received_im_msg
));
purple_signal_disconnect
(
purple_blist_get_handle
(),
"buddy-status-changed"
,
plugin
,
PURPLE_CALLBACK
(
buddy_status_changed
));
purple_signal_disconnect
(
purple_blist_get_handle
(),
"buddy-signed-on"
,
plugin
,
PURPLE_CALLBACK
(
buddy_signed_on
));
purple_signal_disconnect
(
purple_blist_get_handle
(),
"buddy-signed-off"
,
plugin
,
PURPLE_CALLBACK
(
buddy_signed_off
));
purple_signal_disconnect
(
pidgin_blist_get_handle
(),
"drawing-tooltip"
,
plugin
,
PURPLE_CALLBACK
(
drawing_tooltip
));
purple_signal_disconnect
(
purple_connections_get_handle
(),
"signed-on"
,
plugin
,
PURPLE_CALLBACK
(
signed_on
));
purple_signal_disconnect
(
purple_connections_get_handle
(),
"signed-off"
,
plugin
,
PURPLE_CALLBACK
(
signed_off
));
_signals_connected
=
FALSE
;
}
static
void
write_stats_on_unload
(
gpointer
key
,
gpointer
value
,
gpointer
user_data
)
{
CapStatistics
*
stats
=
value
;
if
(
stats
->
last_message
!=
-1
&&
stats
->
buddy
!=
NULL
)
{
insert_cap_failure
(
stats
);
}
}
static
CapPrefsUI
*
create_cap_prefs_ui
()
{
CapPrefsUI
*
ui
=
g_new0
(
CapPrefsUI
,
1
);
ui
->
ret
=
gtk_box_new
(
GTK_ORIENTATION_VERTICAL
,
18
);
gtk_container_set_border_width
(
GTK_CONTAINER
(
ui
->
ret
),
10
);
ui
->
cap_vbox
=
pidgin_make_frame
(
ui
->
ret
,
_
(
"Statistics Configuration"
));
/* msg_difference spinner */
ui
->
msg_difference_label
=
gtk_label_new
(
_
(
"Maximum response timeout:"
));
gtk_label_set_xalign
(
GTK_LABEL
(
ui
->
msg_difference_label
),
0
);
ui
->
msg_difference_input
=
gtk_spin_button_new_with_range
(
1
,
1440
,
1
);
ui
->
msg_difference_minutes_label
=
gtk_label_new
(
_
(
"minutes"
));
gtk_label_set_xalign
(
GTK_LABEL
(
ui
->
msg_difference_minutes_label
),
0
);
/* last_seen spinner */
ui
->
last_seen_label
=
gtk_label_new
(
_
(
"Maximum last-seen difference:"
));
gtk_label_set_xalign
(
GTK_LABEL
(
ui
->
last_seen_label
),
0
);
ui
->
last_seen_input
=
gtk_spin_button_new_with_range
(
1
,
1440
,
1
);
ui
->
last_seen_minutes_label
=
gtk_label_new
(
_
(
"minutes"
));
gtk_label_set_xalign
(
GTK_LABEL
(
ui
->
last_seen_minutes_label
),
0
);
/* threshold spinner */
ui
->
threshold_label
=
gtk_label_new
(
_
(
"Threshold:"
));
gtk_label_set_xalign
(
GTK_LABEL
(
ui
->
threshold_label
),
0
);
ui
->
threshold_input
=
gtk_spin_button_new_with_range
(
1
,
1440
,
1
);
ui
->
threshold_minutes_label
=
gtk_label_new
(
_
(
"minutes"
));
gtk_label_set_xalign
(
GTK_LABEL
(
ui
->
threshold_minutes_label
),
0
);
/* Layout threshold/last-seen/response-timeout input items */
ui
->
table_layout
=
gtk_grid_new
();
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
threshold_label
,
0
,
0
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
threshold_label
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
threshold_input
,
1
,
0
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
threshold_input
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
threshold_minutes_label
,
2
,
0
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
threshold_minutes_label
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
msg_difference_label
,
0
,
1
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
msg_difference_label
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
msg_difference_input
,
1
,
1
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
msg_difference_input
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
msg_difference_minutes_label
,
2
,
1
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
msg_difference_minutes_label
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
last_seen_label
,
0
,
2
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
last_seen_label
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
last_seen_input
,
1
,
2
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
last_seen_input
,
TRUE
);
gtk_grid_attach
(
GTK_GRID
(
ui
->
table_layout
),
ui
->
last_seen_minutes_label
,
2
,
2
,
1
,
1
);
gtk_widget_set_hexpand
(
ui
->
last_seen_minutes_label
,
TRUE
);
/* Config window - lay it out */
gtk_box_pack_start
(
GTK_BOX
(
ui
->
cap_vbox
),
ui
->
table_layout
,
FALSE
,
FALSE
,
0
);
/* Set the input areas to contain the configuration values from
* purple prefs.
*/
if
(
purple_prefs_exists
(
"/plugins/gtk/cap/max_msg_difference"
))
{
int
max_msg_diff
=
purple_prefs_get_int
(
"/plugins/gtk/cap/max_msg_difference"
);
gtk_spin_button_set_value
(
GTK_SPIN_BUTTON
(
ui
->
msg_difference_input
),
max_msg_diff
);
}
if
(
purple_prefs_exists
(
"/plugins/gtk/cap/max_seen_difference"
))
{
int
max_seen_diff
=
purple_prefs_get_int
(
"/plugins/gtk/cap/max_seen_difference"
);
gtk_spin_button_set_value
(
GTK_SPIN_BUTTON
(
ui
->
last_seen_input
),
max_seen_diff
);
}
if
(
purple_prefs_exists
(
"/plugins/gtk/cap/threshold"
))
{
int
threshold
=
purple_prefs_get_int
(
"/plugins/gtk/cap/threshold"
);
gtk_spin_button_set_value
(
GTK_SPIN_BUTTON
(
ui
->
threshold_input
),
threshold
);
}
/* Add the signals */
g_signal_connect
(
G_OBJECT
(
ui
->
ret
),
"destroy"
,
G_CALLBACK
(
cap_prefs_ui_destroy_cb
),
ui
);
g_signal_connect
(
G_OBJECT
(
ui
->
msg_difference_input
),
"value-changed"
,
G_CALLBACK
(
numeric_spinner_prefs_cb
),
"/plugins/gtk/cap/max_msg_difference"
);
g_signal_connect
(
G_OBJECT
(
ui
->
last_seen_input
),
"value-changed"
,
G_CALLBACK
(
numeric_spinner_prefs_cb
),
"/plugins/gtk/cap/max_seen_difference"
);
g_signal_connect
(
G_OBJECT
(
ui
->
threshold_input
),
"value-changed"
,
G_CALLBACK
(
numeric_spinner_prefs_cb
),
"/plugins/gtk/cap/threshold"
);
return
ui
;
}
static
void
cap_prefs_ui_destroy_cb
(
GObject
*
object
,
gpointer
user_data
)
{
CapPrefsUI
*
ui
=
user_data
;
if
(
_db
)
{
add_plugin_functionality
(
_plugin_pointer
);
}
g_free
(
ui
);
}
static
void
numeric_spinner_prefs_cb
(
GtkSpinButton
*
spinbutton
,
gpointer
user_data
)
{
purple_prefs_set_int
(
user_data
,
gtk_spin_button_get_value_as_int
(
spinbutton
));
}
static
GtkWidget
*
get_config_frame
(
PurplePlugin
*
plugin
)
{
CapPrefsUI
*
ui
=
create_cap_prefs_ui
();
/*
* Prevent database stuff from occuring since we are editing values
*/
remove_plugin_functionality
(
_plugin_pointer
);
return
ui
->
ret
;
}
static
PidginPluginInfo
*
plugin_query
(
GError
**
error
)
{
const
gchar
*
const
authors
[]
=
{
"Geoffrey Foster <geoffrey.foster@gmail.com>"
,
NULL
};
return
pidgin_plugin_info_new
(
"id"
,
CAP_PLUGIN_ID
,
"name"
,
N_
(
"Contact Availability Prediction"
),
"version"
,
DISPLAY_VERSION
,
"category"
,
N_
(
"Utility"
),
"summary"
,
N_
(
"Contact Availability Prediction plugin."
),
"description"
,
N_
(
"Displays statistical information about "
"your buddies' availability"
),
"authors"
,
authors
,
"website"
,
PURPLE_WEBSITE
,
"abi-version"
,
PURPLE_ABI_VERSION
,
"gtk-config-frame-cb"
,
get_config_frame
,
NULL
);
}
static
gboolean
plugin_load
(
PurplePlugin
*
plugin
,
GError
**
error
)
{
purple_prefs_add_none
(
"/plugins/gtk/cap"
);
purple_prefs_add_int
(
"/plugins/gtk/cap/max_seen_difference"
,
1
);
purple_prefs_add_int
(
"/plugins/gtk/cap/max_msg_difference"
,
10
);
purple_prefs_add_int
(
"/plugins/gtk/cap/threshold"
,
5
);
_plugin_pointer
=
plugin
;
_signals_connected
=
FALSE
;
/* buddy_stats is a hashtable where strings are keys
* and the keys are a buddies account id (PurpleBuddy.name).
* keys/values are automatically deleted */
_buddy_stats
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
destroy_stats
);
/* ? - Can't remember at the moment
*/
_my_offline_times
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
g_free
);
if
(
create_database_connection
())
{
add_plugin_functionality
(
plugin
);
}
return
TRUE
;
}
static
gboolean
plugin_unload
(
PurplePlugin
*
plugin
,
GError
**
error
)
{
purple_debug_info
(
"cap"
,
"CAP plugin unloading
\n
"
);
/* clean up memory allocations */
if
(
_buddy_stats
)
{
g_hash_table_foreach
(
_buddy_stats
,
write_stats_on_unload
,
NULL
);
g_hash_table_destroy
(
_buddy_stats
);
}
/* close database connection */
destroy_database_connection
();
return
TRUE
;
}
PURPLE_PLUGIN_INIT
(
cap
,
plugin_query
,
plugin_load
,
plugin_unload
);