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