pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Remove the Gtk Ticker plugin as it doesn't scale to today's IM networks
2020-08-22, Gary Kramlich
fefaa6596e74
Remove the Gtk Ticker plugin as it doesn't scale to today's IM networks
Remove the ticker plugin as it doesn't scale to todays typical IM usage.
Testing Done:
Compile and install.
Reviewed at https://reviews.imfreedom.org/r/81/
/* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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
<glib/gi18n-lib.h>
#include
"internal.h"
#include
<glib.h>
#include
<glib/gstdio.h>
#include
"glibcompat.h"
/* for purple_g_stat on win32 */
#include
"account.h"
#include
"debug.h"
#include
"image-store.h"
#include
"log.h"
#include
"prefs.h"
#include
"util.h"
#include
"time.h"
static
GSList
*
loggers
=
NULL
;
static
PurpleLogLogger
*
html_logger
;
static
PurpleLogLogger
*
txt_logger
;
struct
_purple_logsize_user
{
char
*
name
;
PurpleAccount
*
account
;
};
static
GHashTable
*
logsize_users
=
NULL
;
static
GHashTable
*
logsize_users_decayed
=
NULL
;
static
void
log_get_log_sets_common
(
GHashTable
*
sets
);
static
gsize
html_logger_write
(
PurpleLog
*
log
,
PurpleMessageFlags
type
,
const
char
*
from
,
GDateTime
*
time
,
const
char
*
message
);
static
void
html_logger_finalize
(
PurpleLog
*
log
);
static
GList
*
html_logger_list
(
PurpleLogType
type
,
const
char
*
sn
,
PurpleAccount
*
account
);
static
GList
*
html_logger_list_syslog
(
PurpleAccount
*
account
);
static
char
*
html_logger_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
);
static
int
html_logger_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
);
static
gsize
txt_logger_write
(
PurpleLog
*
log
,
PurpleMessageFlags
type
,
const
char
*
from
,
GDateTime
*
time
,
const
char
*
message
);
static
void
txt_logger_finalize
(
PurpleLog
*
log
);
static
GList
*
txt_logger_list
(
PurpleLogType
type
,
const
char
*
sn
,
PurpleAccount
*
account
);
static
GList
*
txt_logger_list_syslog
(
PurpleAccount
*
account
);
static
char
*
txt_logger_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
);
static
int
txt_logger_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
);
/**************************************************************************
* PUBLIC LOGGING FUNCTIONS ***********************************************
**************************************************************************/
PurpleLog
*
purple_log_new
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
,
PurpleConversation
*
conv
,
GDateTime
*
time
)
{
PurpleLog
*
log
;
/* IMPORTANT: Make sure to initialize all the members of PurpleLog */
log
=
g_slice_new
(
PurpleLog
);
log
->
type
=
type
;
log
->
name
=
g_strdup
(
purple_normalize
(
account
,
name
));
log
->
account
=
account
;
log
->
conv
=
conv
;
if
(
time
)
log
->
time
=
g_date_time_ref
(
time
);
else
log
->
time
=
NULL
;
log
->
logger
=
purple_log_logger_get
();
log
->
logger_data
=
NULL
;
if
(
log
->
logger
&&
log
->
logger
->
create
)
log
->
logger
->
create
(
log
);
return
log
;
}
void
purple_log_free
(
PurpleLog
*
log
)
{
g_return_if_fail
(
log
);
if
(
log
->
logger
&&
log
->
logger
->
finalize
)
log
->
logger
->
finalize
(
log
);
g_free
(
log
->
name
);
if
(
log
->
time
)
g_date_time_unref
(
log
->
time
);
g_slice_free
(
PurpleLog
,
log
);
}
void
purple_log_write
(
PurpleLog
*
log
,
PurpleMessageFlags
type
,
const
char
*
from
,
GDateTime
*
time
,
const
char
*
message
)
{
struct
_purple_logsize_user
*
lu
;
gsize
written
,
total
=
0
;
gpointer
ptrsize
;
g_return_if_fail
(
log
);
g_return_if_fail
(
log
->
logger
);
g_return_if_fail
(
log
->
logger
->
write
);
written
=
(
log
->
logger
->
write
)(
log
,
type
,
from
,
time
,
message
);
lu
=
g_new
(
struct
_purple_logsize_user
,
1
);
lu
->
name
=
g_strdup
(
purple_normalize
(
log
->
account
,
log
->
name
));
lu
->
account
=
log
->
account
;
if
(
g_hash_table_lookup_extended
(
logsize_users
,
lu
,
NULL
,
&
ptrsize
))
{
char
*
tmp
=
lu
->
name
;
total
=
GPOINTER_TO_INT
(
ptrsize
);
total
+=
written
;
g_hash_table_replace
(
logsize_users
,
lu
,
GINT_TO_POINTER
(
total
));
/* The hash table takes ownership of lu, so create a new one
* for the logsize_users_decayed check below. */
lu
=
g_new
(
struct
_purple_logsize_user
,
1
);
lu
->
name
=
g_strdup
(
tmp
);
lu
->
account
=
log
->
account
;
}
if
(
g_hash_table_lookup_extended
(
logsize_users_decayed
,
lu
,
NULL
,
&
ptrsize
))
{
total
=
GPOINTER_TO_INT
(
ptrsize
);
total
+=
written
;
g_hash_table_replace
(
logsize_users_decayed
,
lu
,
GINT_TO_POINTER
(
total
));
}
else
{
g_free
(
lu
->
name
);
g_free
(
lu
);
}
}
char
*
purple_log_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
)
{
PurpleLogReadFlags
mflags
;
g_return_val_if_fail
(
log
&&
log
->
logger
,
NULL
);
if
(
log
->
logger
->
read
)
{
char
*
ret
=
(
log
->
logger
->
read
)(
log
,
flags
?
flags
:
&
mflags
);
purple_str_strip_char
(
ret
,
'\r'
);
return
ret
;
}
return
g_strdup
(
_
(
"<b><font color=
\"
red
\"
>The logger has no read function</font></b>"
));
}
int
purple_log_get_size
(
PurpleLog
*
log
)
{
g_return_val_if_fail
(
log
&&
log
->
logger
,
0
);
if
(
log
->
logger
->
size
)
return
log
->
logger
->
size
(
log
);
return
0
;
}
static
guint
_purple_logsize_user_hash
(
struct
_purple_logsize_user
*
lu
)
{
return
g_str_hash
(
lu
->
name
);
}
static
guint
_purple_logsize_user_equal
(
struct
_purple_logsize_user
*
lu1
,
struct
_purple_logsize_user
*
lu2
)
{
return
(
lu1
->
account
==
lu2
->
account
&&
purple_strequal
(
lu1
->
name
,
lu2
->
name
));
}
static
void
_purple_logsize_user_free_key
(
struct
_purple_logsize_user
*
lu
)
{
g_free
(
lu
->
name
);
g_free
(
lu
);
}
int
purple_log_get_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
gpointer
ptrsize
;
int
size
=
0
;
GSList
*
n
;
struct
_purple_logsize_user
*
lu
;
lu
=
g_new
(
struct
_purple_logsize_user
,
1
);
lu
->
name
=
g_strdup
(
purple_normalize
(
account
,
name
));
lu
->
account
=
account
;
if
(
g_hash_table_lookup_extended
(
logsize_users
,
lu
,
NULL
,
&
ptrsize
))
{
size
=
GPOINTER_TO_INT
(
ptrsize
);
g_free
(
lu
->
name
);
g_free
(
lu
);
}
else
{
for
(
n
=
loggers
;
n
;
n
=
n
->
next
)
{
PurpleLogLogger
*
logger
=
n
->
data
;
if
(
logger
->
total_size
){
size
+=
(
logger
->
total_size
)(
type
,
name
,
account
);
}
else
if
(
logger
->
list
)
{
GList
*
logs
=
(
logger
->
list
)(
type
,
name
,
account
);
int
this_size
=
0
;
while
(
logs
)
{
PurpleLog
*
log
=
(
PurpleLog
*
)(
logs
->
data
);
this_size
+=
purple_log_get_size
(
log
);
purple_log_free
(
log
);
logs
=
g_list_delete_link
(
logs
,
logs
);
}
size
+=
this_size
;
}
}
g_hash_table_replace
(
logsize_users
,
lu
,
GINT_TO_POINTER
(
size
));
}
return
size
;
}
gint
purple_log_get_activity_score
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
gpointer
ptrscore
;
int
score
;
GSList
*
n
;
struct
_purple_logsize_user
*
lu
;
lu
=
g_new
(
struct
_purple_logsize_user
,
1
);
lu
->
name
=
g_strdup
(
purple_normalize
(
account
,
name
));
lu
->
account
=
account
;
if
(
g_hash_table_lookup_extended
(
logsize_users_decayed
,
lu
,
NULL
,
&
ptrscore
))
{
score
=
GPOINTER_TO_INT
(
ptrscore
);
g_free
(
lu
->
name
);
g_free
(
lu
);
}
else
{
GDateTime
*
now
=
g_date_time_new_now_utc
();
double
score_double
=
0.0
;
for
(
n
=
loggers
;
n
;
n
=
n
->
next
)
{
PurpleLogLogger
*
logger
=
n
->
data
;
if
(
logger
->
list
)
{
GList
*
logs
=
(
logger
->
list
)(
type
,
name
,
account
);
while
(
logs
)
{
PurpleLog
*
log
=
(
PurpleLog
*
)(
logs
->
data
);
if
(
!
log
)
{
g_warn_if_reached
();
continue
;
}
/* Activity score counts bytes in the log, exponentially
decayed with a half-life of 14 days. */
score_double
+=
purple_log_get_size
(
log
)
*
pow
(
0.5
,
g_date_time_difference
(
now
,
log
->
time
)
/
(
14L
L
*
G_TIME_SPAN_DAY
));
purple_log_free
(
log
);
logs
=
g_list_delete_link
(
logs
,
logs
);
}
}
}
g_date_time_unref
(
now
);
score
=
(
gint
)
ceil
(
score_double
);
g_hash_table_replace
(
logsize_users_decayed
,
lu
,
GINT_TO_POINTER
(
score
));
}
return
score
;
}
gboolean
purple_log_is_deletable
(
PurpleLog
*
log
)
{
g_return_val_if_fail
(
log
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
log
->
logger
!=
NULL
,
FALSE
);
if
(
log
->
logger
->
remove
==
NULL
)
return
FALSE
;
if
(
log
->
logger
->
is_deletable
!=
NULL
)
return
log
->
logger
->
is_deletable
(
log
);
return
TRUE
;
}
gboolean
purple_log_delete
(
PurpleLog
*
log
)
{
g_return_val_if_fail
(
log
!=
NULL
,
FALSE
);
g_return_val_if_fail
(
log
->
logger
!=
NULL
,
FALSE
);
if
(
log
->
logger
->
remove
!=
NULL
)
return
log
->
logger
->
remove
(
log
);
return
FALSE
;
}
char
*
purple_log_get_log_dir
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
PurpleProtocol
*
protocol
;
const
char
*
protocol_name
;
char
*
acct_name
;
const
char
*
target
;
char
*
dir
;
protocol
=
purple_protocols_find
(
purple_account_get_protocol_id
(
account
));
if
(
!
protocol
)
return
NULL
;
protocol_name
=
purple_protocol_class_list_icon
(
protocol
,
account
,
NULL
);
acct_name
=
g_strdup
(
purple_escape_filename
(
purple_normalize
(
account
,
purple_account_get_username
(
account
))));
if
(
type
==
PURPLE_LOG_CHAT
)
{
char
*
temp
=
g_strdup_printf
(
"%s.chat"
,
purple_normalize
(
account
,
name
));
target
=
purple_escape_filename
(
temp
);
g_free
(
temp
);
}
else
if
(
type
==
PURPLE_LOG_SYSTEM
)
{
target
=
".system"
;
}
else
{
target
=
purple_escape_filename
(
purple_normalize
(
account
,
name
));
}
dir
=
g_build_filename
(
purple_data_dir
(),
"logs"
,
protocol_name
,
acct_name
,
target
,
NULL
);
g_free
(
acct_name
);
return
dir
;
}
/****************************************************************************
* LOGGER FUNCTIONS *********************************************************
****************************************************************************/
static
PurpleLogLogger
*
current_logger
=
NULL
;
static
void
logger_pref_cb
(
const
char
*
name
,
PurplePrefType
type
,
gconstpointer
value
,
gpointer
data
)
{
PurpleLogLogger
*
logger
;
GSList
*
l
=
loggers
;
while
(
l
)
{
logger
=
l
->
data
;
if
(
purple_strequal
(
logger
->
id
,
value
))
{
purple_log_logger_set
(
logger
);
return
;
}
l
=
l
->
next
;
}
purple_log_logger_set
(
txt_logger
);
}
PurpleLogLogger
*
purple_log_logger_new
(
const
char
*
id
,
const
char
*
name
,
int
functions
,
...)
{
PurpleLogLogger
*
logger
;
va_list
args
;
g_return_val_if_fail
(
id
!=
NULL
,
NULL
);
g_return_val_if_fail
(
name
!=
NULL
,
NULL
);
g_return_val_if_fail
(
functions
>=
1
,
NULL
);
logger
=
g_new0
(
PurpleLogLogger
,
1
);
logger
->
id
=
g_strdup
(
id
);
logger
->
name
=
g_strdup
(
name
);
va_start
(
args
,
functions
);
if
(
functions
>=
1
)
logger
->
create
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
2
)
logger
->
write
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
3
)
logger
->
finalize
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
4
)
logger
->
list
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
5
)
logger
->
read
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
6
)
logger
->
size
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
7
)
logger
->
total_size
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
8
)
logger
->
list_syslog
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
9
)
logger
->
get_log_sets
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
10
)
logger
->
remove
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
11
)
logger
->
is_deletable
=
va_arg
(
args
,
void
*
);
if
(
functions
>=
12
)
purple_debug_info
(
"log"
,
"Dropping new functions for logger: %s (%s)
\n
"
,
name
,
id
);
va_end
(
args
);
return
logger
;
}
void
purple_log_logger_free
(
PurpleLogLogger
*
logger
)
{
if
(
!
logger
)
return
;
g_free
(
logger
->
name
);
g_free
(
logger
->
id
);
g_free
(
logger
);
}
void
purple_log_logger_add
(
PurpleLogLogger
*
logger
)
{
g_return_if_fail
(
logger
);
if
(
g_slist_find
(
loggers
,
logger
))
return
;
loggers
=
g_slist_append
(
loggers
,
logger
);
if
(
purple_strequal
(
purple_prefs_get_string
(
"/purple/logging/format"
),
logger
->
id
))
{
purple_prefs_trigger_callback
(
"/purple/logging/format"
);
}
}
void
purple_log_logger_remove
(
PurpleLogLogger
*
logger
)
{
g_return_if_fail
(
logger
);
loggers
=
g_slist_remove
(
loggers
,
logger
);
}
void
purple_log_logger_set
(
PurpleLogLogger
*
logger
)
{
g_return_if_fail
(
logger
);
current_logger
=
logger
;
}
PurpleLogLogger
*
purple_log_logger_get
()
{
return
current_logger
;
}
GList
*
purple_log_logger_get_options
(
void
)
{
GSList
*
n
;
GList
*
list
=
NULL
;
PurpleLogLogger
*
data
;
for
(
n
=
loggers
;
n
;
n
=
n
->
next
)
{
data
=
n
->
data
;
if
(
!
data
->
write
)
continue
;
list
=
g_list_append
(
list
,
data
->
name
);
list
=
g_list_append
(
list
,
data
->
id
);
}
return
list
;
}
gint
purple_log_compare
(
gconstpointer
y
,
gconstpointer
z
)
{
const
PurpleLog
*
a
=
y
;
const
PurpleLog
*
b
=
z
;
/* Sort in reverse order. */
return
g_date_time_compare
(
b
->
time
,
a
->
time
);
}
GList
*
purple_log_get_logs
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
GList
*
logs
=
NULL
;
GSList
*
n
;
for
(
n
=
loggers
;
n
;
n
=
n
->
next
)
{
PurpleLogLogger
*
logger
=
n
->
data
;
if
(
!
logger
->
list
)
continue
;
logs
=
g_list_concat
(
logger
->
list
(
type
,
name
,
account
),
logs
);
}
return
g_list_sort
(
logs
,
purple_log_compare
);
}
gint
purple_log_set_compare
(
gconstpointer
y
,
gconstpointer
z
)
{
const
PurpleLogSet
*
a
=
y
;
const
PurpleLogSet
*
b
=
z
;
gint
ret
=
0
;
/* This logic seems weird at first...
* If either account is NULL, we pretend the accounts are
* equal. This allows us to detect duplicates that will
* exist if one logger knows the account and another
* doesn't. */
if
(
a
->
account
!=
NULL
&&
b
->
account
!=
NULL
)
{
ret
=
strcmp
(
purple_account_get_username
(
a
->
account
),
purple_account_get_username
(
b
->
account
));
if
(
ret
!=
0
)
return
ret
;
}
ret
=
strcmp
(
a
->
normalized_name
,
b
->
normalized_name
);
if
(
ret
!=
0
)
return
ret
;
return
(
gint
)
b
->
type
-
(
gint
)
a
->
type
;
}
static
guint
log_set_hash
(
gconstpointer
key
)
{
const
PurpleLogSet
*
set
=
key
;
/* The account isn't hashed because we need PurpleLogSets with NULL accounts
* to be found when we search by a PurpleLogSet that has a non-NULL account
* but the same type and name. */
return
g_int_hash
(
&
set
->
type
)
+
g_str_hash
(
set
->
name
);
}
static
gboolean
log_set_equal
(
gconstpointer
a
,
gconstpointer
b
)
{
/* I realize that the choices made for GList and GHashTable
* make sense for those data types, but I wish the comparison
* functions were compatible. */
return
!
purple_log_set_compare
(
a
,
b
);
}
static
void
log_add_log_set_to_hash
(
GHashTable
*
sets
,
PurpleLogSet
*
set
)
{
PurpleLogSet
*
existing_set
=
g_hash_table_lookup
(
sets
,
set
);
if
(
existing_set
==
NULL
)
g_hash_table_insert
(
sets
,
set
,
set
);
else
if
(
existing_set
->
account
==
NULL
&&
set
->
account
!=
NULL
)
g_hash_table_replace
(
sets
,
set
,
set
);
else
purple_log_set_free
(
set
);
}
GHashTable
*
purple_log_get_log_sets
(
void
)
{
GSList
*
n
;
GHashTable
*
sets
=
g_hash_table_new_full
(
log_set_hash
,
log_set_equal
,
(
GDestroyNotify
)
purple_log_set_free
,
NULL
);
/* Get the log sets from all the loggers. */
for
(
n
=
loggers
;
n
;
n
=
n
->
next
)
{
PurpleLogLogger
*
logger
=
n
->
data
;
if
(
!
logger
->
get_log_sets
)
continue
;
logger
->
get_log_sets
(
log_add_log_set_to_hash
,
sets
);
}
log_get_log_sets_common
(
sets
);
/* Return the GHashTable of unique PurpleLogSets. */
return
sets
;
}
void
purple_log_set_free
(
PurpleLogSet
*
set
)
{
g_return_if_fail
(
set
!=
NULL
);
g_free
(
set
->
name
);
if
(
set
->
normalized_name
!=
set
->
name
)
g_free
(
set
->
normalized_name
);
g_slice_free
(
PurpleLogSet
,
set
);
}
GList
*
purple_log_get_system_logs
(
PurpleAccount
*
account
)
{
GList
*
logs
=
NULL
;
GSList
*
n
;
for
(
n
=
loggers
;
n
;
n
=
n
->
next
)
{
PurpleLogLogger
*
logger
=
n
->
data
;
if
(
!
logger
->
list_syslog
)
continue
;
logs
=
g_list_concat
(
logger
->
list_syslog
(
account
),
logs
);
}
return
g_list_sort
(
logs
,
purple_log_compare
);
}
/****************************************************************************
* LOG SUBSYSTEM ************************************************************
****************************************************************************/
void
*
purple_log_get_handle
(
void
)
{
static
int
handle
;
return
&
handle
;
}
void
purple_log_init
(
void
)
{
void
*
handle
=
purple_log_get_handle
();
purple_prefs_add_none
(
"/purple/logging"
);
purple_prefs_add_bool
(
"/purple/logging/log_ims"
,
TRUE
);
purple_prefs_add_bool
(
"/purple/logging/log_chats"
,
TRUE
);
purple_prefs_add_bool
(
"/purple/logging/log_system"
,
FALSE
);
purple_prefs_add_string
(
"/purple/logging/format"
,
"html"
);
html_logger
=
purple_log_logger_new
(
"html"
,
_
(
"HTML"
),
11
,
NULL
,
html_logger_write
,
html_logger_finalize
,
html_logger_list
,
html_logger_read
,
purple_log_common_sizer
,
html_logger_total_size
,
html_logger_list_syslog
,
NULL
,
purple_log_common_deleter
,
purple_log_common_is_deletable
);
purple_log_logger_add
(
html_logger
);
txt_logger
=
purple_log_logger_new
(
"txt"
,
_
(
"Plain text"
),
11
,
NULL
,
txt_logger_write
,
txt_logger_finalize
,
txt_logger_list
,
txt_logger_read
,
purple_log_common_sizer
,
txt_logger_total_size
,
txt_logger_list_syslog
,
NULL
,
purple_log_common_deleter
,
purple_log_common_is_deletable
);
purple_log_logger_add
(
txt_logger
);
purple_signal_register
(
handle
,
"log-timestamp"
,
purple_marshal_POINTER__POINTER_POINTER_BOOLEAN
,
G_TYPE_STRING
,
3
,
PURPLE_TYPE_LOG
,
G_TYPE_OBJECT
,
G_TYPE_BOOLEAN
);
purple_prefs_connect_callback
(
NULL
,
"/purple/logging/format"
,
logger_pref_cb
,
NULL
);
purple_prefs_trigger_callback
(
"/purple/logging/format"
);
logsize_users
=
g_hash_table_new_full
((
GHashFunc
)
_purple_logsize_user_hash
,
(
GEqualFunc
)
_purple_logsize_user_equal
,
(
GDestroyNotify
)
_purple_logsize_user_free_key
,
NULL
);
logsize_users_decayed
=
g_hash_table_new_full
((
GHashFunc
)
_purple_logsize_user_hash
,
(
GEqualFunc
)
_purple_logsize_user_equal
,
(
GDestroyNotify
)
_purple_logsize_user_free_key
,
NULL
);
}
void
purple_log_uninit
(
void
)
{
purple_signals_unregister_by_instance
(
purple_log_get_handle
());
purple_log_logger_remove
(
html_logger
);
purple_log_logger_free
(
html_logger
);
html_logger
=
NULL
;
purple_log_logger_remove
(
txt_logger
);
purple_log_logger_free
(
txt_logger
);
txt_logger
=
NULL
;
g_hash_table_destroy
(
logsize_users
);
g_hash_table_destroy
(
logsize_users_decayed
);
}
static
PurpleLog
*
purple_log_copy
(
PurpleLog
*
log
)
{
PurpleLog
*
log_copy
;
g_return_val_if_fail
(
log
!=
NULL
,
NULL
);
log_copy
=
g_new
(
PurpleLog
,
1
);
*
log_copy
=
*
log
;
return
log_copy
;
}
GType
purple_log_get_type
(
void
)
{
static
GType
type
=
0
;
if
(
type
==
0
)
{
type
=
g_boxed_type_register_static
(
"PurpleLog"
,
(
GBoxedCopyFunc
)
purple_log_copy
,
(
GBoxedFreeFunc
)
g_free
);
}
return
type
;
}
/****************************************************************************
* LOGGERS ******************************************************************
****************************************************************************/
static
char
*
log_get_timestamp
(
PurpleLog
*
log
,
GDateTime
*
when
)
{
gboolean
show_date
;
char
*
date
;
GDateTime
*
dt
;
dt
=
g_date_time_new_now_utc
();
show_date
=
(
log
->
type
==
PURPLE_LOG_SYSTEM
)
||
(
g_date_time_difference
(
dt
,
when
)
>
20L
*
G_TIME_SPAN_MINUTE
);
g_date_time_unref
(
dt
);
date
=
purple_signal_emit_return_1
(
purple_log_get_handle
(),
"log-timestamp"
,
log
,
when
,
show_date
);
if
(
date
!=
NULL
)
return
date
;
dt
=
g_date_time_to_local
(
when
);
if
(
show_date
)
date
=
g_date_time_format
(
dt
,
_
(
"%x %X"
));
else
date
=
g_date_time_format
(
dt
,
"%X"
);
g_date_time_unref
(
dt
);
return
date
;
}
/* NOTE: This can return msg (which you may or may not want to g_free())
* NOTE: or a newly allocated string which you MUST g_free().
* TODO: XXX: does it really works?
*/
static
char
*
convert_image_tags
(
const
PurpleLog
*
log
,
const
char
*
msg
)
{
const
char
*
tmp
;
const
char
*
start
;
const
char
*
end
;
GData
*
attributes
;
GString
*
newmsg
=
NULL
;
tmp
=
msg
;
while
(
purple_markup_find_tag
(
"img"
,
tmp
,
&
start
,
&
end
,
&
attributes
))
{
int
imgid
=
0
;
char
*
idstr
=
NULL
;
if
(
newmsg
==
NULL
)
newmsg
=
g_string_new
(
""
);
/* copy any text before the img tag */
if
(
tmp
<
start
)
g_string_append_len
(
newmsg
,
tmp
,
start
-
tmp
);
if
((
idstr
=
g_datalist_get_data
(
&
attributes
,
"id"
))
!=
NULL
)
imgid
=
atoi
(
idstr
);
if
(
imgid
!=
0
)
{
FILE
*
image_file
;
char
*
dir
;
PurpleImage
*
image
;
gconstpointer
image_data
;
const
gchar
*
new_filename
=
NULL
;
char
*
path
=
NULL
;
size_t
image_byte_count
;
image
=
purple_image_store_get
(
imgid
);
if
(
image
==
NULL
)
{
/* This should never happen. */
/* This *does* happen for failed Direct-IMs -DAA */
g_string_free
(
newmsg
,
TRUE
);
g_return_val_if_reached
((
char
*
)
msg
);
}
image_data
=
purple_image_get_data
(
image
);
image_byte_count
=
purple_image_get_data_size
(
image
);
dir
=
purple_log_get_log_dir
(
log
->
type
,
log
->
name
,
log
->
account
);
new_filename
=
purple_image_generate_filename
(
image
);
path
=
g_build_filename
(
dir
,
new_filename
,
NULL
);
/* Only save unique files. */
if
(
!
g_file_test
(
path
,
G_FILE_TEST_EXISTS
))
{
if
((
image_file
=
g_fopen
(
path
,
"wb"
))
!=
NULL
)
{
if
(
!
fwrite
(
image_data
,
image_byte_count
,
1
,
image_file
))
{
purple_debug_error
(
"log"
,
"Error writing %s: %s
\n
"
,
path
,
g_strerror
(
errno
));
fclose
(
image_file
);
/* Attempt to not leave half-written files around. */
if
(
g_unlink
(
path
))
{
purple_debug_error
(
"log"
,
"Error deleting partial "
"file %s: %s
\n
"
,
path
,
g_strerror
(
errno
));
}
}
else
{
purple_debug_info
(
"log"
,
"Wrote image file: %s
\n
"
,
path
);
fclose
(
image_file
);
}
}
else
{
purple_debug_error
(
"log"
,
"Unable to create file %s: %s
\n
"
,
path
,
g_strerror
(
errno
));
}
}
/* Write the new image tag */
g_string_append_printf
(
newmsg
,
"<img src=
\"
%s
\"
>"
,
new_filename
);
g_free
(
path
);
g_free
(
dir
);
}
/* Continue from the end of the tag */
tmp
=
end
+
1
;
}
if
(
newmsg
==
NULL
)
{
/* No images were found to change. */
return
(
char
*
)
msg
;
}
/* Append any remaining message data */
g_string_append
(
newmsg
,
tmp
);
return
g_string_free
(
newmsg
,
FALSE
);
}
void
purple_log_common_writer
(
PurpleLog
*
log
,
const
char
*
ext
)
{
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
if
(
data
==
NULL
)
{
/* This log is new */
char
*
dir
;
GDateTime
*
dt
;
const
char
*
tz
;
gchar
*
date
;
char
*
filename
;
char
*
path
;
dir
=
purple_log_get_log_dir
(
log
->
type
,
log
->
name
,
log
->
account
);
if
(
dir
==
NULL
)
return
;
g_mkdir_with_parents
(
dir
,
S_IRUSR
|
S_IWUSR
|
S_IXUSR
);
dt
=
g_date_time_to_local
(
log
->
time
);
tz
=
purple_escape_filename
(
g_date_time_get_timezone_abbreviation
(
dt
));
date
=
g_date_time_format
(
dt
,
"%Y-%m-%d.%H%M%S%z"
);
g_date_time_unref
(
dt
);
filename
=
g_strdup_printf
(
"%s%s%s"
,
date
,
tz
,
ext
?
ext
:
""
);
path
=
g_build_filename
(
dir
,
filename
,
NULL
);
g_free
(
dir
);
g_free
(
date
);
g_free
(
filename
);
log
->
logger_data
=
data
=
g_slice_new0
(
PurpleLogCommonLoggerData
);
data
->
file
=
g_fopen
(
path
,
"a"
);
if
(
data
->
file
==
NULL
)
{
purple_debug
(
PURPLE_DEBUG_ERROR
,
"log"
,
"Could not create log file %s
\n
"
,
path
);
if
(
log
->
conv
!=
NULL
)
purple_conversation_write_system_message
(
log
->
conv
,
_
(
"Logging of this conversation failed."
),
PURPLE_MESSAGE_ERROR
);
g_free
(
path
);
return
;
}
g_free
(
path
);
}
}
GList
*
purple_log_common_lister
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
,
const
char
*
ext
,
PurpleLogLogger
*
logger
)
{
GDir
*
dir
;
GList
*
list
=
NULL
;
const
char
*
filename
;
char
*
path
;
if
(
!
account
)
return
NULL
;
path
=
purple_log_get_log_dir
(
type
,
name
,
account
);
if
(
path
==
NULL
)
return
NULL
;
if
(
!
(
dir
=
g_dir_open
(
path
,
0
,
NULL
)))
{
g_free
(
path
);
return
NULL
;
}
while
((
filename
=
g_dir_read_name
(
dir
)))
{
if
(
g_str_has_suffix
(
filename
,
ext
)
&&
strlen
(
filename
)
>=
(
17
+
strlen
(
ext
)))
{
PurpleLog
*
log
;
PurpleLogCommonLoggerData
*
data
;
GDateTime
*
stamp
=
purple_str_to_date_time
(
purple_unescape_filename
(
filename
),
FALSE
);
log
=
purple_log_new
(
type
,
name
,
account
,
NULL
,
stamp
);
log
->
logger
=
logger
;
log
->
logger_data
=
data
=
g_slice_new0
(
PurpleLogCommonLoggerData
);
data
->
path
=
g_build_filename
(
path
,
filename
,
NULL
);
list
=
g_list_prepend
(
list
,
log
);
g_date_time_unref
(
stamp
);
}
}
g_dir_close
(
dir
);
g_free
(
path
);
return
list
;
}
int
purple_log_common_total_sizer
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
,
const
char
*
ext
)
{
GDir
*
dir
;
int
size
=
0
;
const
char
*
filename
;
char
*
path
;
if
(
!
account
)
return
0
;
path
=
purple_log_get_log_dir
(
type
,
name
,
account
);
if
(
path
==
NULL
)
return
0
;
if
(
!
(
dir
=
g_dir_open
(
path
,
0
,
NULL
)))
{
g_free
(
path
);
return
0
;
}
while
((
filename
=
g_dir_read_name
(
dir
)))
{
if
(
g_str_has_suffix
(
filename
,
ext
)
&&
strlen
(
filename
)
>=
(
17
+
strlen
(
ext
)))
{
char
*
tmp
=
g_build_filename
(
path
,
filename
,
NULL
);
GStatBuf
st
;
if
(
g_stat
(
tmp
,
&
st
))
{
purple_debug_error
(
"log"
,
"Error stating log file: %s
\n
"
,
tmp
);
g_free
(
tmp
);
continue
;
}
g_free
(
tmp
);
size
+=
st
.
st_size
;
}
}
g_dir_close
(
dir
);
g_free
(
path
);
return
size
;
}
int
purple_log_common_sizer
(
PurpleLog
*
log
)
{
GStatBuf
st
;
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
g_return_val_if_fail
(
data
!=
NULL
,
0
);
if
(
!
data
->
path
||
g_stat
(
data
->
path
,
&
st
))
st
.
st_size
=
0
;
return
st
.
st_size
;
}
/* This will build log sets for all loggers that use the common logger
* functions because they use the same directory structure. */
static
void
log_get_log_sets_common
(
GHashTable
*
sets
)
{
gchar
*
log_path
=
g_build_filename
(
purple_data_dir
(),
"logs"
,
NULL
);
GDir
*
log_dir
=
g_dir_open
(
log_path
,
0
,
NULL
);
const
gchar
*
protocol
;
if
(
log_dir
==
NULL
)
{
g_free
(
log_path
);
return
;
}
while
((
protocol
=
g_dir_read_name
(
log_dir
))
!=
NULL
)
{
gchar
*
protocol_path
=
g_build_filename
(
log_path
,
protocol
,
NULL
);
GDir
*
protocol_dir
;
const
gchar
*
username
;
gchar
*
protocol_unescaped
;
GList
*
account_iter
;
GList
*
accounts
=
NULL
;
if
((
protocol_dir
=
g_dir_open
(
protocol_path
,
0
,
NULL
))
==
NULL
)
{
g_free
(
protocol_path
);
continue
;
}
/* Using g_strdup() to cover the one-in-a-million chance that a
* protocol's list_icon function uses purple_unescape_filename(). */
protocol_unescaped
=
g_strdup
(
purple_unescape_filename
(
protocol
));
/* Find all the accounts for protocol. */
for
(
account_iter
=
purple_accounts_get_all
()
;
account_iter
!=
NULL
;
account_iter
=
account_iter
->
next
)
{
PurpleProtocol
*
protocol
;
protocol
=
purple_protocols_find
(
purple_account_get_protocol_id
((
PurpleAccount
*
)
account_iter
->
data
));
if
(
!
protocol
)
continue
;
if
(
purple_strequal
(
protocol_unescaped
,
purple_protocol_class_list_icon
(
protocol
,
(
PurpleAccount
*
)
account_iter
->
data
,
NULL
)))
accounts
=
g_list_prepend
(
accounts
,
account_iter
->
data
);
}
g_free
(
protocol_unescaped
);
while
((
username
=
g_dir_read_name
(
protocol_dir
))
!=
NULL
)
{
gchar
*
username_path
=
g_build_filename
(
protocol_path
,
username
,
NULL
);
GDir
*
username_dir
;
const
gchar
*
username_unescaped
;
PurpleAccount
*
account
=
NULL
;
gchar
*
name
;
if
((
username_dir
=
g_dir_open
(
username_path
,
0
,
NULL
))
==
NULL
)
{
g_free
(
username_path
);
continue
;
}
/* Find the account for username in the list of accounts for protocol. */
username_unescaped
=
purple_unescape_filename
(
username
);
for
(
account_iter
=
g_list_first
(
accounts
)
;
account_iter
!=
NULL
;
account_iter
=
account_iter
->
next
)
{
if
(
purple_strequal
(
purple_account_get_username
((
PurpleAccount
*
)
account_iter
->
data
),
username_unescaped
))
{
account
=
account_iter
->
data
;
break
;
}
}
/* Don't worry about the cast, name will point to dynamically allocated memory shortly. */
while
((
name
=
(
gchar
*
)
g_dir_read_name
(
username_dir
))
!=
NULL
)
{
size_t
len
;
PurpleLogSet
*
set
;
/* IMPORTANT: Always initialize all members of PurpleLogSet */
set
=
g_slice_new
(
PurpleLogSet
);
/* Unescape the filename. */
name
=
g_strdup
(
purple_unescape_filename
(
name
));
/* Get the (possibly new) length of name. */
len
=
strlen
(
name
);
set
->
type
=
PURPLE_LOG_IM
;
set
->
name
=
name
;
set
->
account
=
account
;
/* set->buddy is always set below */
set
->
normalized_name
=
g_strdup
(
purple_normalize
(
account
,
name
));
/* Check for .chat or .system at the end of the name to determine the type. */
if
(
len
>=
7
)
{
gchar
*
tmp
=
&
name
[
len
-
7
];
if
(
purple_strequal
(
tmp
,
".system"
))
{
set
->
type
=
PURPLE_LOG_SYSTEM
;
*
tmp
=
'\0'
;
}
}
if
(
len
>
5
)
{
gchar
*
tmp
=
&
name
[
len
-
5
];
if
(
purple_strequal
(
tmp
,
".chat"
))
{
set
->
type
=
PURPLE_LOG_CHAT
;
*
tmp
=
'\0'
;
}
}
/* Determine if this (account, name) combination exists as a buddy. */
if
(
account
!=
NULL
&&
*
name
!=
'\0'
)
set
->
buddy
=
(
purple_blist_find_buddy
(
account
,
name
)
!=
NULL
);
else
set
->
buddy
=
FALSE
;
log_add_log_set_to_hash
(
sets
,
set
);
}
g_free
(
username_path
);
g_dir_close
(
username_dir
);
}
g_free
(
protocol_path
);
g_list_free
(
accounts
);
g_dir_close
(
protocol_dir
);
}
g_free
(
log_path
);
g_dir_close
(
log_dir
);
}
gboolean
purple_log_common_deleter
(
PurpleLog
*
log
)
{
PurpleLogCommonLoggerData
*
data
;
int
ret
;
g_return_val_if_fail
(
log
!=
NULL
,
FALSE
);
data
=
log
->
logger_data
;
if
(
data
==
NULL
)
return
FALSE
;
if
(
data
->
path
==
NULL
)
return
FALSE
;
ret
=
g_unlink
(
data
->
path
);
if
(
ret
==
0
)
return
TRUE
;
else
if
(
ret
==
-1
)
{
purple_debug_error
(
"log"
,
"Failed to delete: %s - %s
\n
"
,
data
->
path
,
g_strerror
(
errno
));
}
else
{
/* I'm not sure that g_unlink() will ever return
* something other than 0 or -1. -- rlaager */
purple_debug_error
(
"log"
,
"Failed to delete: %s
\n
"
,
data
->
path
);
}
return
FALSE
;
}
gboolean
purple_log_common_is_deletable
(
PurpleLog
*
log
)
{
PurpleLogCommonLoggerData
*
data
;
#ifndef _WIN32
gchar
*
dirname
;
#endif
g_return_val_if_fail
(
log
!=
NULL
,
FALSE
);
data
=
log
->
logger_data
;
if
(
data
==
NULL
)
return
FALSE
;
if
(
data
->
path
==
NULL
)
return
FALSE
;
#ifndef _WIN32
dirname
=
g_path_get_dirname
(
data
->
path
);
if
(
g_access
(
dirname
,
W_OK
)
==
0
)
{
g_free
(
dirname
);
return
TRUE
;
}
purple_debug_info
(
"log"
,
"access(%s) failed: %s
\n
"
,
dirname
,
g_strerror
(
errno
));
g_free
(
dirname
);
#else
/* Unless and until someone writes equivalent win32 code,
* we'll assume the file is deletable. */
return
TRUE
;
#endif
return
FALSE
;
}
static
char
*
process_txt_log
(
char
*
txt
,
char
*
to_free
)
{
char
*
tmp
;
/* The to_free argument allows us to save a
* g_strdup() in some cases. */
if
(
to_free
==
NULL
)
to_free
=
txt
;
/* g_markup_escape_text requires valid UTF-8 */
if
(
!
g_utf8_validate
(
txt
,
-1
,
NULL
))
{
tmp
=
purple_utf8_salvage
(
txt
);
g_free
(
to_free
);
to_free
=
txt
=
tmp
;
}
tmp
=
g_markup_escape_text
(
txt
,
-1
);
g_free
(
to_free
);
txt
=
purple_markup_linkify
(
tmp
);
g_free
(
tmp
);
return
txt
;
}
/****************************
** HTML LOGGER *************
****************************/
static
gsize
html_logger_write
(
PurpleLog
*
log
,
PurpleMessageFlags
type
,
const
char
*
from
,
GDateTime
*
time
,
const
char
*
message
)
{
char
*
msg_fixed
;
char
*
image_corrected_msg
;
char
*
date
;
char
*
header
;
char
*
escaped_from
;
PurpleProtocol
*
protocol
=
purple_protocols_find
(
purple_account_get_protocol_id
(
log
->
account
));
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
gsize
written
=
0
;
if
(
!
data
)
{
const
char
*
proto
=
purple_protocol_class_list_icon
(
protocol
,
log
->
account
,
NULL
);
GDateTime
*
dt
;
gchar
*
date
;
purple_log_common_writer
(
log
,
".html"
);
data
=
log
->
logger_data
;
/* if we can't write to the file, give up before we hurt ourselves */
if
(
!
data
||
!
data
->
file
)
{
return
0
;
}
dt
=
g_date_time_to_local
(
log
->
time
);
date
=
g_date_time_format
(
dt
,
"%c"
);
g_date_time_unref
(
dt
);
written
+=
fprintf
(
data
->
file
,
"<html><head>"
);
written
+=
fprintf
(
data
->
file
,
"<meta http-equiv=
\"
content-type
\"
content=
\"
text/html; charset=UTF-8
\"
>"
);
written
+=
fprintf
(
data
->
file
,
"<title>"
);
if
(
log
->
type
==
PURPLE_LOG_SYSTEM
)
header
=
g_strdup_printf
(
"System log for account %s (%s) connected at %s"
,
purple_account_get_username
(
log
->
account
),
proto
,
date
);
else
header
=
g_strdup_printf
(
"Conversation with %s at %s on %s (%s)"
,
log
->
name
,
date
,
purple_account_get_username
(
log
->
account
),
proto
);
written
+=
fprintf
(
data
->
file
,
"%s"
,
header
);
written
+=
fprintf
(
data
->
file
,
"</title></head><body>"
);
written
+=
fprintf
(
data
->
file
,
"<h3>%s</h3>
\n
"
,
header
);
g_free
(
date
);
g_free
(
header
);
}
/* if we can't write to the file, give up before we hurt ourselves */
if
(
!
data
->
file
)
return
0
;
escaped_from
=
g_markup_escape_text
(
from
!=
NULL
?
from
:
"<NULL>"
,
-1
);
image_corrected_msg
=
convert_image_tags
(
log
,
message
);
purple_markup_html_to_xhtml
(
image_corrected_msg
,
&
msg_fixed
,
NULL
);
/* Yes, this breaks encapsulation. But it's a static function and
* this saves a needless strdup(). */
if
(
image_corrected_msg
!=
message
)
g_free
(
image_corrected_msg
);
date
=
log_get_timestamp
(
log
,
time
);
if
(
log
->
type
==
PURPLE_LOG_SYSTEM
){
written
+=
fprintf
(
data
->
file
,
"---- %s @ %s ----<br/>
\n
"
,
msg_fixed
,
date
);
}
else
{
if
(
type
&
PURPLE_MESSAGE_SYSTEM
)
written
+=
fprintf
(
data
->
file
,
"<font size=
\"
2
\"
>(%s)</font><b> %s</b><br/>
\n
"
,
date
,
msg_fixed
);
else
if
(
type
&
PURPLE_MESSAGE_RAW
)
written
+=
fprintf
(
data
->
file
,
"<font size=
\"
2
\"
>(%s)</font> %s<br/>
\n
"
,
date
,
msg_fixed
);
else
if
(
type
&
PURPLE_MESSAGE_ERROR
)
written
+=
fprintf
(
data
->
file
,
"<font color=
\"
#FF0000
\"
><font size=
\"
2
\"
>(%s)</font><b> %s</b></font><br/>
\n
"
,
date
,
msg_fixed
);
else
if
(
type
&
PURPLE_MESSAGE_AUTO_RESP
)
{
if
(
type
&
PURPLE_MESSAGE_SEND
)
written
+=
fprintf
(
data
->
file
,
_
(
"<font color=
\"
#16569E
\"
><font size=
\"
2
\"
>(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>
\n
"
),
date
,
escaped_from
,
msg_fixed
);
else
if
(
type
&
PURPLE_MESSAGE_RECV
)
written
+=
fprintf
(
data
->
file
,
_
(
"<font color=
\"
#A82F2F
\"
><font size=
\"
2
\"
>(%s)</font> <b>%s <AUTO-REPLY>:</b></font> %s<br/>
\n
"
),
date
,
escaped_from
,
msg_fixed
);
}
else
if
(
type
&
PURPLE_MESSAGE_RECV
)
{
if
(
purple_message_meify
(
msg_fixed
,
-1
))
written
+=
fprintf
(
data
->
file
,
"<font color=
\"
#062585
\"
><font size=
\"
2
\"
>(%s)</font> <b>***%s</b></font> %s<br/>
\n
"
,
date
,
escaped_from
,
msg_fixed
);
else
written
+=
fprintf
(
data
->
file
,
"<font color=
\"
#A82F2F
\"
><font size=
\"
2
\"
>(%s)</font> <b>%s:</b></font> %s<br/>
\n
"
,
date
,
escaped_from
,
msg_fixed
);
}
else
if
(
type
&
PURPLE_MESSAGE_SEND
)
{
if
(
purple_message_meify
(
msg_fixed
,
-1
))
written
+=
fprintf
(
data
->
file
,
"<font color=
\"
#062585
\"
><font size=
\"
2
\"
>(%s)</font> <b>***%s</b></font> %s<br/>
\n
"
,
date
,
escaped_from
,
msg_fixed
);
else
written
+=
fprintf
(
data
->
file
,
"<font color=
\"
#16569E
\"
><font size=
\"
2
\"
>(%s)</font> <b>%s:</b></font> %s<br/>
\n
"
,
date
,
escaped_from
,
msg_fixed
);
}
else
{
purple_debug_error
(
"log"
,
"Unhandled message type.
\n
"
);
written
+=
fprintf
(
data
->
file
,
"<font size=
\"
2
\"
>(%s)</font><b> %s:</b></font> %s<br/>
\n
"
,
date
,
escaped_from
,
msg_fixed
);
}
}
g_free
(
date
);
g_free
(
msg_fixed
);
g_free
(
escaped_from
);
fflush
(
data
->
file
);
return
written
;
}
static
void
html_logger_finalize
(
PurpleLog
*
log
)
{
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
if
(
data
)
{
if
(
data
->
file
)
{
fprintf
(
data
->
file
,
"</body></html>
\n
"
);
fclose
(
data
->
file
);
}
g_free
(
data
->
path
);
g_slice_free
(
PurpleLogCommonLoggerData
,
data
);
}
}
static
GList
*
html_logger_list
(
PurpleLogType
type
,
const
char
*
sn
,
PurpleAccount
*
account
)
{
return
purple_log_common_lister
(
type
,
sn
,
account
,
".html"
,
html_logger
);
}
static
GList
*
html_logger_list_syslog
(
PurpleAccount
*
account
)
{
return
purple_log_common_lister
(
PURPLE_LOG_SYSTEM
,
".system"
,
account
,
".html"
,
html_logger
);
}
static
char
*
html_logger_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
)
{
char
*
read
;
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
*
flags
=
PURPLE_LOG_READ_NO_NEWLINE
;
if
(
!
data
||
!
data
->
path
)
return
g_strdup
(
_
(
"<font color=
\"
red
\"
><b>Unable to find log path!</b></font>"
));
if
(
g_file_get_contents
(
data
->
path
,
&
read
,
NULL
,
NULL
))
{
char
*
minus_header
=
strchr
(
read
,
'\n'
);
if
(
!
minus_header
)
return
read
;
minus_header
=
g_strdup
(
minus_header
+
1
);
g_free
(
read
);
return
minus_header
;
}
return
g_strdup_printf
(
_
(
"<font color=
\"
red
\"
><b>Could not read file: %s</b></font>"
),
data
->
path
);
}
static
int
html_logger_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
return
purple_log_common_total_sizer
(
type
,
name
,
account
,
".html"
);
}
/****************************
** PLAIN TEXT LOGGER *******
****************************/
static
gsize
txt_logger_write
(
PurpleLog
*
log
,
PurpleMessageFlags
type
,
const
char
*
from
,
GDateTime
*
time
,
const
char
*
message
)
{
char
*
date
;
PurpleProtocol
*
protocol
=
purple_protocols_find
(
purple_account_get_protocol_id
(
log
->
account
));
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
char
*
stripped
=
NULL
;
gsize
written
=
0
;
if
(
data
==
NULL
)
{
/* This log is new. We could use the loggers 'new' function, but
* creating a new file there would result in empty files in the case
* that you open a convo with someone, but don't say anything.
*/
const
char
*
proto
=
purple_protocol_class_list_icon
(
protocol
,
log
->
account
,
NULL
);
GDateTime
*
dt
;
gchar
*
date
;
purple_log_common_writer
(
log
,
".txt"
);
data
=
log
->
logger_data
;
/* if we can't write to the file, give up before we hurt ourselves */
if
(
!
data
||
!
data
->
file
)
return
0
;
dt
=
g_date_time_to_local
(
log
->
time
);
date
=
g_date_time_format
(
dt
,
"%c"
);
if
(
log
->
type
==
PURPLE_LOG_SYSTEM
)
written
+=
fprintf
(
data
->
file
,
"System log for account %s (%s) connected at %s
\n
"
,
purple_account_get_username
(
log
->
account
),
proto
,
date
);
else
written
+=
fprintf
(
data
->
file
,
"Conversation with %s at %s on %s (%s)
\n
"
,
log
->
name
,
date
,
purple_account_get_username
(
log
->
account
),
proto
);
g_free
(
date
);
g_date_time_unref
(
dt
);
}
/* if we can't write to the file, give up before we hurt ourselves */
if
(
!
data
->
file
)
return
0
;
stripped
=
purple_markup_strip_html
(
message
);
date
=
log_get_timestamp
(
log
,
time
);
if
(
log
->
type
==
PURPLE_LOG_SYSTEM
){
written
+=
fprintf
(
data
->
file
,
"---- %s @ %s ----
\n
"
,
stripped
,
date
);
}
else
{
if
(
type
&
PURPLE_MESSAGE_SEND
||
type
&
PURPLE_MESSAGE_RECV
)
{
if
(
type
&
PURPLE_MESSAGE_AUTO_RESP
)
{
written
+=
fprintf
(
data
->
file
,
_
(
"(%s) %s <AUTO-REPLY>: %s
\n
"
),
date
,
from
,
stripped
);
}
else
{
if
(
purple_message_meify
(
stripped
,
-1
))
written
+=
fprintf
(
data
->
file
,
"(%s) ***%s %s
\n
"
,
date
,
from
,
stripped
);
else
written
+=
fprintf
(
data
->
file
,
"(%s) %s: %s
\n
"
,
date
,
from
,
stripped
);
}
}
else
if
(
type
&
PURPLE_MESSAGE_SYSTEM
||
type
&
PURPLE_MESSAGE_ERROR
||
type
&
PURPLE_MESSAGE_RAW
)
written
+=
fprintf
(
data
->
file
,
"(%s) %s
\n
"
,
date
,
stripped
);
else
if
(
type
&
PURPLE_MESSAGE_NO_LOG
)
{
/* This shouldn't happen */
g_free
(
stripped
);
return
written
;
}
else
written
+=
fprintf
(
data
->
file
,
"(%s) %s%s %s
\n
"
,
date
,
from
?
from
:
""
,
from
?
":"
:
""
,
stripped
);
}
g_free
(
date
);
g_free
(
stripped
);
fflush
(
data
->
file
);
return
written
;
}
static
void
txt_logger_finalize
(
PurpleLog
*
log
)
{
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
if
(
data
)
{
if
(
data
->
file
)
fclose
(
data
->
file
);
g_free
(
data
->
path
);
g_slice_free
(
PurpleLogCommonLoggerData
,
data
);
}
}
static
GList
*
txt_logger_list
(
PurpleLogType
type
,
const
char
*
sn
,
PurpleAccount
*
account
)
{
return
purple_log_common_lister
(
type
,
sn
,
account
,
".txt"
,
txt_logger
);
}
static
GList
*
txt_logger_list_syslog
(
PurpleAccount
*
account
)
{
return
purple_log_common_lister
(
PURPLE_LOG_SYSTEM
,
".system"
,
account
,
".txt"
,
txt_logger
);
}
static
char
*
txt_logger_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
)
{
char
*
read
,
*
minus_header
;
PurpleLogCommonLoggerData
*
data
=
log
->
logger_data
;
*
flags
=
0
;
if
(
!
data
||
!
data
->
path
)
return
g_strdup
(
_
(
"<font color=
\"
red
\"
><b>Unable to find log path!</b></font>"
));
if
(
g_file_get_contents
(
data
->
path
,
&
read
,
NULL
,
NULL
))
{
minus_header
=
strchr
(
read
,
'\n'
);
if
(
minus_header
)
return
process_txt_log
(
minus_header
+
1
,
read
);
else
return
process_txt_log
(
read
,
NULL
);
}
return
g_strdup_printf
(
_
(
"<font color=
\"
red
\"
><b>Could not read file: %s</b></font>"
),
data
->
path
);
}
static
int
txt_logger_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
return
purple_log_common_total_sizer
(
type
,
name
,
account
,
".txt"
);
}