1 /* Copyright (C) 2007-2008 by Xyhthyx <xyhthyx@gmail.com>
2  *
3  * This file is part of Parcellite.
4  *
5  * Parcellite is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Parcellite is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "parcellite.h"
20 
21 
22 /**This is now a gslist of   */
23 GList* history_list=NULL;
24 static gint dbg=0;
25 /**todo:
26 handle parcellite history magic:
27 if it does not exist, then we assume original format, and convert.
28 	Need to provide a menu item that allows user to convert back to old?
29 	or just provide manual instructions to convert back (i.e. dd skip)?
30 
31   */
32 #define HISTORY_MAGIC_SIZE 32
33 #define HISTORY_VERSION     1 /**index (-1) into array below  */
34 static gchar* history_magics[]={
35 																"1.0ParcelliteHistoryFile",
36 																NULL,
37 };
38 
39 #define HISTORY_FILE0 HISTORY_FILE
40 /*#define HISTORY_FILE0 "parcellite/hist.test" */
41 
42 /***************************************************************************/
43 /** Pass in the text via the struct. We assume len is correct, and BYTE based,
44 not character.
45 \n\b Arguments:
46 \n\b Returns:	length of resulting string.
47 ****************************************************************************/
validate_utf8_text(gchar * text,glong len)48 glong validate_utf8_text(gchar *text, glong len)
49 {
50 	const gchar *valid;
51 	if(NULL == text || len <= 0)
52 		return 0;
53 	text[len]=0;
54 	if(FALSE == g_utf8_validate(text,-1,&valid)) {
55 		len=valid-text;
56 		text[len]=0;
57 		g_fprintf(stderr,"Truncating invalid utf8 text entry: '%s'\n",text);
58 	}
59 	return len;
60 }
61 /***************************************************************************/
62 /** Read the old history file and covert to the new format.
63 \n\b Arguments:
64 \n\b Returns:
65 ****************************************************************************/
read_history_old()66 void read_history_old ()
67 {
68   /* Build file path */
69   gchar* history_path = g_build_filename(g_get_user_data_dir(), HISTORY_FILE0,   NULL);
70 
71   /* Open the file for reading */
72   FILE* history_file = fopen(history_path, "rb");
73   g_free(history_path);
74   /* Check that it opened and begin read */
75   if (history_file)   {
76     /* Read the size of the first item */
77     gint size=1;
78     g_mutex_lock(hist_lock);
79     /* Continue reading until size is 0 */
80     while (size)   {
81 			struct history_item *c;
82 			if (fread(&size, 4, 1, history_file) != 1){
83 				size = 0;
84 				break;
85 			} else if( 0 == size )
86 				break;
87       /* Malloc according to the size of the item */
88 			c = (struct history_item *)g_malloc0(size+ 2+sizeof(struct history_item));
89       c->type=CLIP_TYPE_TEXT;
90 			c->len=size;
91       /* Read item and add ending character */
92       if(fread(c->text, size, 1, history_file) !=1){
93 				g_printf("Error reading history file entry \n");
94 			}else{
95 				c->text[size] = 0;
96 				c->len=validate_utf8_text(c->text,c->len);
97 				if(0 != c->len) /* Prepend item and read next size */
98 	      	history_list = g_list_prepend(history_list, c);
99 				else g_free(c);
100 			}
101     }
102     /* Close file and reverse the history to normal */
103     fclose(history_file);
104     history_list = g_list_reverse(history_list);
105 		g_mutex_unlock(hist_lock);
106   }
107 }
108 
109 /* Saves history to ~/.local/share/parcellite/history */
save_history_old()110 void save_history_old()
111 {
112   /* Check that the directory is available */
113   check_dirs();
114   /* Build file path */
115   gchar* history_path = g_build_filename(g_get_user_data_dir(),HISTORY_FILE0, NULL);
116   /* Open the file for writing */
117   FILE* history_file = fopen(history_path, "wb");
118   g_free(history_path);
119   /* Check that it opened and begin write */
120   if (history_file)  {
121     GList* element;
122     /* Write each element to a binary file */
123     for (element = history_list; element != NULL; element = element->next) {
124       /* Create new GString from element data, write its length (size)
125        * to file followed by the element data itself
126        */
127       GString* item = g_string_new((gchar*)element->data);
128       fwrite(&(item->len), 4, 1, history_file);
129       fputs(item->str, history_file);
130       g_string_free(item, TRUE);
131     }
132     /* Write 0 to indicate end of file */
133     gint end = 0;
134     fwrite(&end, 4, 1, history_file);
135     fclose(history_file);
136   }
137 }
138 
139 /***************************************************************************/
140 /** .
141 \n\b Arguments:
142 magic is what we are looking for, fmagic iw what we read from the file.
143 \n\b Returns: history matched on match, -1 on erro, 0 if not found
144 ****************************************************************************/
check_magic(gchar * fmagic)145 int check_magic(gchar *fmagic)
146 {
147 	gint i, rtn;
148 	gchar *magic=g_malloc0(2+HISTORY_MAGIC_SIZE);
149 	if( NULL == magic) return -1;
150 	for (i=0;NULL !=history_magics[i];++i){
151 		memset(magic,0,HISTORY_MAGIC_SIZE);
152 		memcpy(magic,history_magics[i],strlen(history_magics[i]));
153 		if(!memcmp(magic,fmagic,HISTORY_MAGIC_SIZE)){
154 			rtn= i+1;
155 			goto done;
156 		}
157 	}
158 	rtn=0;
159 done:
160 	g_free(magic);
161 	return rtn;
162 }
163 
164 /***************************************************************************/
165 /** Reads history from ~/.local/share/parcellite/history .
166 Current scheme is to have the total zize of element followed by the type, then the data
167 \n\b Arguments:
168 \n\b Returns:
169 ****************************************************************************/
read_history()170 void read_history ()
171 {
172 	size_t x;
173   /* Build file path */
174   gchar* history_path = g_build_filename(g_get_user_data_dir(),HISTORY_FILE0,NULL);
175 	gchar *magic=g_malloc0(2+HISTORY_MAGIC_SIZE);
176   /*g_printf("History file '%s'",history_path); */
177   /* Open the file for reading */
178   FILE* history_file = fopen(history_path, "rb");
179   g_free(history_path);
180   /* Check that it opened and begin read */
181   if (history_file)  {
182     /* Read the magic*/
183     guint32 size=1, end;
184 		if (fread(magic,HISTORY_MAGIC_SIZE , 1, history_file) != 1){
185 			g_fprintf(stderr,"No magic! Assume no history.\n");
186 			goto done;
187 		}
188     if(HISTORY_VERSION !=check_magic(magic)){
189 			g_fprintf(stderr,"Assuming old history style. Read and convert.\n");
190 			/*g_printf("TODO! History version not matching!!Discarding history.\n"); */
191 			g_free(magic);
192 			fclose(history_file);
193 			read_history_old();
194 			return;
195 		}
196 		if(dbg) g_printf("History Magic OK. Reading\n");
197     /* Continue reading until size is 0 */
198 		g_mutex_lock(hist_lock);
199     while (size)   {
200 			struct history_item *c;
201 			if (fread(&size, 4, 1, history_file) != 1)
202        size = 0;
203 			if(0 == size)
204 				break;
205       /* Malloc according to the size of the item */
206       c = (struct history_item *)g_malloc0(size+ 1);
207 			end=size-(sizeof(struct history_item)+4);
208 
209       if (fread(c, sizeof(struct history_item), 1, history_file) !=1)
210       	g_fprintf(stderr,"history_read: Invalid type!");
211 			if(c->len != end)
212 				g_fprintf(stderr,"len check: invalid: ex %d got %d\n",end,c->len);
213 			/* Read item and add ending character */
214 			if ((x =fread(&c->text,end,1,history_file)) != 1){
215 				c->text[end] = 0;
216 				g_fprintf(stderr,"history_read: Invalid text, code %ld!\n'%s'\n",(unsigned long)x,c->text);
217 			}	else {
218 				c->text[end] = 0;
219 				c->len=validate_utf8_text(c->text,c->len);
220 				if(dbg) g_fprintf(stderr,"len %d type %d '%s'\n",c->len,c->type,c->text);
221 				if(0 != c->len) /* Prepend item and read next size */
222 	      	history_list = g_list_prepend(history_list, c);
223 				else g_free(c);
224 			}
225 
226     }
227 done:
228 		g_free(magic);
229     /* Close file and reverse the history to normal */
230     fclose(history_file);
231     history_list = g_list_reverse(history_list);
232 		g_mutex_unlock(hist_lock);
233   }
234 	if(dbg) g_printf("History read done\n");
235 }
236 /**  NOTES:
237 	gint width, height, rowstride, n_channels,bits_per_sample ;
238   guchar *pixels;
239 
240   n_channels = gdk_pixbuf_get_n_channels (pixbuf);
241 
242   g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
243   bits_per_sample=gdk_pixbuf_get_bits_per_sample (pixbuf);
244 
245   width = gdk_pixbuf_get_width (pixbuf);
246   height = gdk_pixbuf_get_height (pixbuf);
247 
248   rowstride = gdk_pixbuf_get_rowstride (pixbuf);
249   pixels = gdk_pixbuf_get_pixels (pixbuf);
250 
251 len of pixbuf=rowstride*(height-1)+width * ((n_channels * bits_per_sample + 7) / 8)
252 
253 last row of pixbuf=width * ((n_channels * bits_per_sample + 7) / 8)
254 */
255 /* Saves history to ~/.local/share/parcellite/history */
256 
257 /***************************************************************************/
258 /** write total len, then write type, then write data.
259 \n\b Arguments:
260 \n\b Returns:
261 ****************************************************************************/
save_history()262 void save_history()
263 {
264   /* Check that the directory is available */
265   check_dirs();
266   /* Build file path */
267   gchar* history_path = g_build_filename(g_get_user_data_dir(), HISTORY_FILE0, NULL);
268   /* Open the file for writing */
269   FILE* history_file = fopen(history_path, "wb");
270 
271   /* Check that it opened and begin write */
272   if (history_file)  {
273     GList* element;
274 		gchar *magic=g_malloc0(2+HISTORY_MAGIC_SIZE);
275 	  if( NULL == magic) {
276 			fclose(history_file);
277 			return;
278 		}
279 		memcpy(magic,history_magics[HISTORY_VERSION-1],strlen(history_magics[HISTORY_VERSION-1]));
280 		fwrite(magic,HISTORY_MAGIC_SIZE,1,history_file);
281 		g_mutex_lock(hist_lock);
282     /* Write each element to a binary file */
283     for (element = history_list; element != NULL; element = element->next) {
284 		  struct history_item *c;
285 			gint32 len;
286       /* Create new GString from element data, write its length (size)
287        * to file followed by the element data itself
288        */
289 			c=(struct history_item *)element->data;
290 			/**write total len  */
291 			/**write total len  */
292 			if(c->len >0){
293 				len=c->len+sizeof(struct history_item)+4;
294 				fwrite(&len,4,1,history_file);
295 				fwrite(c,sizeof(struct history_item),1,history_file);
296 				fwrite(c->text,c->len,1,history_file);
297 			}
298 
299     }
300 		g_mutex_unlock(hist_lock);
301     /* Write 0 to indicate end of file */
302     gint end = 0;
303     fwrite(&end, 4, 1, history_file);
304     fclose(history_file);
305   }else{
306 		g_fprintf(stderr,"Unable to open history file for save '%s'\n",history_path);
307 	}
308 	g_free(history_path);
309 }
310 
311 /***************************************************************************/
312 /** .
313 \n\b Arguments:
314 \n\b Returns:
315 ****************************************************************************/
new_clip_item(gint type,guint32 len,void * data)316 struct history_item *new_clip_item(gint type, guint32 len, void *data)
317 {
318 	struct history_item *c;
319 	if(NULL == (c=g_malloc0(sizeof(struct history_item)+len))){
320 		g_fprintf(stderr,"Hit NULL for malloc of history_item!\n");
321 		return NULL;
322 	}
323 
324 	c->type=type;
325 	memcpy(c->text,data,len);
326 	c->len=len;
327 	return c;
328 }
329 /***************************************************************************/
330 /**  checks to see if text is already in history. Also is a find text
331 \n\b Arguments: if mode is 1, delete it too.
332 \n\b Returns: -1 if not found, or nth element.
333 ****************************************************************************/
is_duplicate(gchar * item,int mode,gint * flags)334 gint is_duplicate(gchar* item, int mode, gint *flags)
335 {
336   GList* element;
337 	gint i;
338 	if(NULL ==item)
339 		return -1;
340   /* Go through each element compare each */
341   for (i=0,element = history_list; element != NULL; element = element->next,++i) {
342 	  struct history_item *c;
343 		c=(struct history_item *)element->data;
344 		if(CLIP_TYPE_TEXT == c->type){
345 	    if (g_strcmp0((gchar*)c->text, item) == 0) {
346 				if(mode){
347 					if(NULL != flags && (CLIP_TYPE_PERSISTENT&c->flags)){
348 						*flags=c->flags;
349 					}
350 					/*g_printf("Freeing 0x%02X '%s'\n",c->flags,c->text);  */
351 					/** g_free(element->data);
352 		      history_list = g_list_delete_link(history_list, element);*/
353 				}
354 				return i;
355 	      break;
356 	    }
357 		}
358   }
359 	return -1;
360 }
361 /***************************************************************************/
362 /**  Adds item to the end of history .
363 \n\b Arguments:
364 \n\b Returns:
365 ****************************************************************************/
append_item(gchar * item,int checkdup,gint iflags,gint itype)366 void append_item(gchar* item, int checkdup, gint iflags, gint itype)
367 {
368 	gint flags=0,node=-1;
369 	GList *element=NULL;
370 	struct history_item *c=NULL;
371 	if(NULL == item)
372 		return;
373 	g_mutex_lock(hist_lock);
374 /**delete if HIST_DEL flag is set.  */
375 	if( checkdup & HIST_CHECKDUP){
376 		node=is_duplicate(item, checkdup & HIST_DEL, &flags);
377 		/*g_printf("isd done "); */
378 		if(node > -1){ /**found it  */
379 			/*g_printf(" found\n"); */
380 			if(!(checkdup & HIST_DEL))
381 				return;
382 		}
383 	}
384 	if (-1 != node ){/**we found a duplicate in the history, remove, then re-add existing  */
385 		element=g_list_nth(history_list,node);
386 		history_list=g_list_remove_link(history_list,element);
387 	}else{ /**not found   */
388 		if(NULL == (c=new_clip_item(CLIP_TYPE_TEXT,strlen(item),item)) )
389 			return;
390 		if(node > -1 && (checkdup & HIST_KEEP_FLAGS) ){
391 			c->flags=flags;
392 			/*g_printf("Restoring 0x%02X '%s'\n",c->flags,c->text);  */
393 		}	else
394 			c->flags=iflags;
395 		c->type=itype;
396 	}
397 
398 
399 
400 	/*g_printf("Append '%s'\n",item); */
401    /* Prepend new item */
402   history_list = g_list_prepend(history_list, NULL ==element?c:element->data);
403 	g_mutex_unlock(hist_lock);
404   /* Shorten history if necessary */
405   truncate_history();
406 }
407 
408 /***************************************************************************/
409 /**  Deletes duplicate item in history . Orphaned function.
410 \n\b Arguments:
411 \n\b Returns:
412 ****************************************************************************/
delete_duplicate(gchar * item)413 void delete_duplicate(gchar* item)
414 {
415   GList* element;
416 	int p=get_pref_int32("persistent_history");
417   /* Go through each element compare each */
418 	g_mutex_lock(hist_lock);
419   for (element = history_list; element != NULL; element = element->next) {
420 	  struct history_item *c;
421 		c=(struct history_item *)element->data;
422 		if( (!p || !(CLIP_TYPE_PERSISTENT&c->flags)) && CLIP_TYPE_TEXT == c->type){
423 	    if (g_strcmp0((gchar*)c->text, item) == 0) {
424 				g_printf("del dup '%s'\n",c->text);
425 	      g_free(element->data);
426 	      history_list = g_list_delete_link(history_list, element);
427 	      break;
428 	    }
429 		}
430   }
431 	g_mutex_unlock(hist_lock);
432 }
433 
434 /***************************************************************************/
435 /**  Truncates history to history_limit items, while preserving persistent
436     data, if specified by the user. FIXME: This may not shorten the history
437 
438 \n\b Arguments:
439 \n\b Returns:
440 ****************************************************************************/
truncate_history()441 void truncate_history()
442 {
443   int p=get_pref_int32("persistent_history");
444   if (history_list)  {
445 		g_mutex_lock(hist_lock);
446 		guint ll=g_list_length(history_list);
447 		guint lim=get_pref_int32("history_limit");
448 		if(ll > lim){ /* Shorten history if necessary */
449 			GList* last = g_list_last(history_list);
450 			while (last->prev && ll>lim)   {
451 	      struct history_item *c=(struct history_item *)last->data;
452 				last=last->prev;
453 	      if(!p || !(c->flags&CLIP_TYPE_PERSISTENT)){
454 					history_list=g_list_remove(history_list,c);
455 					--ll;
456 				}
457 	    }
458 		}
459 		g_mutex_unlock(hist_lock);
460     /* Save changes */
461     if (get_pref_int32("save_history"))
462       save_history();
463   }
464 }
465 
466 /* Returns pointer to last item in history */
get_last_item()467 gpointer get_last_item()
468 {
469   if (history_list)
470   {
471     if (history_list->data)
472     {
473       /* Return the last element */
474       gpointer last_item = history_list->data;
475       return last_item;
476     }
477     else
478       return NULL;
479   }
480   else
481     return NULL;
482 }
483 
484 
485 /***************************************************************************/
486 /** .
487 \n\b Arguments:
488 \n\b Returns:
489 ****************************************************************************/
clear_history(void)490 void clear_history( void )
491 {
492 	g_mutex_lock(hist_lock);
493 	if( !get_pref_int32("persistent_history")){
494 		g_list_free(history_list);
495 		history_list = NULL;
496 	}	else{ /**save any persistent items  */
497 		GList *element, *successor;
498 		for (element = history_list; element != NULL; element = successor) {
499 		  struct history_item *c;
500 			c=(struct history_item *)element->data;
501 			successor=element->next;
502 			if(!(c->flags & CLIP_TYPE_PERSISTENT))
503 				history_list=g_list_remove(history_list,c);
504 		}
505 	}
506 	g_mutex_unlock(hist_lock);
507   if (get_pref_int32("save_history"))
508   	save_history();
509 }
510 /***************************************************************************/
511 /** .
512 \n\b Arguments:
513 \n\b Returns:
514 ****************************************************************************/
save_history_as_text(gchar * path)515 int save_history_as_text(gchar *path)
516 {
517 	FILE* fp = fopen(path, "w");
518   /* Check that it opened and begin write */
519   if (fp)  {
520 		gint i;
521     GList* element;
522 		g_mutex_lock(hist_lock);
523     /* Write each element to  file */
524     for (i=0,element = history_list; element != NULL; element = element->next) {
525 		  struct history_item *c;
526 			c=(struct history_item *)element->data;
527 			if(c->flags & CLIP_TYPE_PERSISTENT)
528 				fprintf(fp,"PHIST_%04d %s\n",i,c->text);
529 			else
530 				fprintf(fp,"NHIST_%04d %s\n",i,c->text);
531 			++i;
532     }
533 		g_mutex_unlock(hist_lock);
534     fclose(fp);
535   }
536 
537 	g_printf("histpath='%s'\n",path);
538 	return 0;
539 }
540 /***************************************************************************/
541 /** Dialog to save the history file.
542 \n\b Arguments:
543 \n\b Returns:
544 ****************************************************************************/
history_save_as(GtkMenuItem * menu_item,gpointer user_data)545 void history_save_as(GtkMenuItem *menu_item, gpointer user_data)
546 {
547 	GtkWidget *dialog;
548   dialog = gtk_file_chooser_dialog_new ("Save History File",
549 				      NULL,/**parent  */
550 				      GTK_FILE_CHOOSER_ACTION_SAVE,
551 				      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
552 				      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
553 				      NULL);
554 	gtk_window_set_icon((GtkWindow*)dialog, gtk_widget_render_icon(dialog, GTK_STOCK_SAVE_AS, -1, NULL));
555 	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
556 /*	if (user_edited_a_new_document)  { */
557 	    gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), g_get_home_dir());
558 	    gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), "ParcelliteHistory.txt");
559 /**    }
560 	else
561 	  gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), filename_for_existing_document);*/
562 	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
563 	    gchar *filename;
564 	    filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
565 	    save_history_as_text (filename);
566 	    g_free (filename);
567 	  }
568 	gtk_widget_destroy (dialog);
569 }
570 
571