1 /*
2  * help.c       -- Help command
3  *
4  * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
5  * Copyright (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or (at
10  * your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 /*
22  * How it works
23  *
24  * Main calls help_init, that installs option guards. These guards do
25  * nothing, but set help_dirs_stalled flag. When user issues help command,
26  * it checks, if help_dirs_stalled flag is set, and if it is, it calls
27  * init_help_dirs before performing help search.
28  *
29  * Options:
30  *   lang       List of semicolon-separated language codes. If unset, will
31  *              be detected from locale, with fallback to english.
32  *   help_dirs  List of semicolon-seaparated directories, where search for
33  *              help (in language subdirectories) will be performed.
34  *              Defaults to DATA_DIR/mcabber/help.
35  *   help_to_current  Print help to current buddy's buffer.
36  *
37  * XXX:
38  *   Remove command list from hlp.txt and print detected list of all help
39  *   topics?
40  */
41 
42 #include <glib.h>
43 #include <string.h>
44 #include <locale.h>
45 #include <sys/types.h>
46 #include <dirent.h>
47 
48 #include "logprint.h"
49 #include "screen.h"
50 #include "hbuf.h"
51 #include "settings.h"
52 #include "utils.h"
53 
54 static GSList   *help_dirs         = NULL;
55 static gboolean  help_dirs_stalled = TRUE;
56 
free_help_dirs(void)57 void free_help_dirs(void)
58 {
59   GSList *hel;
60 
61   for (hel = help_dirs; hel; hel = hel->next)
62     g_free(hel->data);
63 
64   g_slist_free(help_dirs);
65 
66   help_dirs = NULL;
67 }
68 
dir_push_languages(const char * langs,const char * dir)69 void dir_push_languages(const char *langs, const char *dir)
70 {
71   const char *lstart = langs;
72   const char *lend;
73   char       *path   = expand_filename(dir);
74 
75   for (lend = strchr(lstart, ';'); lend; lend = strchr(lstart, ';')) {
76     char *lang = g_strndup(lstart, lend - lstart);
77     char *dir  = g_strdup_printf("%s/%s", path, lang);
78 
79     help_dirs = g_slist_append(help_dirs, dir);
80 
81     g_free(lang);
82     lstart = lend + 1;
83   }
84 
85   { // finishing element
86     char *dir = g_strdup_printf("%s/%s", path, lstart);
87 
88     help_dirs = g_slist_append(help_dirs, dir);
89   }
90 
91   g_free(path);
92 }
93 
init_help_dirs(void)94 void init_help_dirs(void)
95 {
96   const char *paths;
97   const char *langs;
98   char        lang[6];
99 
100   if (help_dirs)
101     free_help_dirs();
102 
103   // initialize variables
104   paths = settings_opt_get("help_dirs");
105   if (!paths || !*paths)
106 #ifdef DATA_DIR
107     paths = DATA_DIR "/mcabber/help";
108 #else
109     paths = "/usr/local/share/mcabber/help;/usr/share/mcabber/help";
110 #endif
111 
112   langs = settings_opt_get("lang");
113 
114   if (!langs || !*langs) {
115     char *locale = setlocale(LC_MESSAGES, NULL);
116 
117     // XXX crude method to distinguish between xx_XX xx xx@xxx
118     // and C POSIX NULL etc.
119     if (locale && isalpha(locale[0]) && isalpha(locale[1])
120         && !isalpha(locale[2])) {
121       lang[0] = locale[0];
122       lang[1] = locale[1];
123 
124       if (lang[0] == 'e' && lang[1] == 'n')
125         lang[2] = '\0';
126       else {
127         lang[2] = ';';
128         lang[3] = 'e';
129         lang[4] = 'n';
130         lang[5] = '\0';
131       }
132 
133       langs = lang;
134     } else
135       langs = "en";
136   }
137 
138   { // parse
139     const char *pstart = paths;
140     const char *pend;
141 
142     for (pend = strchr(pstart, ';'); pend; pend = strchr(pstart, ';')) {
143       char *path = g_strndup(pstart, pend - pstart);
144 
145       dir_push_languages(langs, path);
146 
147       g_free(path);
148       pstart = pend + 1;
149     }
150 
151     // last element
152     dir_push_languages(langs, pstart);
153   }
154 
155   help_dirs_stalled = FALSE;
156 }
157 
do_help_in_dir(const char * arg,const char * path,const char * jid)158 static gboolean do_help_in_dir(const char *arg, const char *path, const char *jid)
159 {
160   char       *fname;
161   GIOChannel *channel;
162   GString    *line;
163   int         lines   = 0;
164 
165   if (arg && *arg)
166     fname = g_strdup_printf("%s/hlp_%s.txt", path, arg);
167   else
168     fname = g_strdup_printf("%s/hlp.txt", path);
169 
170   channel = g_io_channel_new_file(fname, "r", NULL);
171 
172   if (!channel)
173     return FALSE;
174 
175   line = g_string_new(NULL);
176 
177   while (TRUE) {
178     gsize     endpos;
179     GIOStatus ret;
180 
181     ret = g_io_channel_read_line_string(channel, line, &endpos, NULL);
182     if (ret != G_IO_STATUS_NORMAL) // XXX G_IO_STATUS_AGAIN?
183       break;
184 
185     line->str[endpos] = '\0';
186 
187     if (jid)
188       scr_WriteIncomingMessage(jid, line->str, 0,
189                                HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
190     else
191       scr_LogPrint(LPRINT_NORMAL, "%s", line->str);
192 
193     ++lines;
194   }
195 
196   g_io_channel_unref(channel);
197 
198   g_string_free(line, TRUE);
199 
200   if (!lines)
201     return FALSE;
202 
203   if (!jid) {
204     scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
205     scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
206                                    ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
207   }
208 
209   return TRUE;
210 }
211 
help_process(char * arg)212 void help_process(char *arg)
213 {
214   gchar      *string;
215   const char *jid    = NULL;
216   gboolean    done   = FALSE;
217 
218   if (help_dirs_stalled)
219     init_help_dirs();
220 
221   { // check input
222     char *c;
223 
224     for (c = arg; *c; ++c)
225       if (!isalnum(*c) && *c != '-' && *c != '_') {
226         scr_LogPrint(LPRINT_NORMAL, "Wrong help expression, "
227                      "it can contain only alphbetic, numeric"
228                      " characters and symbols '-' and '_'.");
229         return;
230       }
231 
232     string = g_strdup(arg);
233     mc_strtolower(string);
234   }
235 
236   if (settings_opt_get_int("help_to_current") && CURRENT_JID)
237     jid = CURRENT_JID;
238 
239   { // search
240     GSList *hel;
241 
242     for (hel = help_dirs; hel && !done; hel = hel->next) {
243       char *dir = (char *)hel->data;
244       done = do_help_in_dir(string, dir, jid);
245     }
246   }
247 
248   if (!done && string && *string) { // match and print any similar topics
249     GSList *hel;
250     GSList *matches = NULL;
251 
252     for (hel = help_dirs; hel; hel = hel->next) {
253       const char *path = (const char *)hel->data;
254       DIR        *dd   = opendir(path);
255 
256       if (dd) {
257         struct dirent *file;
258 
259         for (file = readdir(dd); file; file = readdir(dd)) {
260           const char *name = file->d_name;
261 
262           if (name && name[0] == 'h' && name[1] == 'l' &&
263                       name[2] == 'p' && name[3] == '_') {
264             const char *nstart = name + 4;
265             const char *nend   = strrchr(nstart, '.');
266 
267             if (nend) {
268               gsize len = nend - nstart;
269 
270               if (g_strstr_len(nstart, len, string)) {
271                 gchar *match = g_strndup(nstart, len);
272 
273                 if (!g_slist_find_custom(matches, match,
274                                          (GCompareFunc)strcmp))
275                   matches = g_slist_append(matches, match);
276                 else
277                   g_free(match);
278 
279                 done = TRUE;
280               }
281             }
282           }
283         }
284 
285         closedir(dd);
286       }
287     }
288 
289     if (done) {
290       GString *message = g_string_new("No exact match found. "
291                                       "Keywords, that contain this word:");
292       GSList  *wel;
293 
294       for (wel = matches; wel; wel = wel->next) {
295         gchar *word = (gchar *)wel->data;
296 
297         g_string_append_printf(message, " %s,", word);
298 
299         g_free(wel->data);
300       }
301 
302       message->str[message->len - 1] = '.';
303 
304       g_slist_free(matches);
305 
306       {
307         char *msg = g_string_free(message, FALSE);
308 
309         if (jid)
310           scr_WriteIncomingMessage(jid, msg, 0,
311                                    HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
312         else
313           scr_LogPrint(LPRINT_NORMAL, "%s", msg);
314 
315         g_free(msg);
316       }
317     }
318   }
319 
320   if (!done) {
321     if (jid) // XXX
322       scr_WriteIncomingMessage(jid, "No help found.", 0,
323                                HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
324     else
325       scr_LogPrint(LPRINT_NORMAL, "No help found.");
326   }
327 
328   g_free(string);
329 }
330 
help_guard(const gchar * key,const gchar * new_value)331 static gchar *help_guard(const gchar *key, const gchar *new_value)
332 {
333   help_dirs_stalled = TRUE;
334   return g_strdup(new_value);
335 }
336 
help_init(void)337 void help_init(void)
338 {
339   settings_set_guard("lang", help_guard);
340   settings_set_guard("help_dirs", help_guard);
341 }
342 
343 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
344