pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Move Pidgin/Finch plugins to XDG data directory.
2019-10-08, Elliott Sales de Andrade
e843e8fab70c
Move Pidgin/Finch plugins to XDG data directory.
/* 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
"internal.h"
#include
"account.h"
#include
"debug.h"
#include
"glibcompat.h"
#include
"image-store.h"
#include
"log.h"
#include
"prefs.h"
#include
"util.h"
#include
"stringref.h"
#include
"time.h"
static
GSList
*
loggers
=
NULL
;
static
PurpleLogLogger
*
html_logger
;
static
PurpleLogLogger
*
txt_logger
;
static
PurpleLogLogger
*
old_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
GList
*
old_logger_list
(
PurpleLogType
type
,
const
char
*
sn
,
PurpleAccount
*
account
);
static
int
old_logger_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
);
static
char
*
old_logger_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
);
static
int
old_logger_size
(
PurpleLog
*
log
);
static
void
old_logger_get_log_sets
(
PurpleLogSetCallback
cb
,
GHashTable
*
sets
);
static
void
old_logger_finalize
(
PurpleLog
*
log
);
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
);
old_logger
=
purple_log_logger_new
(
"old"
,
_
(
"Old flat format"
),
9
,
NULL
,
NULL
,
old_logger_finalize
,
old_logger_list
,
old_logger_read
,
old_logger_size
,
old_logger_total_size
,
NULL
,
old_logger_get_log_sets
);
purple_log_logger_add
(
old_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
;
purple_log_logger_remove
(
old_logger
);
purple_log_logger_free
(
old_logger
);
old_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
;
purple_build_dir
(
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
(
purple_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
(
purple_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"
);
}
/****************
* OLD LOGGER ***
****************/
/* The old logger doesn't write logs, only reads them. This is to include
* old logs in the log viewer transparently.
*/
struct
old_logger_data
{
PurpleStringref
*
pathref
;
int
offset
;
int
length
;
};
static
GList
*
old_logger_list
(
PurpleLogType
type
,
const
char
*
sn
,
PurpleAccount
*
account
)
{
char
*
logfile
=
g_strdup_printf
(
"%s.log"
,
purple_normalize
(
account
,
sn
));
char
*
pathstr
=
g_build_filename
(
purple_data_dir
(),
"logs"
,
logfile
,
NULL
);
PurpleStringref
*
pathref
=
purple_stringref_new
(
pathstr
);
GStatBuf
st
;
time_t
log_last_modified
;
FILE
*
index
;
FILE
*
file
;
int
file_fd
,
index_fd
;
char
*
index_tmp
;
char
buf
[
BUF_LONG
];
gint
year
,
month
,
day
,
hour
,
minute
,
second
;
char
month_str
[
4
];
struct
old_logger_data
*
data
=
NULL
;
int
logfound
=
0
;
int
lastoff
=
0
;
int
newlen
;
GDateTime
*
lasttime
=
NULL
;
PurpleLog
*
log
=
NULL
;
GList
*
list
=
NULL
;
g_free
(
logfile
);
file_fd
=
g_open
(
purple_stringref_value
(
pathref
),
0
,
O_RDONLY
);
if
(
file_fd
==
-1
||
(
file
=
fdopen
(
file_fd
,
"rb"
))
==
NULL
)
{
purple_debug_error
(
"log"
,
"Failed to open log file
\"
%s
\"
for reading: %s
\n
"
,
purple_stringref_value
(
pathref
),
g_strerror
(
errno
));
purple_stringref_unref
(
pathref
);
g_free
(
pathstr
);
return
NULL
;
}
if
(
_purple_fstat
(
file_fd
,
&
st
)
==
-1
)
{
purple_stringref_unref
(
pathref
);
g_free
(
pathstr
);
fclose
(
file
);
return
NULL
;
}
else
log_last_modified
=
st
.
st_mtime
;
/* Change the .log extension to .idx */
strcpy
(
pathstr
+
strlen
(
pathstr
)
-
3
,
"idx"
);
index_fd
=
g_open
(
pathstr
,
0
,
O_RDONLY
);
if
(
index_fd
!=
-1
)
{
if
(
_purple_fstat
(
index_fd
,
&
st
)
!=
0
)
{
close
(
index_fd
);
index_fd
=
-1
;
}
}
if
(
index_fd
!=
-1
)
{
if
(
st
.
st_mtime
<
log_last_modified
)
{
purple_debug_warning
(
"log"
,
"Index
\"
%s
\"
exists, but is older than the log.
\n
"
,
pathstr
);
close
(
index_fd
);
}
else
{
/* The index file exists and is at least as new as the log, so open it. */
if
(
!
(
index
=
fdopen
(
index_fd
,
"rb"
)))
{
purple_debug_error
(
"log"
,
"Failed to open index file
\"
%s
\"
for reading: %s
\n
"
,
pathstr
,
g_strerror
(
errno
));
/* Fall through so that we'll parse the log file. */
}
else
{
purple_debug_info
(
"log"
,
"Using index: %s
\n
"
,
pathstr
);
g_free
(
pathstr
);
while
(
fgets
(
buf
,
BUF_LONG
,
index
))
{
unsigned
long
idx_time
;
if
(
sscanf
(
buf
,
"%d
\t
%d
\t
%lu"
,
&
lastoff
,
&
newlen
,
&
idx_time
)
==
3
)
{
log
=
purple_log_new
(
PURPLE_LOG_IM
,
sn
,
account
,
NULL
,
NULL
);
log
->
logger
=
old_logger
;
log
->
time
=
g_date_time_new_from_unix_local
(
idx_time
);
/* IMPORTANT: Always set all members of struct old_logger_data */
data
=
g_slice_new
(
struct
old_logger_data
);
data
->
pathref
=
purple_stringref_ref
(
pathref
);
data
->
offset
=
lastoff
;
data
->
length
=
newlen
;
log
->
logger_data
=
data
;
list
=
g_list_prepend
(
list
,
log
);
}
}
fclose
(
index
);
purple_stringref_unref
(
pathref
);
fclose
(
file
);
return
list
;
}
}
}
index_tmp
=
g_strdup_printf
(
"%s.XXXXXX"
,
pathstr
);
if
((
index_fd
=
g_mkstemp
(
index_tmp
))
==
-1
)
{
purple_debug_error
(
"log"
,
"Failed to open index temp file: %s
\n
"
,
g_strerror
(
errno
));
g_free
(
pathstr
);
g_free
(
index_tmp
);
index
=
NULL
;
}
else
{
if
((
index
=
fdopen
(
index_fd
,
"wb"
))
==
NULL
)
{
purple_debug_error
(
"log"
,
"Failed to fdopen() index temp file: %s
\n
"
,
g_strerror
(
errno
));
close
(
index_fd
);
if
(
index_tmp
!=
NULL
)
{
g_unlink
(
index_tmp
);
g_free
(
index_tmp
);
}
g_free
(
pathstr
);
}
}
while
(
fgets
(
buf
,
BUF_LONG
,
file
))
{
if
(
strstr
(
buf
,
"---- New C"
)
!=
NULL
)
{
int
length
;
int
offset
;
char
convostart
[
32
];
char
*
temp
=
strchr
(
buf
,
'@'
);
if
(
temp
==
NULL
||
strlen
(
temp
)
<
2
)
continue
;
temp
++
;
length
=
strcspn
(
temp
,
"-"
);
if
(
length
>
31
)
length
=
31
;
offset
=
ftell
(
file
);
if
(
logfound
)
{
newlen
=
offset
-
lastoff
-
length
;
if
(
strstr
(
buf
,
"----</H3><BR>"
))
{
newlen
-=
sizeof
(
"<HR><BR><H3 Align=Center> ---- New Conversation @ "
)
+
sizeof
(
"----</H3><BR>"
)
-
2
;
}
else
{
newlen
-=
sizeof
(
"---- New Conversation @ "
)
+
sizeof
(
"----"
)
-
2
;
}
if
(
strchr
(
buf
,
'\r'
))
newlen
--
;
if
(
newlen
!=
0
)
{
log
=
purple_log_new
(
PURPLE_LOG_IM
,
sn
,
account
,
NULL
,
lasttime
);
log
->
logger
=
old_logger
;
/* IMPORTANT: Always set all members of struct old_logger_data */
data
=
g_slice_new
(
struct
old_logger_data
);
data
->
pathref
=
purple_stringref_ref
(
pathref
);
data
->
offset
=
lastoff
;
data
->
length
=
newlen
;
log
->
logger_data
=
data
;
list
=
g_list_prepend
(
list
,
log
);
if
(
index
!=
NULL
)
fprintf
(
index
,
"%d
\t
%d
\t
%lu
\n
"
,
data
->
offset
,
data
->
length
,
(
unsigned
long
)
g_date_time_to_unix
(
log
->
time
));
}
}
logfound
=
1
;
lastoff
=
offset
;
g_snprintf
(
convostart
,
length
,
"%s"
,
temp
);
year
=
month
=
day
=
hour
=
minute
=
second
=
0
;
if
(
sscanf
(
convostart
,
"%*s %3s %d %d:%d:%d %d"
,
month_str
,
&
day
,
&
hour
,
&
minute
,
&
second
,
&
year
)
!=
6
)
{
purple_debug_warning
(
"log"
,
"invalid date format
\n
"
);
}
/* Ugly hack, in case current locale is not English */
if
(
purple_strequal
(
month_str
,
"Jan"
))
{
month
=
1
;
}
else
if
(
purple_strequal
(
month_str
,
"Feb"
))
{
month
=
2
;
}
else
if
(
purple_strequal
(
month_str
,
"Mar"
))
{
month
=
3
;
}
else
if
(
purple_strequal
(
month_str
,
"Apr"
))
{
month
=
4
;
}
else
if
(
purple_strequal
(
month_str
,
"May"
))
{
month
=
5
;
}
else
if
(
purple_strequal
(
month_str
,
"Jun"
))
{
month
=
6
;
}
else
if
(
purple_strequal
(
month_str
,
"Jul"
))
{
month
=
7
;
}
else
if
(
purple_strequal
(
month_str
,
"Aug"
))
{
month
=
8
;
}
else
if
(
purple_strequal
(
month_str
,
"Sep"
))
{
month
=
9
;
}
else
if
(
purple_strequal
(
month_str
,
"Oct"
))
{
month
=
10
;
}
else
if
(
purple_strequal
(
month_str
,
"Nov"
))
{
month
=
11
;
}
else
if
(
purple_strequal
(
month_str
,
"Dec"
))
{
month
=
12
;
}
if
(
lasttime
)
g_date_time_unref
(
lasttime
);
lasttime
=
g_date_time_new_local
(
year
,
month
,
day
,
hour
,
minute
,
second
);
}
}
if
(
logfound
)
{
if
((
newlen
=
ftell
(
file
)
-
lastoff
)
!=
0
)
{
log
=
purple_log_new
(
PURPLE_LOG_IM
,
sn
,
account
,
NULL
,
lasttime
);
log
->
logger
=
old_logger
;
/* IMPORTANT: Always set all members of struct old_logger_data */
data
=
g_slice_new
(
struct
old_logger_data
);
data
->
pathref
=
purple_stringref_ref
(
pathref
);
data
->
offset
=
lastoff
;
data
->
length
=
newlen
;
log
->
logger_data
=
data
;
list
=
g_list_prepend
(
list
,
log
);
if
(
index
!=
NULL
)
fprintf
(
index
,
"%d
\t
%d
\t
%lu
\n
"
,
data
->
offset
,
data
->
length
,
(
unsigned
long
)
log
->
time
);
}
}
if
(
lasttime
)
g_date_time_unref
(
lasttime
);
purple_stringref_unref
(
pathref
);
fclose
(
file
);
if
(
index
!=
NULL
)
{
fclose
(
index
);
if
(
index_tmp
==
NULL
)
{
g_free
(
pathstr
);
g_return_val_if_reached
(
list
);
}
if
(
g_rename
(
index_tmp
,
pathstr
))
{
purple_debug_warning
(
"log"
,
"Failed to rename index temp file
\"
%s
\"
to
\"
%s
\"
: %s
\n
"
,
index_tmp
,
pathstr
,
g_strerror
(
errno
));
g_unlink
(
index_tmp
);
}
else
purple_debug_info
(
"log"
,
"Built index: %s
\n
"
,
pathstr
);
g_free
(
index_tmp
);
g_free
(
pathstr
);
}
return
list
;
}
static
int
old_logger_total_size
(
PurpleLogType
type
,
const
char
*
name
,
PurpleAccount
*
account
)
{
char
*
logfile
=
g_strdup_printf
(
"%s.log"
,
purple_normalize
(
account
,
name
));
char
*
pathstr
=
g_build_filename
(
purple_data_dir
(),
"logs"
,
logfile
,
NULL
);
int
size
;
GStatBuf
st
;
if
(
g_stat
(
pathstr
,
&
st
))
size
=
0
;
else
size
=
st
.
st_size
;
g_free
(
logfile
);
g_free
(
pathstr
);
return
size
;
}
static
char
*
old_logger_read
(
PurpleLog
*
log
,
PurpleLogReadFlags
*
flags
)
{
size_t
result
;
struct
old_logger_data
*
data
=
log
->
logger_data
;
const
char
*
path
=
purple_stringref_value
(
data
->
pathref
);
FILE
*
file
=
g_fopen
(
path
,
"rb"
);
char
*
read
;
g_return_val_if_fail
(
file
,
g_strdup
(
""
));
read
=
g_malloc
(
data
->
length
+
1
);
if
(
fseek
(
file
,
data
->
offset
,
SEEK_SET
)
!=
0
)
result
=
0
;
else
result
=
fread
(
read
,
data
->
length
,
1
,
file
);
if
(
result
!=
1
)
purple_debug_error
(
"log"
,
"Unable to read from log file: %s
\n
"
,
path
);
fclose
(
file
);
read
[
data
->
length
]
=
'\0'
;
*
flags
=
0
;
if
(
strstr
(
read
,
"<BR>"
))
{
*
flags
|=
PURPLE_LOG_READ_NO_NEWLINE
;
return
read
;
}
return
process_txt_log
(
read
,
NULL
);
}
static
int
old_logger_size
(
PurpleLog
*
log
)
{
struct
old_logger_data
*
data
=
log
->
logger_data
;
return
data
?
data
->
length
:
0
;
}
static
void
old_logger_get_log_sets
(
PurpleLogSetCallback
cb
,
GHashTable
*
sets
)
{
char
*
log_path
=
g_build_filename
(
purple_data_dir
(),
"logs"
,
NULL
);
GDir
*
log_dir
=
g_dir_open
(
log_path
,
0
,
NULL
);
gchar
*
name
;
PurpleBlistNode
*
gnode
,
*
cnode
,
*
bnode
;
g_free
(
log_path
);
if
(
log_dir
==
NULL
)
return
;
/* Don't worry about the cast, name will be filled with a dynamically allocated data shortly. */
while
((
name
=
(
gchar
*
)
g_dir_read_name
(
log_dir
))
!=
NULL
)
{
size_t
len
;
gchar
*
ext
;
PurpleLogSet
*
set
;
gboolean
found
=
FALSE
;
/* Unescape the filename. */
name
=
g_strdup
(
purple_unescape_filename
(
name
));
/* Get the (possibly new) length of name. */
len
=
strlen
(
name
);
if
(
len
<
5
)
{
g_free
(
name
);
continue
;
}
/* Make sure we're dealing with a log file. */
ext
=
&
name
[
len
-
4
];
if
(
!
purple_strequal
(
ext
,
".log"
))
{
g_free
(
name
);
continue
;
}
/* IMPORTANT: Always set all members of PurpleLogSet */
set
=
g_slice_new
(
PurpleLogSet
);
/* Chat for .chat at the end of the name to determine the type. */
*
ext
=
'\0'
;
set
->
type
=
PURPLE_LOG_IM
;
if
(
len
>
9
)
{
char
*
tmp
=
&
name
[
len
-
9
];
if
(
purple_strequal
(
tmp
,
".chat"
))
{
set
->
type
=
PURPLE_LOG_CHAT
;
*
tmp
=
'\0'
;
}
}
set
->
name
=
set
->
normalized_name
=
name
;
/* Search the buddy list to find the account and to determine if this is a buddy. */
for
(
gnode
=
purple_blist_get_default_root
();
!
found
&&
gnode
!=
NULL
;
gnode
=
purple_blist_node_get_sibling_next
(
gnode
))
{
if
(
!
PURPLE_IS_GROUP
(
gnode
))
continue
;
for
(
cnode
=
purple_blist_node_get_first_child
(
gnode
);
!
found
&&
cnode
!=
NULL
;
cnode
=
purple_blist_node_get_sibling_next
(
cnode
))
{
if
(
!
PURPLE_IS_CONTACT
(
cnode
))
continue
;
for
(
bnode
=
purple_blist_node_get_first_child
(
cnode
);
!
found
&&
bnode
!=
NULL
;
bnode
=
purple_blist_node_get_sibling_next
(
bnode
))
{
PurpleBuddy
*
buddy
=
(
PurpleBuddy
*
)
bnode
;
if
(
purple_strequal
(
purple_buddy_get_name
(
buddy
),
name
))
{
set
->
account
=
purple_buddy_get_account
(
buddy
);
set
->
buddy
=
TRUE
;
found
=
TRUE
;
}
}
}
}
if
(
!
found
)
{
set
->
account
=
NULL
;
set
->
buddy
=
FALSE
;
}
cb
(
sets
,
set
);
}
g_dir_close
(
log_dir
);
}
static
void
old_logger_finalize
(
PurpleLog
*
log
)
{
struct
old_logger_data
*
data
=
log
->
logger_data
;
purple_stringref_unref
(
data
->
pathref
);
g_slice_free
(
struct
old_logger_data
,
data
);
}