gmrun/src/history.c

366 lines
8.8 KiB
C

/*
* This is free and unencumbered software released into the public domain.
*
* For more information, please refer to <https://unlicense.org>
*/
/*
* - Generic implementation of HistoryFile
* - Loads history from file. Saves history to file.
* - Keeps track of the last entry, making it a bit more efficient
* - Only adding entries is supported ...
* - Supports maximum number of entries,
* The first entry is removed if the new entry exceeds the max count.
* - Does not allow duplicate strings
* This is not a good idea if the list is too long
* - If entry exists it's moved to the end of the list..
*
*/
#include "history.h"
// ============================================================
// PRIVATE
// ============================================================
struct _Whistory
{
long int index;
unsigned int count;
unsigned int max;
char * filename;
int has_changed;
int save_if_changed;
GList * list;
GList * list_end;
GList * current;
};
static void _history_clear (HistoryFile * history)
{
// keep history->filename (destroyed in _history_free())
if (history->list)
{
//g_list_free_full (history->list, free);
GList * i;
for (i = history->list; i; i = i->next) {
free (i->data);
}
g_list_free (history->list);
history->list = NULL;
history->has_changed = 1;
}
history->index = 0;
history->count = 0;
}
static void _history_free (HistoryFile * history)
{
_history_clear (history);
if (history->filename) {
free (history->filename);
history->filename = NULL;
}
free (history);
}
/// load entries from file and initialize private variables
static void _history_load_from_file (HistoryFile * history, const char * filename)
{
FILE *fp;
char buf[1024];
char * p, * item;
size_t len;
unsigned int count = 0;
unsigned int max = history->max;
GList * out_list = NULL;
fp = fopen (filename, "r");
if (!fp) {
return;
}
/* Read file line by line */
while (fgets (buf, sizeof (buf), fp))
{
p = buf;
while (*p && *p <= 0x20) { // 32 = space [ignore spaces]
p++;
}
if (!*p) {
continue;
}
item = strdup (p);
len = strlen (buf);
item[len-1] = 0;
if (max > 0 && count >= max) {
free (item);
break;
}
count++;
out_list = g_list_prepend (out_list, (gpointer) item);
}
if (out_list) {
history->list_end = out_list;
history->list = out_list;
if (out_list->next) {
history->list = g_list_reverse (out_list);
}
history->index = 1;
}
history->count = count;
history->current = history->list; // current = 1st item
fclose (fp);
return;
}
void _history_write_to_file (HistoryFile * history, const char * filename)
{
FILE *fp;
fp = fopen (filename, "w");
if (!fp) {
return;
}
GList * i;
for (i = history->list; i; i = i->next)
{
fprintf (fp, "%s\n", (char *) (i->data));
}
fclose (fp);
return;
}
// ============================================================
// PUBLIC
// ============================================================
HistoryFile * history_new (char * filename, unsigned int maxcount)
{
HistoryFile * history = calloc (1, sizeof (HistoryFile));
history->max = maxcount;
if (filename && *filename) {
history->filename = strdup (filename);
_history_load_from_file (history, filename);
}
return (history);
}
void history_save (HistoryFile * history, int save_if_changed)
{
if (history && history->filename) {
if (save_if_changed) {
if (history->has_changed) {
_history_write_to_file (history, history->filename);
} // else printf ("not saving!!\n");
} else {
_history_write_to_file (history, history->filename);
}
} else {
fprintf (stderr, "history_save(): history or filename is NULL\n");
}
}
void history_destroy (HistoryFile * history)
{
if (history) {
_history_free (history);
}
}
void history_reload (HistoryFile * history)
{
if (history) {
_history_clear (history);
if (history->filename) {
_history_load_from_file (history, history->filename);
}
history->has_changed = 0;
}
}
void history_print (HistoryFile * history)
{
if (history) {
unsigned int count = 0;
GList * i;
for (i = history->list; i; i = i->next) {
count++;
printf ("[%d] %s\n", count, (char *) (i->data));
}
printf ("-- list internal count: [%d]\n", history->count);
if (history->list_end) {
printf ("** list : %s\n", (char *) (history->list->data));
printf ("** list_end : %s\n", (char *) (history->list_end->data));
}
if (history->current) {
printf ("** list current : %s\n", (char *) (history->current->data));
}
}
}
// some apps might want to handle prev/next in a special way
void history_unset_current (HistoryFile * history)
{
if (history) {
history->current = NULL;
history->index = -1;
}
}
const char * history_get_current (HistoryFile * history)
{
if (history) return ((char*) (history->current->data));
else return (NULL);
}
int history_get_current_index (HistoryFile * history)
{
if (history) return (history->index);
else return (-1);
}
const char * history_next (HistoryFile * history)
{
if (history->current && history->current->next) {
history->current = history->current->next;
history->index++;
return ((char *) (history->current->data));
}
return (NULL);
}
const char * history_prev (HistoryFile * history)
{
if (history->current && history->current->prev) {
history->current = history->current->prev;
history->index--;
return ((char *) (history->current->data));
}
return (NULL);
}
const char * history_first (HistoryFile * history)
{
if (history->list) {
history->current = history->list;
history->index = 1;
return ((char *) (history->current->data));
}
return (NULL);
}
const char * history_last (HistoryFile * history)
{
if (history->list) {
history->current = history->list_end; // g_list_last (history->list);
return ((char *) (history->current->data));
history->index = history->count;
}
return (NULL);
}
void history_append (HistoryFile * history, const char * text)
{
if (!text || !*text) {
return;
}
GList * i, * templist;
char * ientry;
// if new entry = last entry, then abort
if (history->list_end) {
ientry = (char *) (history->list_end->data);
if (strcmp (text, ientry) == 0) {
return;
}
}
// do not allow duplicate entries, remove existing entry
for (i = history->list; i; i = i->next)
{
char * ientry = (char *) (i->data);
if (strcmp (text, ientry) == 0)
{
// entry already exists.. remove
free (i->data);
templist = g_list_delete_link (history->list, i);
if (!templist->prev) { // no previous entry.. new start
history->list = templist;
}
if (!templist->next) { // no next... only 1 entry
history->list_end = templist;
}
history->count--;
break;
}
// TODO: handle 'current'
}
// if new entry exceeds the max count, first entry will be removed
unsigned int remove_first = 0;
if (history->max > 0
&& history->count > 1
&& history->count >= history->max) {
remove_first = 1;
}
if (history->count < history->max) {
history->count++;
}
// add new item
char * new_item = strdup (text);
GList * position = history->list_end ? history->list_end : history->list;
position = g_list_append (position, (gpointer) new_item);
history->has_changed = 1;
if (!history->list) {
history->list = position; // first
history->current = history->list; // set current to first
history->index = 1;
}
if (history->list_end) {
history->list_end = history->list_end->next;
} else {
history->list_end = history->list; // first element has just been added
}
if (remove_first && history->list->next) {
// problem when removing the first entry - current may become invalid
if (history->current == history->list) {
history->current = history->list->next;
}
free (history->list->data);
history->list = g_list_delete_link (history->list, history->list);
}
}
void history_reverse (HistoryFile * history)
{
if (history && history->list) {
GList * prev_first = history->list;
history->list = g_list_reverse (history->list);
history->list_end = prev_first;
if (history->current == prev_first) {
history->current = history->list;
}
}
}