comparison libpurple/plugins/log_reader.c @ 20897:8de7c44fd577

Patch from QuLogic to add support for aMSN logs from the log-reader plugin. This is really awesome! Closes #3497.
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Fri, 12 Oct 2007 11:51:13 +0000
parents 49fcee9835aa
children 70082d0db571
comparison
equal deleted inserted replaced
20896:ce9579a6bcdd 20897:8de7c44fd577
2096 2096
2097 g_free(data->path); 2097 g_free(data->path);
2098 g_free(data); 2098 g_free(data);
2099 } 2099 }
2100 2100
2101 /*************************************************************************
2102 * aMSN Logger *
2103 *************************************************************************/
2104
2105 /* The aMSN logger doesn't write logs, only reads them. This is to include
2106 * aMSN logs in the log viewer transparently.
2107 */
2108
2109 static PurpleLogLogger *amsn_logger;
2110
2111 struct amsn_logger_data {
2112 char *path;
2113 int offset;
2114 int length;
2115 };
2116
2117 #define AMSN_LOG_CONV_START "|\"LRED[Conversation started on "
2118 #define AMSN_LOG_CONV_END "|\"LRED[You have closed the window on "
2119 #define AMSN_LOG_CONV_EXTRA "01 Aug 2001 00:00:00]"
2120
2121 /* `log_dir`/username@hotmail.com/logs/buddyname@hotmail.com.log */
2122 /* `log_dir`/username@hotmail.com/logs/Month Year/buddyname@hotmail.com.log */
2123 static GList *amsn_logger_list(PurpleLogType type, const char *sn, PurpleAccount *account)
2124 {
2125 GList *list = NULL;
2126 struct amsn_logger_data *data;
2127 const char *logdir;
2128 char *username;
2129 char *log_path;
2130 char *buddy_log;
2131 char *filename;
2132 GDir *dir;
2133 const char *name;
2134 GError *error;
2135 char *contents;
2136 PurpleLog *log;
2137 GList *files = NULL;
2138 GList *f;
2139
2140 logdir = purple_prefs_get_string("/plugins/core/log_reader/amsn/log_directory");
2141
2142 /* By clearing the log directory path, this logger can be (effectively) disabled. */
2143 if (!logdir || !*logdir)
2144 return NULL;
2145
2146 /* aMSN only works with MSN/WLM */
2147 if (strcmp(account->protocol_id, "prpl-msn"))
2148 return NULL;
2149
2150 username = g_strdup(purple_normalize(account, account->username));
2151 buddy_log = g_strdup_printf("%s.log", purple_normalize(account, sn));
2152 log_path = g_build_filename(logdir, username, "logs", NULL);
2153
2154 /* First check in the top-level */
2155 filename = g_build_filename(log_path, buddy_log, NULL);
2156 if (g_file_test(filename, G_FILE_TEST_EXISTS))
2157 files = g_list_prepend(files, filename);
2158 else
2159 g_free(filename);
2160
2161 /* Check in previous months */
2162 dir = g_dir_open(log_path, 0, NULL);
2163 if (dir) {
2164 while ((name = g_dir_read_name(dir)) != NULL) {
2165 filename = g_build_filename(log_path, name, buddy_log, NULL);
2166 if (g_file_test(filename, G_FILE_TEST_EXISTS))
2167 files = g_list_prepend(files, filename);
2168 else
2169 g_free(filename);
2170 }
2171 g_dir_close(dir);
2172 }
2173
2174 g_free(log_path);
2175
2176 /* New versions use 'friendlier' directory names */
2177 purple_util_chrreplace(username, '@', '_');
2178 purple_util_chrreplace(username, '.', '_');
2179
2180 log_path = g_build_filename(logdir, username, "logs", NULL);
2181
2182 /* First check in the top-level */
2183 filename = g_build_filename(log_path, buddy_log, NULL);
2184 if (g_file_test(filename, G_FILE_TEST_EXISTS))
2185 files = g_list_prepend(files, filename);
2186 else
2187 g_free(filename);
2188
2189 /* Check in previous months */
2190 dir = g_dir_open(log_path, 0, NULL);
2191 if (dir) {
2192 while ((name = g_dir_read_name(dir)) != NULL) {
2193 filename = g_build_filename(log_path, name, buddy_log, NULL);
2194 if (g_file_test(filename, G_FILE_TEST_EXISTS))
2195 files = g_list_prepend(files, filename);
2196 else
2197 g_free(filename);
2198 }
2199 g_dir_close(dir);
2200 }
2201
2202 g_free(log_path);
2203 g_free(username);
2204 g_free(buddy_log);
2205
2206 /* Loop through files looking for logs */
2207 for(f = g_list_first(files); f; f = g_list_next(f)) {
2208 filename = f->data;
2209 purple_debug_info("aMSN logger", "Reading %s\n", filename);
2210 error = NULL;
2211 if (!g_file_get_contents(filename, &contents, NULL, &error)) {
2212 purple_debug_error("aMSN logger",
2213 "Couldn't read file %s: %s \n", filename,
2214 (error && error->message) ?
2215 error->message : "Unknown error");
2216 if (error)
2217 g_error_free(error);
2218 } else {
2219 char *c = contents;
2220 gboolean found_start = FALSE;
2221 char *start_log = c;
2222 int offset = 0;
2223 struct tm tm;
2224 while (c && *c) {
2225 if (purple_str_has_prefix(c, AMSN_LOG_CONV_START)) {
2226 char month[4];
2227 if (sscanf(c + strlen(AMSN_LOG_CONV_START),
2228 "%u %3s %u %u:%u:%u",
2229 &tm.tm_mday, (char*)&month, &tm.tm_year,
2230 &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
2231 found_start = FALSE;
2232 purple_debug_error("aMSN logger",
2233 "Error parsing start date for %s\n",
2234 filename);
2235 } else {
2236 const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2237 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
2238 tm.tm_year -= 1900;
2239
2240 /* Let the C library deal with
2241 * daylight savings time.
2242 */
2243 tm.tm_isdst = -1;
2244
2245 /* Ugly hack, in case current locale
2246 * is not English. This code is taken
2247 * from log.c.
2248 */
2249 for (tm.tm_mon = 0; months[tm.tm_mon]; tm.tm_mon++) {
2250 if (strcmp(month, months[tm.tm_mon]) == 0)
2251 break;
2252 }
2253 found_start = TRUE;
2254 offset = c - contents;
2255 start_log = c;
2256 }
2257 } else if (purple_str_has_prefix(c, AMSN_LOG_CONV_END) && found_start) {
2258 data = g_new0(struct amsn_logger_data, 1);
2259 data->path = g_strdup(filename);
2260 data->offset = offset;
2261 data->length = c - start_log
2262 + strlen(AMSN_LOG_CONV_END)
2263 + strlen(AMSN_LOG_CONV_EXTRA);
2264 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
2265 log->logger = amsn_logger;
2266 log->logger_data = data;
2267 list = g_list_prepend(list, log);
2268 found_start = FALSE;
2269
2270 purple_debug_info("aMSN logger",
2271 "Found log for %s:"
2272 " path = (%s),"
2273 " offset = (%d),"
2274 " length = (%d)\n",
2275 sn, data->path, data->offset, data->length);
2276 }
2277 c = strstr(c, "\n");
2278 c++;
2279 }
2280
2281 /* I've seen the file end without the AMSN_LOG_CONV_END bit */
2282 if (found_start) {
2283 data = g_new0(struct amsn_logger_data, 1);
2284 data->path = g_strdup(filename);
2285 data->offset = offset;
2286 data->length = c - start_log
2287 + strlen(AMSN_LOG_CONV_END)
2288 + strlen(AMSN_LOG_CONV_EXTRA);
2289 log = purple_log_new(PURPLE_LOG_IM, sn, account, NULL, mktime(&tm), NULL);
2290 log->logger = amsn_logger;
2291 log->logger_data = data;
2292 list = g_list_prepend(list, log);
2293 found_start = FALSE;
2294
2295 purple_debug_info("aMSN logger",
2296 "Found log for %s:"
2297 " path = (%s),"
2298 " offset = (%d),"
2299 " length = (%d)\n",
2300 sn, data->path, data->offset, data->length);
2301 }
2302 g_free(contents);
2303 }
2304 g_free(filename);
2305 }
2306
2307 g_list_free(files);
2308
2309 return list;
2310 }
2311
2312 /* Really it's |"L, but the string's been escaped */
2313 #define AMSN_LOG_FORMAT_TAG "|&quot;L"
2314
2315 static char *amsn_logger_read(PurpleLog *log, PurpleLogReadFlags *flags)
2316 {
2317 struct amsn_logger_data *data;
2318 FILE *file;
2319 char *contents;
2320 char *escaped;
2321 GString *formatted;
2322 char *start;
2323 gboolean in_span = FALSE;
2324
2325 if (flags != NULL)
2326 *flags = PURPLE_LOG_READ_NO_NEWLINE;
2327
2328 g_return_val_if_fail(log != NULL, g_strdup(""));
2329
2330 data = log->logger_data;
2331
2332 g_return_val_if_fail(data->path != NULL, g_strdup(""));
2333 g_return_val_if_fail(data->length > 0, g_strdup(""));
2334
2335 contents = g_malloc(data->length + 2);
2336
2337 file = g_fopen(data->path, "rb");
2338 g_return_val_if_fail(file != NULL, g_strdup(""));
2339
2340 fseek(file, data->offset, SEEK_SET);
2341 fread(contents, data->length, 1, file);
2342 fclose(file);
2343
2344 contents[data->length] = '\n';
2345 contents[data->length + 1] = '\0';
2346
2347 escaped = g_markup_escape_text(contents, -1);
2348 g_free(contents);
2349 contents = escaped;
2350
2351 formatted = g_string_sized_new(data->length + 2);
2352
2353 start = contents;
2354 while (start && *start) {
2355 char *end;
2356 char *old_tag;
2357 char *tag;
2358 end = strstr(start, "\n");
2359 if (!end)
2360 break;
2361 *end = '\0';
2362 if (purple_str_has_prefix(start, AMSN_LOG_FORMAT_TAG) && in_span) {
2363 /* New format for this line */
2364 g_string_append(formatted, "</span><br>");
2365 in_span = FALSE;
2366 } else if (start != contents) {
2367 /* Continue format from previous line */
2368 g_string_append(formatted, "<br>");
2369 }
2370 old_tag = start;
2371 tag = strstr(start, AMSN_LOG_FORMAT_TAG);
2372 while (tag) {
2373 g_string_append_len(formatted, old_tag, tag - old_tag);
2374 tag += strlen(AMSN_LOG_FORMAT_TAG);
2375 if (in_span) {
2376 g_string_append(formatted, "</span>");
2377 in_span = FALSE;
2378 }
2379 if (*tag == 'C') {
2380 /* |"LCxxxxxx is a hex colour */
2381 char colour[7];
2382 strncpy(colour, tag + 1, 6);
2383 colour[6] = '\0';
2384 g_string_append_printf(formatted, "<span style=\"color: #%s;\">", colour);
2385 /* This doesn't appear to work? */
2386 /* g_string_append_printf(formatted, "<span style=\"color: #%6s;\">", tag + 1); */
2387 in_span = TRUE;
2388 old_tag = tag + 7; /* C + xxxxxx */
2389 } else {
2390 /* |"Lxxx is a 3-digit colour code */
2391 if (purple_str_has_prefix(tag, "RED")) {
2392 g_string_append(formatted, "<span style=\"color: red;\">");
2393 in_span = TRUE;
2394 } else if (purple_str_has_prefix(tag, "GRA")) {
2395 g_string_append(formatted, "<span style=\"color: gray;\">");
2396 in_span = TRUE;
2397 } else if (purple_str_has_prefix(tag, "NOR")) {
2398 g_string_append(formatted, "<span style=\"color: black;\">");
2399 in_span = TRUE;
2400 } else if (purple_str_has_prefix(tag, "ITA")) {
2401 g_string_append(formatted, "<span style=\"color: blue;\">");
2402 in_span = TRUE;
2403 } else if (purple_str_has_prefix(tag, "GRE")) {
2404 g_string_append(formatted, "<span style=\"color: darkgreen;\">");
2405 in_span = TRUE;
2406 } else {
2407 purple_debug_info("aMSN logger", "Unknown colour format: %3s\n", tag);
2408 }
2409 old_tag = tag + 3;
2410 }
2411 tag = strstr(tag, AMSN_LOG_FORMAT_TAG);
2412 }
2413 g_string_append(formatted, old_tag);
2414 start = end + 1;
2415 }
2416 if (in_span)
2417 g_string_append(formatted, "</span>");
2418
2419 g_free(contents);
2420
2421 return g_string_free(formatted, FALSE);
2422 }
2423
2424 static int amsn_logger_size(PurpleLog *log)
2425 {
2426 struct amsn_logger_data *data;
2427 char *text;
2428 int size;
2429
2430 g_return_val_if_fail(log != NULL, 0);
2431
2432 data = log->logger_data;
2433
2434 if (purple_prefs_get_bool("/plugins/core/log_reader/fast_sizes")) {
2435 return data ? data->length : 0;
2436 }
2437
2438 text = amsn_logger_read(log, NULL);
2439 size = strlen(text);
2440 g_free(text);
2441
2442 return size;
2443 }
2444
2445 static void amsn_logger_finalize(PurpleLog *log)
2446 {
2447 struct amsn_logger_data *data;
2448
2449 g_return_if_fail(log != NULL);
2450
2451 data = log->logger_data;
2452 g_free(data->path);
2453 g_free(data);
2454 }
2455
2101 /***************************************************************************** 2456 /*****************************************************************************
2102 * Plugin Code * 2457 * Plugin Code *
2103 *****************************************************************************/ 2458 *****************************************************************************/
2104 2459
2105 static void 2460 static void
2345 path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT, 2700 path = g_build_filename(PURPLE_LOG_READER_WINDOWS_MOUNT_POINT,
2346 "Program Files", "QIP", "Users", NULL); 2701 "Program Files", "QIP", "Users", NULL);
2347 #endif 2702 #endif
2348 purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path ? path : ""); 2703 purple_prefs_add_string("/plugins/core/log_reader/qip/log_directory", path ? path : "");
2349 g_free(path); 2704 g_free(path);
2705
2706 /* Add aMSN Messenger log directory preference. */
2707 purple_prefs_add_none("/plugins/core/log_reader/amsn");
2708
2709 /* Calculate default aMSN log directory. */
2710 #ifdef _WIN32
2711 folder = wpurple_get_special_folder(CSIDL_PROFILE); /* Silly aMSN, not using CSIDL_APPDATA */
2712 path = g_build_filename(folder, "amsn", NULL);
2713 #else
2714 path = g_build_filename(purple_home_dir(), ".amsn", NULL);
2715 #endif
2716 purple_prefs_add_string("/plugins/core/log_reader/amsn/log_directory", path);
2717 g_free(path);
2350 } 2718 }
2351 2719
2352 static gboolean 2720 static gboolean
2353 plugin_load(PurplePlugin *plugin) 2721 plugin_load(PurplePlugin *plugin)
2354 { 2722 {
2427 trillian_logger_list, 2795 trillian_logger_list,
2428 trillian_logger_read, 2796 trillian_logger_read,
2429 trillian_logger_size); 2797 trillian_logger_size);
2430 purple_log_logger_add(trillian_logger); 2798 purple_log_logger_add(trillian_logger);
2431 2799
2800 /* The names of IM clients are marked for translation at the request of
2801 translators who wanted to transliterate them. Many translators
2802 choose to leave them alone. Choose what's best for your language. */
2803 amsn_logger = purple_log_logger_new("amsn", _("aMSN"), 6,
2804 NULL,
2805 NULL,
2806 amsn_logger_finalize,
2807 amsn_logger_list,
2808 amsn_logger_read,
2809 amsn_logger_size);
2810 purple_log_logger_add(amsn_logger);
2811
2432 return TRUE; 2812 return TRUE;
2433 } 2813 }
2434 2814
2435 static gboolean 2815 static gboolean
2436 plugin_unload(PurplePlugin *plugin) 2816 plugin_unload(PurplePlugin *plugin)
2443 purple_log_logger_remove(messenger_plus_logger); 2823 purple_log_logger_remove(messenger_plus_logger);
2444 #endif 2824 #endif
2445 purple_log_logger_remove(msn_logger); 2825 purple_log_logger_remove(msn_logger);
2446 purple_log_logger_remove(trillian_logger); 2826 purple_log_logger_remove(trillian_logger);
2447 purple_log_logger_remove(qip_logger); 2827 purple_log_logger_remove(qip_logger);
2828 purple_log_logger_remove(amsn_logger);
2448 2829
2449 return TRUE; 2830 return TRUE;
2450 } 2831 }
2451 2832
2452 static PurplePluginPrefFrame * 2833 static PurplePluginPrefFrame *
2501 "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger")); 2882 "/plugins/core/log_reader/msn/log_directory", _("MSN Messenger"));
2502 purple_plugin_pref_frame_add(frame, ppref); 2883 purple_plugin_pref_frame_add(frame, ppref);
2503 2884
2504 ppref = purple_plugin_pref_new_with_name_and_label( 2885 ppref = purple_plugin_pref_new_with_name_and_label(
2505 "/plugins/core/log_reader/trillian/log_directory", _("Trillian")); 2886 "/plugins/core/log_reader/trillian/log_directory", _("Trillian"));
2887 purple_plugin_pref_frame_add(frame, ppref);
2888
2889 ppref = purple_plugin_pref_new_with_name_and_label(
2890 "/plugins/core/log_reader/amsn/log_directory", _("aMSN"));
2506 purple_plugin_pref_frame_add(frame, ppref); 2891 purple_plugin_pref_frame_add(frame, ppref);
2507 2892
2508 return frame; 2893 return frame;
2509 } 2894 }
2510 2895