sync with sylpheed 0.6.5cvs11
[claws.git] / src / utils.c
index 0f75c413a79667a7c4323a61df94ddc5245947d6..54b57108b1cbf51221cbd255bc75399c433005d1 100644 (file)
@@ -135,27 +135,16 @@ gchar *itos(gint n)
 
 gchar *to_human_readable(off_t size)
 {
-       static gchar str[9];
-       gint count;
-       guint32 div = 1;
-
-       for (count = 0; count < 3; count++) {
-               if (size / div < 1024)
-                       break;
-               else
-                       div *= 1024;
-       }
-
-       switch (count) {
-       case 0: g_snprintf(str, sizeof(str), "%dB",    (gint)size);   break;
-       case 1: g_snprintf(str, sizeof(str), "%.1fKB", (gfloat)size / div);
-               break;
-       case 2: g_snprintf(str, sizeof(str), "%.1fMB", (gfloat)size / div);
-               break;
-       default:
-               g_snprintf(str, sizeof(str), "%.1fGB", (gfloat)size / div);
-               break;
-       }
+       static gchar str[10];
+
+       if (size < 1024)
+               g_snprintf(str, sizeof(str), "%dB", (gint)size);
+       else if (size >> 10 < 1024)
+               g_snprintf(str, sizeof(str), "%.1fKB", (gfloat)size / (1 << 10));
+       else if (size >> 20 < 1024)
+               g_snprintf(str, sizeof(str), "%.2fMB", (gfloat)size / (1 << 20));
+       else
+               g_snprintf(str, sizeof(str), "%.2fGB", (gfloat)size / (1 << 30));
 
        return str;
 }
@@ -168,7 +157,14 @@ gint strcmp2(const gchar *s1, const gchar *s2)
        else
                return strcmp(s1, s2);
 }
-
+/* strstr with NULL-checking */
+gchar *strstr2(const gchar *s1, const gchar *s2)
+{
+       if (s1 == NULL || s2 == NULL)
+               return NULL;
+       else
+               return strstr(s1, s2);
+}
 /* compare paths */
 gint path_cmp(const gchar *s1, const gchar *s2)
 {
@@ -215,6 +211,21 @@ gchar *strtailchomp(gchar *str, gchar tail_char)
        return str;
 }
 
+/* remove CR (carriage return) */
+gchar *strcrchomp(gchar *str)
+{
+       register gchar *s;
+
+       if (!*str) return str;
+
+       s = str + strlen(str) - 1;
+       if (*s == '\n' && s > str && *(s - 1) == '\r') {
+               *(s - 1) = '\n';
+               *s = '\0';
+       }
+
+       return str;
+}
 
 /* Similar to `strstr' but this function ignores the case of both strings.  */
 gchar *strcasestr(const gchar *haystack, const gchar *needle)
@@ -462,7 +473,7 @@ gboolean is_next_mbs(const wchar_t *s)
 {
        gint mbl;
        const wchar_t *wp;
-       gchar tmp[MB_CUR_MAX];
+       gchar tmp[MB_LEN_MAX];
 
        /* skip head space */
        for (wp = s; *wp != (wchar_t)0 && iswspace(*wp); wp++)
@@ -493,30 +504,20 @@ wchar_t *find_wspace(const wchar_t *s)
 /* compare subjects */
 gint subject_compare(const gchar *s1, const gchar *s2)
 {
-       gint retval;
        gchar *str1, *str2;
 
        if (!s1 || !s2) return -1;
        if (!*s1 || !*s2) return -1;
 
-       Xalloca(str1, strlen(s1) + 1, return -1);
-       Xalloca(str2, strlen(s2) + 1, return -1);
-       strcpy(str1, s1);
-       strcpy(str2, s2);
+       Xstrdup_a(str1, s1, return -1);
+       Xstrdup_a(str2, s2, return -1);
 
        trim_subject(str1);
        trim_subject(str2);
 
        if (!*str1 || !*str2) return -1;
 
-       retval = strcmp(str1, str2);
-       /*
-       if (retval == 0)
-               g_print("\ns1 = %s\ns2 = %s\n"
-                       "str1 = %s\nstr2 = %s\nmatched.\n",
-                       s1, s2, str1, str2);
-       */
-       return retval;
+       return strcmp(str1, str2);
 }
 
 void trim_subject(gchar *str)
@@ -668,7 +669,12 @@ void extract_quote(gchar *str, gchar quote_chr)
        register gchar *p;
 
        if ((str = strchr(str, quote_chr))) {
-               if ((p = strchr(str + 1, quote_chr))) {
+               p = str;
+               while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
+                       memmove(p - 1, p, strlen(p) + 1);
+                       p--;
+               }
+               if(p) {
                        *p = '\0';
                        memmove(str, str + 1, p - str);
                }
@@ -852,6 +858,31 @@ GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
        return group_list;
 }
 
+GList *add_history(GList *list, const gchar *str)
+{
+       GList *old;
+
+       g_return_val_if_fail(str != NULL, list);
+
+       old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
+       if (old) {
+               g_free(old->data);
+               list = g_list_remove(list, old->data);
+       } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
+               GList *last;
+
+               last = g_list_last(list);
+               if (last) {
+                       g_free(last->data);
+                       g_list_remove(list, last->data);
+               }
+       }
+
+       list = g_list_prepend(list, g_strdup(str));
+
+       return list;
+}
+
 void remove_return(gchar *str)
 {
        register gchar *p = str;
@@ -937,88 +968,45 @@ gboolean is_ascii_str(const guchar *str)
 
 gint get_quote_level(const gchar *str)
 {
-       size_t firstquotepos;
-       size_t lastquotepos = -1;
+       const gchar *first_pos;
+       const gchar *last_pos;
        const gchar *p = str;
-       const gchar *pos;
-       gint quotelevel = -1;
-       gint i = 0;
+       gint quote_level = -1;
 
        /* speed up line processing by only searching to the last '>' */
-       if ((pos = strchr(str, '>')) != NULL) {
-               firstquotepos = pos - str;
-               lastquotepos = strrchr(str, '>') - str + 1;
-
+       if ((first_pos = strchr(str, '>')) != NULL) {
                /* skip a line if it contains a '<' before the initial '>' */
-               if (memchr(str, '<', pos - str) != NULL)
+               if (memchr(str, '<', first_pos - str) != NULL)
                        return -1;
+               last_pos = strrchr(first_pos, '>');
        } else
                return -1;
 
-       while (i < lastquotepos) {
-               while (i < lastquotepos) {
-                       if (isspace(*p) || (*p == '\t')) {
+       while (p <= last_pos) {
+               while (p < last_pos) {
+                       if (isspace(*p))
                                p++;
-                               i++;
-                       } else
+                       else
                                break;
                }
-               if (i >= lastquotepos)
-                       break;
 
                if (*p == '>')
-                       quotelevel++;
-               else if ((*p != '-') && !isspace(*p) && (i < lastquotepos)) {
+                       quote_level++;
+               else if (*p != '-' && !isspace(*p) && p <= last_pos) {
                        /* any characters are allowed except '-' and space */
-                       while ((*p != '-') && (*p != '>') && !isspace(*p) &&
-                              (i < lastquotepos)) {
+                       while (*p != '-' && *p != '>' && !isspace(*p) &&
+                              p < last_pos)
                                p++;
-                               i++;
-                       }
                        if (*p == '>')
-                               quotelevel++;
-                       else if ((i >= lastquotepos) || isspace(*p))
+                               quote_level++;
+                       else
                                break;
                }
 
                p++;
-               i++;
        }
 
-       return quotelevel;
-}
-
-GList *uri_list_extract_filenames(const gchar *uri_list)
-{
-       GList *result = NULL;
-       const gchar *p, *q;
-       gchar *file;
-
-       p = uri_list;
-
-       while (p) {
-               if (*p != '#') {
-                       while (isspace(*p)) p++;
-                       if (!strncmp(p, "file:", 5)) {
-                               p += 5;
-                               q = p;
-                               while (*q && *q != '\n' && *q != '\r') q++;
-
-                               if (q > p) {
-                                       q--;
-                                       while (q > p && isspace(*q)) q--;
-                                       file = g_malloc(q - p + 2);
-                                       strncpy(file, p, q - p + 1);
-                                       file[q - p + 1] = '\0';
-                                       result = g_list_append(result,file);
-                               }
-                       }
-               }
-               p = strchr(p, '\n');
-               if (p) p++;
-       }
-
-       return result;
+       return quote_level;
 }
 
 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
@@ -1109,10 +1097,58 @@ gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
        return str_array;
 }
 
+GList *uri_list_extract_filenames(const gchar *uri_list)
+{
+       GList *result = NULL;
+       const gchar *p, *q;
+       gchar *file;
+
+       p = uri_list;
+
+       while (p) {
+               if (*p != '#') {
+                       while (isspace(*p)) p++;
+                       if (!strncmp(p, "file:", 5)) {
+                               p += 5;
+                               q = p;
+                               while (*q && *q != '\n' && *q != '\r') q++;
+
+                               if (q > p) {
+                                       q--;
+                                       while (q > p && isspace(*q)) q--;
+                                       file = g_malloc(q - p + 2);
+                                       strncpy(file, p, q - p + 1);
+                                       file[q - p + 1] = '\0';
+                                       result = g_list_append(result,file);
+                               }
+                       }
+               }
+               p = strchr(p, '\n');
+               if (p) p++;
+       }
+
+       return result;
+}
+
+#define HEX_TO_INT(val, hex) \
+{ \
+       gchar c = hex; \
+ \
+       if ('0' <= c && c <= '9') { \
+               val = c - '0'; \
+       } else if ('a' <= c && c <= 'f') { \
+               val = c - 'a' + 10; \
+       } else if ('A' <= c && c <= 'F') { \
+               val = c - 'A' + 10; \
+       } else { \
+               val = 0; \
+       } \
+}
+
 /*
  * We need this wrapper around g_get_home_dir(), so that
  * we can fix some Windoze things here.  Should be done in glibc of course
- * but as long as we are not able to do our own extensions to glibc, we do 
+ * but as long as we are not able to do our own extensions to glibc, we do
  * it here.
  */
 gchar *get_home_dir(void)
@@ -1195,6 +1231,17 @@ gchar *get_mime_tmp_dir(void)
        return mime_tmp_dir;
 }
 
+gchar *get_template_dir(void)
+{
+       static gchar *template_dir = NULL;
+
+       if (!template_dir)
+               template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                                          TEMPLATE_DIR, NULL);
+
+       return template_dir;
+}
+
 gchar *get_tmp_file(void)
 {
        static gchar *tmp_file = NULL;
@@ -1441,7 +1488,7 @@ gint remove_dir_recursive(const gchar *dir)
        struct dirent *d;
        gchar *prev_dir;
 
-       /*g_print("dir = %s\n", dir);*/
+       /* g_print("dir = %s\n", dir); */
 
        if (stat(dir, &s) < 0) {
                FILE_OP_ERROR(dir, "stat");
@@ -1459,7 +1506,7 @@ gint remove_dir_recursive(const gchar *dir)
        }
 
        prev_dir = g_get_current_dir();
-       /*g_print("prev_dir = %s\n", prev_dir);*/
+       /* g_print("prev_dir = %s\n", prev_dir); */
 
        if (!path_cmp(prev_dir, dir)) {
                g_free(prev_dir);
@@ -1494,7 +1541,7 @@ gint remove_dir_recursive(const gchar *dir)
                        continue;
                }
 
-               /*g_print("removing %s\n", d->d_name);*/
+               /* g_print("removing %s\n", d->d_name); */
 
                if (S_ISDIR(s.st_mode)) {
                        if (remove_dir_recursive(d->d_name) < 0) {
@@ -1785,7 +1832,28 @@ gint execute_async(gchar *const argv[])
        return 0;
 }
 
-gint execute_command_line(const gchar *cmdline)
+gint execute_sync(gchar *const argv[])
+{
+       pid_t pid;
+
+       if ((pid = fork()) < 0) {
+               perror("fork");
+               return -1;
+       }
+
+       if (pid == 0) {         /* child process */
+               execvp(argv[0], argv);
+
+               perror("execvp");
+               _exit(1);
+       }
+
+       waitpid(pid, NULL, 0);
+
+       return 0;
+}
+
+gint execute_command_line(const gchar *cmdline, gboolean async)
 {
        gchar **argv;
        gint i;
@@ -1807,32 +1875,147 @@ gint execute_command_line(const gchar *cmdline)
                }
        }
 
-       ret = execute_async(argv);
+       if (async)
+               ret = execute_async(argv);
+       else
+               ret = execute_sync(argv);
        g_strfreev(argv);
 
        return ret;
 }
 
+static gint is_unchanged_uri_char(char c)
+{
+       switch (c) {
+               case '(':
+               case ')':
+               case ',':
+                       return 0;
+               default:
+                       return 1;
+       }
+}
+
+void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
+{
+       int i;
+       int k;
+
+       k = 0;
+       for(i = 0; i < strlen(uri) ; i++) {
+               if (is_unchanged_uri_char(uri[i])) {
+                       if (k + 2 >= bufsize)
+                               break;
+                       encoded_uri[k++] = uri[i];
+               }
+               else {
+                       char * hexa = "0123456789ABCDEF";
+                       
+                       if (k + 4 >= bufsize)
+                               break;
+                       encoded_uri[k++] = '%';
+                       encoded_uri[k++] = hexa[uri[i] / 16];
+                       encoded_uri[k++] = hexa[uri[i] % 16];
+               }
+       }
+       encoded_uri[k] = 0;
+}
+
+/* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped 
+ * characters
+ */
+static gint axtoi(const gchar *hexstr)
+{
+       gint hi, lo, result;
+       
+       hi = hexstr[0];
+       if ('0' <= hi && hi <= '9') {
+               hi -= '0';
+       } else
+               if ('a' <= hi && hi <= 'f') {
+                       hi -= ('a' - 10);
+               } else
+                       if ('A' <= hi && hi <= 'F') {
+                               hi -= ('A' - 10);
+                       }
+
+       lo = hexstr[1];
+       if ('0' <= lo && lo <= '9') {
+               lo -= '0';
+       } else
+               if ('a' <= lo && lo <= 'f') {
+                       lo -= ('a'-10);
+               } else
+                       if ('A' <= lo && lo <= 'F') {
+                               lo -= ('A' - 10);
+                       }
+       result = lo + (16 * hi);
+       return result;
+}
+
+
+/* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
+ * plusses, and escape characters are used)
+ */
+
+void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
+{
+       const gchar *encoded;
+       gchar *decoded;
+
+       encoded = encoded_uri;
+       decoded = decoded_uri;
+
+       while (*encoded) {
+               if (*encoded == '%') {
+                       encoded++;
+                       if (isxdigit(encoded[0])
+                           && isxdigit(encoded[1])) {
+                               *decoded = (gchar) axtoi(encoded);
+                               decoded++;
+                               encoded += 2;
+                       }
+               }
+               else if (*encoded == '+') {
+                       *decoded = ' ';
+                       decoded++;
+                       encoded++;
+               }
+               else {
+                       *decoded = *encoded;
+                       decoded++;
+                       encoded++;
+               }
+       }
+
+       *decoded = '\0';
+}
+
+
 gint open_uri(const gchar *uri, const gchar *cmdline)
 {
        static gchar *default_cmdline = "netscape -remote openURL(%s,raise)";
        gchar buf[BUFFSIZE];
        gchar *p;
-
+       gchar encoded_uri[BUFFSIZE];
+       
        g_return_val_if_fail(uri != NULL, -1);
 
+       /* an option to choose whether to use encode_uri or not ? */
+       encode_uri(encoded_uri, BUFFSIZE, uri);
+       
        if (cmdline &&
            (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
            !strchr(p + 2, '%'))
-               g_snprintf(buf, sizeof(buf), cmdline, uri);
+               g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
        else {
                if (cmdline)
                        g_warning(_("Open URI command line is invalid: `%s'"),
                                  cmdline);
-               g_snprintf(buf, sizeof(buf), default_cmdline, uri);
+               g_snprintf(buf, sizeof(buf), default_cmdline, encoded_uri);
        }
-
-       execute_command_line(buf);
+       
+       execute_command_line(buf, TRUE);
 
        return 0;
 }
@@ -1844,16 +2027,16 @@ time_t remote_tzoffset_sec(const gchar *zone)
        gchar *p;
        gchar c;
        gint iustz;
-       gint h, m;
+       gint offset;
        time_t remoteoffset;
 
        strncpy(zone3, zone, 3);
        zone3[3] = '\0';
        remoteoffset = 0;
 
-       if (sscanf(zone, "%c%2d%2d", &c, &h, &m) == 3 &&
+       if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
            (c == '+' || c == '-')) {
-               remoteoffset = ((h * 60) + m) * 60;
+               remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
                if (c == '-')
                        remoteoffset = -remoteoffset;
        } else if (!strncmp(zone, "UT" , 2) ||
@@ -1999,7 +2182,17 @@ void close_log_file(void)
        }
 }
 
-void debug_print(const gchar *format, ...)
+static guint log_verbosity_count = 0;
+
+void log_verbosity_set(gboolean verbose)
+{
+       if (verbose)
+               log_verbosity_count++;
+       else if (log_verbosity_count > 0)
+               log_verbosity_count--;
+}
+
+void debug_print_real(const gchar *format, ...)
 {
        va_list args;
        gchar buf[BUFFSIZE];
@@ -2017,18 +2210,27 @@ void log_print(const gchar *format, ...)
 {
        va_list args;
        gchar buf[BUFFSIZE];
+       gchar *logbuf;
+       gchar timestr[6];
+       time_t t;
 
        va_start(args, format);
        g_vsnprintf(buf, sizeof(buf), format, args);
        va_end(args);
+       
+       time(&t);
+       strftime(timestr, 6, "%H:%M", localtime(&t));
+       logbuf = g_strdup_printf("[%s] %s", timestr, buf);
 
-       if (debug_mode) fputs(buf, stdout);
-       log_window_append(buf, LOG_NORMAL);
+       if (debug_mode) fputs(logbuf, stdout);
+       log_window_append(logbuf, LOG_NORMAL);
        if (log_fp) {
-               fputs(buf, log_fp);
+               fputs(logbuf, log_fp);
                fflush(log_fp);
        }
-       statusbar_puts_all(buf);
+       if (log_verbosity_count)
+               statusbar_puts_all(buf);
+       g_free(logbuf);
 }
 
 void log_message(const gchar *format, ...)
@@ -2047,6 +2249,7 @@ void log_message(const gchar *format, ...)
                fputs(buf, log_fp);
                fflush(log_fp);
        }
+       statusbar_puts_all(buf);
 }
 
 void log_warning(const gchar *format, ...)
@@ -2084,3 +2287,53 @@ void log_error(const gchar *format, ...)
                fflush(log_fp);
        }
 }
+
+
+void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
+{
+       if (subject == NULL)
+               subject = "";
+
+       if (g_strncasecmp(subject, "Re: ", 4) == 0)
+               return g_hash_table_lookup(subject_table, subject + 4);
+       else
+               return g_hash_table_lookup(subject_table, subject);
+}
+
+void subject_table_insert(GHashTable *subject_table, gchar * subject,
+                         void * data)
+{
+       if (subject == NULL)
+               return;
+       if (* subject == 0)
+               return;
+       if (g_strcasecmp(subject, "Re:") == 0)
+               return;
+       if (g_strcasecmp(subject, "Re: ") == 0)
+               return;
+
+       if (g_strncasecmp(subject, "Re: ", 4) == 0)
+               g_hash_table_insert(subject_table, subject + 4, data);
+       else
+               g_hash_table_insert(subject_table, subject, data);
+}
+
+void subject_table_remove(GHashTable *subject_table, gchar * subject)
+{
+       if (subject == NULL)
+               return;
+
+       if (g_strncasecmp(subject, "Re: ", 4) == 0)
+               g_hash_table_remove(subject_table, subject + 4);
+       else
+               g_hash_table_remove(subject_table, subject);
+}
+
+gboolean subject_is_reply(const gchar *subject)
+{
+       /* XXX: just simply here so someone can handle really
+        * advanced Re: detection like "Re[4]", "ANTW:" or
+        * Re: Re: Re: Re: Re: Re: Re: Re:" stuff. */
+       if (subject == NULL) return FALSE;
+       else return 0 == g_strncasecmp(subject, "Re: ", 4);
+}