Mercurial > pidgin
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 "|"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 |
