1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2004-2005 Timo Hirvonen
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * 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 "history.h"
20 #include "xmalloc.h"
21 #include "file.h"
22 #include "uchar.h"
23 #include "list.h"
24 #include "prog.h"
25 
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 
31 struct history_entry {
32 	struct list_head node;
33 	char *text;
34 };
35 
history_entry_new(const char * text)36 static struct history_entry *history_entry_new(const char *text)
37 {
38 	struct history_entry *new;
39 	new = xnew(struct history_entry, 1);
40 	new->text = xstrdup(text);
41 	return new;
42 }
43 
history_entry_free(struct history_entry * history)44 static void history_entry_free(struct history_entry *history)
45 {
46 	free(history->text);
47 	free(history);
48 }
49 
history_free(struct history * history)50 void history_free(struct history *history)
51 {
52 	struct list_head *item, *temp;
53 	list_for_each_safe(item, temp, &history->head) {
54 		struct history_entry *history_entry;
55 		history_entry = list_entry(item, struct history_entry, node);
56 		history_entry_free(history_entry);
57 	}
58 }
59 
history_add_tail(void * data,const char * line)60 static int history_add_tail(void *data, const char *line)
61 {
62 	struct history *history = data;
63 
64 	if (history->lines < history->max_lines) {
65 		struct history_entry *new;
66 
67 		new = history_entry_new(line);
68 		list_add_tail(&new->node, &history->head);
69 		history->lines++;
70 	}
71 	return 0;
72 }
73 
history_load(struct history * history,char * filename,int max_lines)74 void history_load(struct history *history, char *filename, int max_lines)
75 {
76 	list_init(&history->head);
77 	history->max_lines = max_lines;
78 	history->lines = 0;
79 	history->search_pos = NULL;
80 	history->filename = filename;
81 	file_for_each_line(filename, history_add_tail, history);
82 }
83 
history_save(struct history * history)84 void history_save(struct history *history)
85 {
86 	char filename_tmp[512];
87 	struct list_head *item;
88 	int fd;
89 	ssize_t rc;
90 
91 	snprintf(filename_tmp, sizeof(filename_tmp), "%s.tmp", history->filename);
92 	fd = open(filename_tmp, O_CREAT | O_WRONLY | O_TRUNC, 0666);
93 	if (fd == -1)
94 		return;
95 	list_for_each(item, &history->head) {
96 		struct history_entry *history_entry;
97 		const char nl = '\n';
98 
99 		history_entry = list_entry(item, struct history_entry, node);
100 
101 		rc = write(fd, history_entry->text, strlen(history_entry->text));
102 		if (rc == -1)
103 			goto out;
104 
105 		rc = write(fd, &nl, 1);
106 		if (rc == -1)
107 			goto out;
108 	}
109 out:
110 	close(fd);
111 
112 	rc = rename(filename_tmp, history->filename);
113 	if (rc)
114 		warn_errno("renaming %s to %s", filename_tmp, history->filename);
115 }
116 
history_add_line(struct history * history,const char * line)117 void history_add_line(struct history *history, const char *line)
118 {
119 	struct history_entry *new;
120 	struct list_head *item;
121 
122 	new = history_entry_new(line);
123 	list_add(&new->node, &history->head);
124 	history->lines++;
125 
126 	/* remove identical */
127 	item = history->head.next->next;
128 	while (item != &history->head) {
129 		struct list_head *next = item->next;
130 		struct history_entry *hentry;
131 
132 		hentry = container_of(item, struct history_entry, node);
133 		if (strcmp(hentry->text, new->text) == 0) {
134 			list_del(item);
135 			history_entry_free(hentry);
136 			history->lines--;
137 		}
138 		item = next;
139 	}
140 
141 	/* remove oldest if history is 'full' */
142 	if (history->lines > history->max_lines) {
143 		struct list_head *node;
144 		struct history_entry *hentry;
145 
146 		node = history->head.prev;
147 		list_del(node);
148 		hentry = list_entry(node, struct history_entry, node);
149 		history_entry_free(hentry);
150 		history->lines--;
151 	}
152 }
153 
history_reset_search(struct history * history)154 void history_reset_search(struct history *history)
155 {
156 	history->search_pos = NULL;
157 }
158 
history_search_forward(struct history * history,const char * text)159 const char *history_search_forward(struct history *history, const char *text)
160 {
161 	struct list_head *item;
162 	int search_len;
163 
164 	if (history->search_pos == NULL) {
165 		/* first time to search. set search */
166 		item = history->head.next;
167 	} else {
168 		item = history->search_pos->next;
169 	}
170 	search_len = strlen(text);
171 	while (item != &history->head) {
172 		struct history_entry *hentry;
173 
174 		hentry = list_entry(item, struct history_entry, node);
175 		if (strncmp(text, hentry->text, search_len) == 0) {
176 			history->search_pos = item;
177 			return hentry->text;
178 		}
179 		item = item->next;
180 	}
181 	return NULL;
182 }
183 
history_search_backward(struct history * history,const char * text)184 const char *history_search_backward(struct history *history, const char *text)
185 {
186 	struct list_head *item;
187 	int search_len;
188 
189 	if (history->search_pos == NULL)
190 		return NULL;
191 	item = history->search_pos->prev;
192 	search_len = strlen(text);
193 	while (item != &history->head) {
194 		struct history_entry *hentry;
195 
196 		hentry = list_entry(item, struct history_entry, node);
197 		if (strncmp(text, hentry->text, search_len) == 0) {
198 			history->search_pos = item;
199 			return hentry->text;
200 		}
201 		item = item->prev;
202 	}
203 	history->search_pos = NULL;
204 	return NULL;
205 }
206