/* Copyright (C) 2007-2008 by Xyhthyx * * This file is part of Parcellite. * * Parcellite 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 3 of the License, or * (at your option) any later version. * * Parcellite 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, see . */ #include "parcellite.h" /**This is now a gslist of */ GList* history_list=NULL; static gint dbg=0; /**todo: handle parcellite history magic: if it does not exist, then we assume original format, and convert. Need to provide a menu item that allows user to convert back to old? or just provide manual instructions to convert back (i.e. dd skip)? */ #define HISTORY_MAGIC_SIZE 32 #define HISTORY_VERSION 1 /**index (-1) into array below */ static gchar* history_magics[]={ "1.0ParcelliteHistoryFile", NULL, }; #define HISTORY_FILE0 HISTORY_FILE /*#define HISTORY_FILE0 "parcellite/hist.test" */ /***************************************************************************/ /** Pass in the text via the struct. We assume len is correct, and BYTE based, not character. \n\b Arguments: \n\b Returns: length of resulting string. ****************************************************************************/ glong validate_utf8_text(gchar *text, glong len) { const gchar *valid; if(NULL == text || len <= 0) return 0; text[len]=0; if(FALSE == g_utf8_validate(text,-1,&valid)) { len=valid-text; text[len]=0; g_fprintf(stderr,"Truncating invalid utf8 text entry: '%s'\n",text); } return len; } /***************************************************************************/ /** Read the old history file and covert to the new format. \n\b Arguments: \n\b Returns: ****************************************************************************/ void read_history_old () { /* Build file path */ gchar* history_path = g_build_filename(g_get_user_data_dir(), HISTORY_FILE0, NULL); /* Open the file for reading */ FILE* history_file = fopen(history_path, "rb"); g_free(history_path); /* Check that it opened and begin read */ if (history_file) { /* Read the size of the first item */ gint size=1; g_mutex_lock(hist_lock); /* Continue reading until size is 0 */ while (size) { struct history_item *c; if (fread(&size, 4, 1, history_file) != 1){ size = 0; break; } else if( 0 == size ) break; /* Malloc according to the size of the item */ c = (struct history_item *)g_malloc0(size+ 2+sizeof(struct history_item)); c->type=CLIP_TYPE_TEXT; c->len=size; /* Read item and add ending character */ if(fread(c->text, size, 1, history_file) !=1){ g_printf("Error reading history file entry \n"); }else{ c->text[size] = 0; c->len=validate_utf8_text(c->text,c->len); if(0 != c->len) /* Prepend item and read next size */ history_list = g_list_prepend(history_list, c); else g_free(c); } } /* Close file and reverse the history to normal */ fclose(history_file); history_list = g_list_reverse(history_list); g_mutex_unlock(hist_lock); } } /* Saves history to ~/.local/share/parcellite/history */ void save_history_old() { /* Check that the directory is available */ check_dirs(); /* Build file path */ gchar* history_path = g_build_filename(g_get_user_data_dir(),HISTORY_FILE0, NULL); /* Open the file for writing */ FILE* history_file = fopen(history_path, "wb"); g_free(history_path); /* Check that it opened and begin write */ if (history_file) { GList* element; /* Write each element to a binary file */ for (element = history_list; element != NULL; element = element->next) { /* Create new GString from element data, write its length (size) * to file followed by the element data itself */ GString* item = g_string_new((gchar*)element->data); fwrite(&(item->len), 4, 1, history_file); fputs(item->str, history_file); g_string_free(item, TRUE); } /* Write 0 to indicate end of file */ gint end = 0; fwrite(&end, 4, 1, history_file); fclose(history_file); } } /***************************************************************************/ /** . \n\b Arguments: magic is what we are looking for, fmagic iw what we read from the file. \n\b Returns: history matched on match, -1 on erro, 0 if not found ****************************************************************************/ int check_magic(gchar *fmagic) { gint i, rtn; gchar *magic=g_malloc0(2+HISTORY_MAGIC_SIZE); if( NULL == magic) return -1; for (i=0;NULL !=history_magics[i];++i){ memset(magic,0,HISTORY_MAGIC_SIZE); memcpy(magic,history_magics[i],strlen(history_magics[i])); if(!memcmp(magic,fmagic,HISTORY_MAGIC_SIZE)){ rtn= i+1; goto done; } } rtn=0; done: g_free(magic); return rtn; } /***************************************************************************/ /** Reads history from ~/.local/share/parcellite/history . Current scheme is to have the total zize of element followed by the type, then the data \n\b Arguments: \n\b Returns: ****************************************************************************/ void read_history () { size_t x; /* Build file path */ gchar* history_path = g_build_filename(g_get_user_data_dir(),HISTORY_FILE0,NULL); gchar *magic=g_malloc0(2+HISTORY_MAGIC_SIZE); /*g_printf("History file '%s'",history_path); */ /* Open the file for reading */ FILE* history_file = fopen(history_path, "rb"); g_free(history_path); /* Check that it opened and begin read */ if (history_file) { /* Read the magic*/ guint32 size=1, end; if (fread(magic,HISTORY_MAGIC_SIZE , 1, history_file) != 1){ g_fprintf(stderr,"No magic! Assume no history.\n"); goto done; } if(HISTORY_VERSION !=check_magic(magic)){ g_fprintf(stderr,"Assuming old history style. Read and convert.\n"); /*g_printf("TODO! History version not matching!!Discarding history.\n"); */ g_free(magic); fclose(history_file); read_history_old(); return; } if(dbg) g_printf("History Magic OK. Reading\n"); /* Continue reading until size is 0 */ g_mutex_lock(hist_lock); while (size) { struct history_item *c; if (fread(&size, 4, 1, history_file) != 1) size = 0; if(0 == size) break; /* Malloc according to the size of the item */ c = (struct history_item *)g_malloc0(size+ 1); end=size-(sizeof(struct history_item)+4); if (fread(c, sizeof(struct history_item), 1, history_file) !=1) g_fprintf(stderr,"history_read: Invalid type!"); if(c->len != end) g_fprintf(stderr,"len check: invalid: ex %d got %d\n",end,c->len); /* Read item and add ending character */ if ((x =fread(&c->text,end,1,history_file)) != 1){ c->text[end] = 0; g_fprintf(stderr,"history_read: Invalid text, code %ld!\n'%s'\n",(unsigned long)x,c->text); } else { c->text[end] = 0; c->len=validate_utf8_text(c->text,c->len); if(dbg) g_fprintf(stderr,"len %d type %d '%s'\n",c->len,c->type,c->text); if(0 != c->len) /* Prepend item and read next size */ history_list = g_list_prepend(history_list, c); else g_free(c); } } done: g_free(magic); /* Close file and reverse the history to normal */ fclose(history_file); history_list = g_list_reverse(history_list); g_mutex_unlock(hist_lock); } if(dbg) g_printf("History read done\n"); } /** NOTES: gint width, height, rowstride, n_channels,bits_per_sample ; guchar *pixels; n_channels = gdk_pixbuf_get_n_channels (pixbuf); g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB); bits_per_sample=gdk_pixbuf_get_bits_per_sample (pixbuf); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); pixels = gdk_pixbuf_get_pixels (pixbuf); len of pixbuf=rowstride*(height-1)+width * ((n_channels * bits_per_sample + 7) / 8) last row of pixbuf=width * ((n_channels * bits_per_sample + 7) / 8) */ /* Saves history to ~/.local/share/parcellite/history */ /***************************************************************************/ /** write total len, then write type, then write data. \n\b Arguments: \n\b Returns: ****************************************************************************/ void save_history() { /* Check that the directory is available */ check_dirs(); /* Build file path */ gchar* history_path = g_build_filename(g_get_user_data_dir(), HISTORY_FILE0, NULL); /* Open the file for writing */ FILE* history_file = fopen(history_path, "wb"); /* Check that it opened and begin write */ if (history_file) { GList* element; gchar *magic=g_malloc0(2+HISTORY_MAGIC_SIZE); if( NULL == magic) { fclose(history_file); return; } memcpy(magic,history_magics[HISTORY_VERSION-1],strlen(history_magics[HISTORY_VERSION-1])); fwrite(magic,HISTORY_MAGIC_SIZE,1,history_file); g_mutex_lock(hist_lock); /* Write each element to a binary file */ for (element = history_list; element != NULL; element = element->next) { struct history_item *c; gint32 len; /* Create new GString from element data, write its length (size) * to file followed by the element data itself */ c=(struct history_item *)element->data; /**write total len */ /**write total len */ if(c->len >0){ len=c->len+sizeof(struct history_item)+4; fwrite(&len,4,1,history_file); fwrite(c,sizeof(struct history_item),1,history_file); fwrite(c->text,c->len,1,history_file); } } g_mutex_unlock(hist_lock); /* Write 0 to indicate end of file */ gint end = 0; fwrite(&end, 4, 1, history_file); fclose(history_file); }else{ g_fprintf(stderr,"Unable to open history file for save '%s'\n",history_path); } g_free(history_path); } /***************************************************************************/ /** . \n\b Arguments: \n\b Returns: ****************************************************************************/ struct history_item *new_clip_item(gint type, guint32 len, void *data) { struct history_item *c; if(NULL == (c=g_malloc0(sizeof(struct history_item)+len))){ g_fprintf(stderr,"Hit NULL for malloc of history_item!\n"); return NULL; } c->type=type; memcpy(c->text,data,len); c->len=len; return c; } /***************************************************************************/ /** checks to see if text is already in history. Also is a find text \n\b Arguments: if mode is 1, delete it too. \n\b Returns: -1 if not found, or nth element. ****************************************************************************/ gint is_duplicate(gchar* item, int mode, gint *flags) { GList* element; gint i; if(NULL ==item) return -1; /* Go through each element compare each */ for (i=0,element = history_list; element != NULL; element = element->next,++i) { struct history_item *c; c=(struct history_item *)element->data; if(CLIP_TYPE_TEXT == c->type){ if (g_strcmp0((gchar*)c->text, item) == 0) { if(mode){ if(NULL != flags && (CLIP_TYPE_PERSISTENT&c->flags)){ *flags=c->flags; } /*g_printf("Freeing 0x%02X '%s'\n",c->flags,c->text); */ /** g_free(element->data); history_list = g_list_delete_link(history_list, element);*/ } return i; break; } } } return -1; } /***************************************************************************/ /** Adds item to the end of history . \n\b Arguments: \n\b Returns: ****************************************************************************/ void append_item(gchar* item, int checkdup, gint iflags, gint itype) { gint flags=0,node=-1; GList *element=NULL; struct history_item *c=NULL; if(NULL == item) return; g_mutex_lock(hist_lock); /**delete if HIST_DEL flag is set. */ if( checkdup & HIST_CHECKDUP){ node=is_duplicate(item, checkdup & HIST_DEL, &flags); /*g_printf("isd done "); */ if(node > -1){ /**found it */ /*g_printf(" found\n"); */ if(!(checkdup & HIST_DEL)) return; } } if (-1 != node ){/**we found a duplicate in the history, remove, then re-add existing */ element=g_list_nth(history_list,node); history_list=g_list_remove_link(history_list,element); }else{ /**not found */ if(NULL == (c=new_clip_item(CLIP_TYPE_TEXT,strlen(item),item)) ) return; if(node > -1 && (checkdup & HIST_KEEP_FLAGS) ){ c->flags=flags; /*g_printf("Restoring 0x%02X '%s'\n",c->flags,c->text); */ } else c->flags=iflags; c->type=itype; } /*g_printf("Append '%s'\n",item); */ /* Prepend new item */ history_list = g_list_prepend(history_list, NULL ==element?c:element->data); g_mutex_unlock(hist_lock); /* Shorten history if necessary */ truncate_history(); } /***************************************************************************/ /** Deletes duplicate item in history . Orphaned function. \n\b Arguments: \n\b Returns: ****************************************************************************/ void delete_duplicate(gchar* item) { GList* element; int p=get_pref_int32("persistent_history"); /* Go through each element compare each */ g_mutex_lock(hist_lock); for (element = history_list; element != NULL; element = element->next) { struct history_item *c; c=(struct history_item *)element->data; if( (!p || !(CLIP_TYPE_PERSISTENT&c->flags)) && CLIP_TYPE_TEXT == c->type){ if (g_strcmp0((gchar*)c->text, item) == 0) { g_printf("del dup '%s'\n",c->text); g_free(element->data); history_list = g_list_delete_link(history_list, element); break; } } } g_mutex_unlock(hist_lock); } /***************************************************************************/ /** Truncates history to history_limit items, while preserving persistent data, if specified by the user. FIXME: This may not shorten the history \n\b Arguments: \n\b Returns: ****************************************************************************/ void truncate_history() { int p=get_pref_int32("persistent_history"); if (history_list) { g_mutex_lock(hist_lock); guint ll=g_list_length(history_list); guint lim=get_pref_int32("history_limit"); if(ll > lim){ /* Shorten history if necessary */ GList* last = g_list_last(history_list); while (last->prev && ll>lim) { struct history_item *c=(struct history_item *)last->data; last=last->prev; if(!p || !(c->flags&CLIP_TYPE_PERSISTENT)){ history_list=g_list_remove(history_list,c); --ll; } } } g_mutex_unlock(hist_lock); /* Save changes */ if (get_pref_int32("save_history")) save_history(); } } /* Returns pointer to last item in history */ gpointer get_last_item() { if (history_list) { if (history_list->data) { /* Return the last element */ gpointer last_item = history_list->data; return last_item; } else return NULL; } else return NULL; } /***************************************************************************/ /** . \n\b Arguments: \n\b Returns: ****************************************************************************/ void clear_history( void ) { g_mutex_lock(hist_lock); if( !get_pref_int32("persistent_history")){ g_list_free(history_list); history_list = NULL; } else{ /**save any persistent items */ GList *element, *successor; for (element = history_list; element != NULL; element = successor) { struct history_item *c; c=(struct history_item *)element->data; successor=element->next; if(!(c->flags & CLIP_TYPE_PERSISTENT)) history_list=g_list_remove(history_list,c); } } g_mutex_unlock(hist_lock); if (get_pref_int32("save_history")) save_history(); } /***************************************************************************/ /** . \n\b Arguments: \n\b Returns: ****************************************************************************/ int save_history_as_text(gchar *path) { FILE* fp = fopen(path, "w"); /* Check that it opened and begin write */ if (fp) { gint i; GList* element; g_mutex_lock(hist_lock); /* Write each element to file */ for (i=0,element = history_list; element != NULL; element = element->next) { struct history_item *c; c=(struct history_item *)element->data; if(c->flags & CLIP_TYPE_PERSISTENT) fprintf(fp,"PHIST_%04d %s\n",i,c->text); else fprintf(fp,"NHIST_%04d %s\n",i,c->text); ++i; } g_mutex_unlock(hist_lock); fclose(fp); } g_printf("histpath='%s'\n",path); return 0; } /***************************************************************************/ /** Dialog to save the history file. \n\b Arguments: \n\b Returns: ****************************************************************************/ void history_save_as(GtkMenuItem *menu_item, gpointer user_data) { GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new ("Save History File", NULL,/**parent */ GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_window_set_icon((GtkWindow*)dialog, gtk_widget_render_icon(dialog, GTK_STOCK_SAVE_AS, -1, NULL)); gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); /* if (user_edited_a_new_document) { */ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), g_get_home_dir()); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), "ParcelliteHistory.txt"); /** } else gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), filename_for_existing_document);*/ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { gchar *filename; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); save_history_as_text (filename); g_free (filename); } gtk_widget_destroy (dialog); }