view autoprofile/comp_rss.c @ 1028:314cfd774bc4

s/purple.guifications.org/plugins.guifications.org/
author Paul Aurich <paul@darkrain42.org>
date Thu, 06 Aug 2009 12:30:12 -0700
parents 658d25bf4a3b
children 888daeb5b79d
line wrap: on
line source

/*--------------------------------------------------------------------------*
 * AUTOPROFILE                                                              *
 *                                                                          *
 * A Purple away message and profile manager that supports dynamic text       *
 *                                                                          *
 * AutoProfile is the legal property of its developers.  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
 *--------------------------------------------------------------------------*/

#include "comp_rss.h"
#include "autoprofile.h"

#include "gtkimhtml.h"

#include <ctype.h>

static GtkWidget *entry_username = NULL;
static GtkWidget *entry_url = NULL;

GHashTable *rss_entries = NULL;
static GHashTable *rss_timeouts = NULL;
GStaticMutex rss_mutex = G_STATIC_MUTEX_INIT;

/* Core functions */
static char *get_rss_data (struct widget *w, const char *field, int index, 
  struct tm **time) 
{
  GList *tmp;
  const struct rss_entry *e;
  char *ret;
  int max;

  g_static_mutex_lock (&rss_mutex);
  tmp = (GList *) g_hash_table_lookup (rss_entries, w);

  if (index < 0 ) {
    g_static_mutex_unlock (&rss_mutex);
    return strdup (_("[ERROR: Invalid entry number]")); 
  }

  if (tmp == NULL) {
    g_static_mutex_unlock (&rss_mutex);
    return strdup (_("[ERROR: No data, invalid URL/account?]"));
  }

  if (index != 0) {
    while (index-- != 1) {
      tmp = tmp->next;
      if (tmp == NULL) {
        g_static_mutex_unlock (&rss_mutex);
        return strdup (_("[ERROR: Insufficient number of entries]"));
      }
    }
  }
 
  e = (struct rss_entry *) tmp->data;

  if (!strcmp (field, "link")) {
    if (e->url)
      ret = strdup (e->url);
    else
      ret = NULL;
  } else if (!strcmp (field, "title")) {
    if (e->title)
      ret = strdup (e->title);
    else
      ret = NULL;
  } else if (!strcmp (field, "entry")) {
    if (e->entry) {
      max = ap_prefs_get_int (w, "entry_limit");
      ret = strdup (e->entry);
      if (max < g_utf8_strlen (ret, -1)) {
        gchar *tmp = g_utf8_offset_to_pointer (ret, max);    
        *tmp = '\0';
      } 
    } else {
      ret = NULL;
    }
  } else if (!strcmp (field, "time")) {
    *time = e->t;
    ret = NULL;
  } else {
    ret = NULL;
  }

  g_static_mutex_unlock (&rss_mutex);
  return ret;   
}

static char *rss_generate (struct widget *w)
{
  GString *output;
  gchar *result;

  char *tmp, *time_tmp;
  int state;
  int count;
  const char *format;
  char fmt_char [3];
  struct tm *time;
  
  fmt_char[0] = '%';
  fmt_char[2] = '\0';
  
  format = ap_prefs_get_string (w, "format");
  output = g_string_new ("");
  time_tmp = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM);
  
  state = 0;
  count = 0;

  while (*format) {
    if (state == 1) {
      if (isdigit (*format)) {
        count = (count * 10) + (int) *format - 48;
        format++;
      } else {
        switch (*format) {
          case 'H':
          case 'I':
          case 'p':
          case 'M':
          case 'S':
          case 'a':
          case 'A':
          case 'b':
          case 'B':
          case 'm':
          case 'd':
          case 'j':
          case 'W':
          case 'w':
          case 'y':
          case 'Y':
          case 'z':
            time = NULL;
            tmp = get_rss_data (w, "time", count, &time);
            if (time) {
              fmt_char[1] = *format;
              strftime (time_tmp, AP_SIZE_MAXIMUM, fmt_char, time);
              g_string_append_printf (output, "%s", time_tmp);
            } 
            break;
          case 'l':
            tmp = get_rss_data (w, "link", count, NULL);
            if (tmp) {
              g_string_append_printf (output, "%s", tmp);
              free (tmp);
            } 
            break;
          case 't':
            tmp = get_rss_data (w, "title", count, NULL);
            if (tmp) {
              g_string_append_printf (output, "%s", tmp);
              free (tmp);
            }
            break;
          case 'e':
            tmp = get_rss_data (w, "entry", count, NULL);
            if (tmp) {
              g_string_append_printf (output, "%s", tmp);
              free (tmp);
            }
            break;
          case '%':
            g_string_append_printf (output, "%c", *format);
            break;
          default:
            g_string_append_unichar (output, g_utf8_get_char (format));
            break;
        }
        format = g_utf8_next_char (format);
        state = 0;
      }
    } else {
      if (*format == '%') {
        state = 1;
        count = 0;
      } else {
        g_string_append_unichar (output, g_utf8_get_char (format));
      }
      format = g_utf8_next_char (format);
    }
  }

  result = output->str;
  g_string_free (output, FALSE);
  return result;
}

static gboolean rss_update (gpointer data)
{
  parse_rss ((struct widget *) data);
  return TRUE;
}

static void rss_load (struct widget *w)
{
  gpointer rss_timeout;

  g_static_mutex_lock (&rss_mutex);
  if (!rss_entries) {
    rss_entries = g_hash_table_new (NULL, NULL);
  }
  if (!rss_timeouts) {
    rss_timeouts = g_hash_table_new (NULL, NULL);
  }

  rss_timeout = GINT_TO_POINTER (g_timeout_add (
    ap_prefs_get_int (w, "update_rate") * 60 * 1000,
    rss_update, w));
  g_hash_table_insert (rss_timeouts, w, rss_timeout);

  g_static_mutex_unlock (&rss_mutex);

  rss_update (w);
}

static void rss_unload (struct widget *w)
{
  gpointer rss_timeout;

  g_static_mutex_lock (&rss_mutex);
  rss_timeout = g_hash_table_lookup (rss_timeouts, w);
  g_source_remove (GPOINTER_TO_INT (rss_timeout));
  g_hash_table_remove (rss_timeouts, w);

  g_static_mutex_unlock (&rss_mutex);
}

static void rss_init (struct widget *w)
{
  ap_prefs_add_int (w, "type", RSS_XANGA);
  ap_prefs_add_string (w, "location", "");
  ap_prefs_add_string (w, "username", "");
  ap_prefs_add_string (w, "format", 
    "My <a href=\"%l\">blog</a> was most recently updated on "
    "%1B %1d at %I:%M %p");
  ap_prefs_add_int (w, "update_rate", 5);
  ap_prefs_add_int (w, "entry_limit", 1000);
}

/* GUI functions */
static gboolean update_refresh_rate (GtkWidget *widget, GdkEventFocus *evt, 
                              struct widget *w)
{
  gpointer timeout;
  int minutes;

  minutes = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
  ap_prefs_set_int (w, "update_rate", minutes);
   
  // Kill the current timer and run a new one
  g_static_mutex_lock (&rss_mutex);
  timeout = g_hash_table_lookup (rss_timeouts, w);
  g_source_remove (GPOINTER_TO_INT(timeout));
  timeout = GINT_TO_POINTER (g_timeout_add (minutes * 60 * 1000,
    rss_update, w));
  g_hash_table_replace (rss_timeouts, w, timeout);
  g_static_mutex_unlock (&rss_mutex);

  return FALSE;
}

static void rss_data_update (GtkWidget *widget, struct widget *w)
{
  rss_update (w);
}

static void sensitivity_cb (const char *name, PurplePrefType type,
  gconstpointer val, gpointer data) 
{
  int real_val = GPOINTER_TO_INT (val);

  if (real_val == RSS_XANGA || real_val == RSS_LIVEJOURNAL) {
    gtk_widget_set_sensitive (entry_username, TRUE);
    gtk_widget_set_sensitive (entry_url, FALSE);
  } else {
    gtk_widget_set_sensitive (entry_username, FALSE);
    gtk_widget_set_sensitive (entry_url, TRUE);
  }
}

static GtkWidget *entry;

static void event_cb (GtkWidget *widget, struct widget *w)
{
  ap_prefs_set_string (w, "format", 
                  gtk_imhtml_get_markup (GTK_IMHTML(entry)));
}
static void formatting_toggle_cb (GtkIMHtml *imhtml, 
                GtkIMHtmlButtons buttons, struct widget *w)
{
  ap_prefs_set_string (w, "format", 
                  gtk_imhtml_get_markup (GTK_IMHTML(entry)));

}

static void formatting_clear_cb (GtkIMHtml *imhtml,
               struct widget *w)
{
  ap_prefs_set_string (w, "format", 
                  gtk_imhtml_get_markup (GTK_IMHTML(entry)));
}

static GtkWidget *rss_menu (struct widget *w)
{
  GtkWidget *ret;
  GList *options;
  GtkWidget *label, *hbox, *button, *spinner, *sw;
  GtkWidget *entry_window, *toolbar;
  GtkTextBuffer *text_buffer;
  
  int value;
  gchar *pref;

  ret = gtk_vbox_new (FALSE, 5);

  /* Format string */
  label = gtk_label_new (NULL);
  gtk_label_set_markup (GTK_LABEL(label), "<b>Format string for output</b>");
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0);

  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX(ret), hbox, TRUE, TRUE, 0);
  
  entry_window = pidgin_create_imhtml (TRUE, &entry, &toolbar, &sw);
  gtk_box_pack_start (GTK_BOX (hbox), entry_window, TRUE, TRUE, 0);
  gtk_imhtml_append_text_with_images (GTK_IMHTML(entry),
                  ap_prefs_get_string (w, "format"),
                  0, NULL);
  text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
  g_signal_connect (G_OBJECT (text_buffer), "changed",
                    G_CALLBACK (event_cb), w);
  g_signal_connect_after(G_OBJECT(entry), "format_function_toggle",
           G_CALLBACK(formatting_toggle_cb), w);
  g_signal_connect_after(G_OBJECT(entry), "format_function_clear",
           G_CALLBACK(formatting_clear_cb), w); 

  label = gtk_label_new (_(
    "The following options can be specified with a numerical modifier\n"
    "(i.e. \"%e\" can also be written \"%1e\" or \"%2e\").  The optional\n"
    "number specifies which entry to get the data for. \"1\" refers to the\n"
    "most recent entry, \"2\" refers to the second-most recent entry, and so\n"
    "forth.  \"1\" is used if no number is specified.\n\n"
    "%e\tStarting text of the entry.\n"
    "%l\tLink to the specific entry.\n"
    "%t\tTitle of entry (Xanga incompatible)\n"

    "\nTime of entry:\n"
    "%H\thour of entry(24-hour clock)\n"
    "%I\thour (12-hour clock)\n"
    "%p\tAM or PM\n"
    "%M\tminute\n"
    "%S\tsecond\n"
    "%a\tabbreviated weekday name\n"
    "%A\tfull weekday name\n"
    "%b\tabbreviated month name\n"
    "%B\tfull month name\n"
    "%m\tmonth (numerical)\n"
    "%d\tday of the month\n"
    "%j\tday of the year\n"
    "%W\tweek number of the year\n"
    "%w\tweekday (numerical)\n"
    "%y\tyear without century\n"
    "%Y\tyear with century\n"
    "%z\ttime zone name, if any\n"
    "%%\t%"));

  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
  sw = gtk_scrolled_window_new (NULL,NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
    GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
  gtk_box_pack_start (GTK_BOX (hbox), sw, TRUE, TRUE , 0);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label);

  /* Type/URL/Username selection */
  label = gtk_label_new (NULL);
  gtk_label_set_markup (GTK_LABEL(label), "<b>RSS/Blog location</b>");
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0);

  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0);

  /* Dropdown */
  options = g_list_append (NULL, (char *) _("Xanga"));
  options = g_list_append (options, GINT_TO_POINTER(RSS_XANGA));
  options = g_list_append (options, (char *) _("LiveJournal"));
  options = g_list_append (options, GINT_TO_POINTER(RSS_LIVEJOURNAL));
  options = g_list_append (options, (char *) _("RSS 2.0"));
  options = g_list_append (options, GINT_TO_POINTER(RSS_2));
  ap_prefs_dropdown_from_list (w, hbox, NULL, PURPLE_PREF_INT, "type", options);
  g_list_free (options);
  
  pref = ap_prefs_get_pref_name (w, "type");
  purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    sensitivity_cb, w);
  free (pref);

  /* Username/URL fields */
  entry_username = ap_prefs_labeled_entry (w, hbox, _("Username:"),
    "username", NULL);
  entry_url = ap_prefs_labeled_entry (w, hbox, _("URL of feed:"),
    "location", NULL);

  value = ap_prefs_get_int (w, "type");
  if (value == RSS_XANGA || value == RSS_LIVEJOURNAL) {
    gtk_widget_set_sensitive (entry_username, TRUE);
    gtk_widget_set_sensitive (entry_url, FALSE);
  } else {
    gtk_widget_set_sensitive (entry_username, FALSE);
    gtk_widget_set_sensitive (entry_url, TRUE);
  }

  /* Other options */
  label = gtk_label_new (NULL);
  gtk_label_set_markup (GTK_LABEL(label), "<b>Other options</b>");
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0);

  /* # of chars to display from description */
  ap_prefs_labeled_spin_button (w, ret, 
    "Max characters to show in entry (%e)", "entry_limit", 1,
    AP_SIZE_MAXIMUM - 1, NULL);

  /* Update rate selection */
  hbox = gtk_hbox_new (FALSE, 5);
  gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);

  label = gtk_label_new (_("Minutes between checks for updates:"));
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

  spinner = gtk_spin_button_new_with_range (1, 60, 1);
  gtk_box_pack_start (GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    ap_prefs_get_int (w, "update_rate"));
  g_signal_connect (G_OBJECT (spinner), "value-changed",
                    G_CALLBACK (update_refresh_rate), w);

  button = gtk_button_new_with_label ("Fetch data now!");
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (rss_data_update), w);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);

  return ret;
}

/* Le end */
struct component rss =
{
  N_("RSS / Blogs"),
  N_("Information taken from an RSS feed (Xanga and LiveJournal capable)"),
  "RSS",
  &rss_generate,
  &rss_init,
  &rss_load,
  &rss_unload,
  NULL,
  &rss_menu
};