source: git/src/cavernlog.cc @ bbed692

line_contentsstereotravis-osx
Last change on this file since bbed692 was bbed692, checked in by Olly Betts <olly@…>, 5 years ago

src/cavernlog.cc: Avoid hardcoding numeric value of LOG_REPROCESS.

  • Property mode set to 100644
File size: 13.9 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005,2006,2010,2011,2012,2014,2015 Olly Betts
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
21#ifdef HAVE_CONFIG_H
22# include <config.h>
23#endif
24
25#include "aven.h"
26#include "cavernlog.h"
27#include "filename.h"
28#include "mainfrm.h"
29#include "message.h"
30
31#include <errno.h>
32#include <stdio.h>
33#include <stdlib.h>
34
35// For select():
36#ifdef HAVE_SYS_SELECT_H
37#include <sys/select.h>
38#endif
39#include <sys/time.h>
40#include <sys/types.h>
41#include <unistd.h>
42
43enum { LOG_REPROCESS = 1234, LOG_SAVE = 1235 };
44
45BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
46    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
47    EVT_BUTTON(LOG_SAVE, CavernLogWindow::OnSave)
48    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
49END_EVENT_TABLE()
50
51static wxString escape_for_shell(wxString s, bool protect_dash = false)
52{
53    size_t p = 0;
54#ifdef __WXMSW__
55    bool needs_quotes = false;
56    while (p < s.size()) {
57        wxChar ch = s[p];
58        if (ch < 127) {
59            if (ch == wxT('"')) {
60                s.insert(p, 1, wxT('\\'));
61                ++p;
62                needs_quotes = true;
63            } else if (strchr(" <>&|^", ch)) {
64                needs_quotes = true;
65            }
66        }
67        ++p;
68    }
69    if (needs_quotes) {
70        s.insert(0u, 1, wxT('"'));
71        s += wxT('"');
72    }
73#else
74    if (protect_dash && !s.empty() && s[0u] == '-') {
75        // If the filename starts with a '-', protect it from being
76        // treated as an option by prepending "./".
77        s.insert(0, wxT("./"));
78        p = 2;
79    }
80    while (p < s.size()) {
81        // Exclude a few safe characters which are common in filenames
82        if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
83            s.insert(p, 1, wxT('\\'));
84            ++p;
85        }
86        ++p;
87    }
88#endif
89    return s;
90}
91
92CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
93    : wxHtmlWindow(parent), mainfrm(mainfrm_), init_done(false), survey(survey_)
94{
95    int fsize = parent->GetFont().GetPointSize();
96    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
97    SetFonts(wxString(), wxString(), sizes);
98}
99
100void
101CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
102{
103    wxString href = link.GetHref();
104    wxString title = link.GetTarget();
105    size_t colon = href.rfind(wxT(':'));
106    if (colon == wxString::npos)
107        return;
108    size_t colon2 = href.rfind(wxT(':'), colon - 1);
109    if (colon2 != wxString::npos) swap(colon, colon2);
110#ifdef __WXMSW__
111    wxString cmd = wxT("notepad $f");
112#elif defined __WXMAC__
113    wxString cmd = wxT("open -t $f");
114#else
115    wxString cmd = wxT("x-terminal-emulator -title $t -e vim +'call cursor($l,$c)' $f");
116    // wxString cmd = wxT("gedit -b $f +$l:$c $f");
117    // wxString cmd = wxT("x-terminal-emulator -title $t -e emacs +$l $f");
118    // wxString cmd = wxT("x-terminal-emulator -title $t -e nano +$l $f");
119    // wxString cmd = wxT("x-terminal-emulator -title $t -e jed -g $l $f");
120#endif
121    const char * p = getenv("SURVEXEDITOR");
122    if (p) {
123        cmd = wxString(p, wxConvUTF8);
124        if (!cmd.find(wxT("$f"))) {
125            cmd += wxT(" $f");
126        }
127    }
128    size_t i = 0;
129    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
130        if (++i >= cmd.size()) break;
131        switch ((int)cmd[i]) {
132            case wxT('$'):
133                cmd.erase(i, 1);
134                break;
135            case wxT('f'): {
136                wxString f = escape_for_shell(href.substr(0, colon), true);
137                cmd.replace(i - 1, 2, f);
138                i += f.size() - 1;
139                break;
140            }
141            case wxT('t'): {
142                wxString t = escape_for_shell(title);
143                cmd.replace(i - 1, 2, t);
144                i += t.size() - 1;
145                break;
146            }
147            case wxT('l'): {
148                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
149                cmd.replace(i - 1, 2, l);
150                i += l.size() - 1;
151                break;
152            }
153            case wxT('c'): {
154                wxString l;
155                if (colon2 == wxString::npos)
156                    l = wxT("0");
157                else
158                    l = escape_for_shell(href.substr(colon2 + 1));
159                cmd.replace(i - 1, 2, l);
160                i += l.size() - 1;
161                break;
162            }
163            default:
164                ++i;
165        }
166    }
167#ifdef __WXMSW__
168    if (_wsystem(cmd.c_str()) >= 0)
169        return;
170#else
171    if (system(cmd.mb_str()) >= 0)
172        return;
173#endif
174    wxString m;
175    // TRANSLATORS: %s is replaced by the command we attempted to run.
176    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
177    m += wxT(" (");
178    m += wxString(strerror(errno), wxConvUTF8);
179    m += wxT(')');
180    wxGetApp().ReportError(m);
181}
182
183int
184CavernLogWindow::process(const wxString &file)
185{
186    SetFocus();
187    filename = file;
188
189    log_txt.resize(0);
190
191#ifdef __WXMSW__
192    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
193#else
194    setenv("SURVEX_UTF8", "1", 1);
195#endif
196
197    wxString escaped_file = escape_for_shell(file, true);
198#ifdef __WXMSW__
199    wxString cmd;
200    {
201        DWORD len = 256;
202        wchar_t *buf = NULL;
203        while (1) {
204            DWORD got;
205            buf = (wchar_t*)osrealloc(buf, len * 2);
206            got = GetModuleFileNameW(NULL, buf, len);
207            if (got < len) break;
208            len += len;
209        }
210        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
211        if (wcsncmp(buf, L"\\\\?\\", 4) == 0) buf += 4;
212        wchar_t * slash = wcsrchr(buf, L'\\');
213        if (slash) {
214            cmd.assign(buf, slash - buf + 1);
215        }
216    }
217    cmd += L"cavern";
218    cmd = escape_for_shell(cmd, false);
219#else
220    char *cavern = use_path(msg_exepth(), "cavern");
221    wxString cmd = escape_for_shell(wxString(cavern, wxConvUTF8), false);
222    osfree(cavern);
223#endif
224    cmd += wxT(" -o ");
225    cmd += escaped_file;
226    cmd += wxT(' ');
227    cmd += escaped_file;
228
229#ifdef __WXMSW__
230    FILE * cavern_out = _wpopen(cmd.c_str(), L"r");
231#else
232    FILE * cavern_out = popen(cmd.mb_str(), "r");
233#endif
234    if (!cavern_out) {
235        wxString m;
236        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
237        m += wxT(" (");
238        m += wxString(strerror(errno), wxConvUTF8);
239        m += wxT(')');
240        wxGetApp().ReportError(m);
241        return -2;
242    }
243
244    const wxString & error_marker = wmsg(/*error*/93) + ":";
245    const wxString & warning_marker = wmsg(/*warning*/4) + ":";
246
247    int cavern_fd;
248#ifdef __WXMSW__
249    cavern_fd = _fileno(cavern_out);
250#else
251    cavern_fd = fileno(cavern_out);
252#endif
253    assert(cavern_fd < FD_SETSIZE); // FIXME we shouldn't just assert, but what else to do?
254    wxString cur;
255    int link_count = 0;
256    unsigned char buf[1024];
257    unsigned char * end = buf;
258    while (true) {
259        fd_set rfds, efds;
260        FD_ZERO(&rfds);
261        FD_SET(cavern_fd, &rfds);
262        FD_ZERO(&efds);
263        FD_SET(cavern_fd, &efds);
264        // Timeout instantly the first time, and call Update() so the window
265        // contents get updated.  If we're still waiting for output after that,
266        // wait for output for 0.1 seconds, then call wxYield() so that the UI
267        // doesn't block.
268        struct timeval timeout;
269        timeout.tv_sec = 0;
270        timeout.tv_usec = 0;
271        bool first = true;
272        while (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
273            if (first) {
274                first = false;
275                Update();
276            } else {
277                wxYield();
278            }
279            FD_SET(cavern_fd, &rfds);
280            FD_SET(cavern_fd, &efds);
281            // Set timeout to 0.1 seconds.
282            timeout.tv_sec = 0;
283            timeout.tv_usec = 100000;
284        }
285        if (!FD_ISSET(cavern_fd, &rfds)) {
286            // Error, which pclose() should report.
287            break;
288        }
289
290        ssize_t r = read(cavern_fd, end, sizeof(buf) - (end - buf));
291        if (r <= 0) {
292            if (r == 0 && buf != end) {
293                // FIXME: Truncated UTF-8 sequence.
294            }
295            break;
296        }
297        log_txt.append((const char *)end, r);
298        end += r;
299
300        const unsigned char * p = buf;
301
302        while (p != end) {
303            int ch = *p++;
304            if (ch >= 0x80) {
305                // Decode multi-byte UTF-8 sequence.
306                if (ch < 0xc0) {
307                    // FIXME: Invalid UTF-8 sequence.
308                    goto abort;
309                } else if (ch < 0xe0) {
310                    /* 2 byte sequence */
311                    if (p == end) {
312                        // Incomplete UTF-8 sequence - try to read more.
313                        break;
314                    }
315                    int ch1 = *p++;
316                    if ((ch1 & 0xc0) != 0x80) {
317                        // FIXME: Invalid UTF-8 sequence.
318                        goto abort;
319                    }
320                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
321                } else if (ch < 0xf0) {
322                    /* 3 byte sequence */
323                    if (end - p <= 1) {
324                        // Incomplete UTF-8 sequence - try to read more.
325                        break;
326                    }
327                    int ch1 = *p++;
328                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
329                    if ((ch1 & 0xc0) != 0x80) {
330                        // FIXME: Invalid UTF-8 sequence.
331                        goto abort;
332                    }
333                    int ch2 = *p++;
334                    if ((ch2 & 0xc0) != 0x80) {
335                        // FIXME: Invalid UTF-8 sequence.
336                        goto abort;
337                    }
338                    ch |= (ch2 & 0x3f);
339                } else {
340                    // FIXME: Overlong UTF-8 sequence.
341                    goto abort;
342                }
343            }
344
345            switch (ch) {
346                case '\r':
347                    // Ignore.
348                    break;
349                case '\n': {
350                    if (cur.empty()) continue;
351#ifndef __WXMSW__
352                    size_t colon = cur.find(':');
353#else
354                    // If the path is "C:\path\to\file.svx" then don't split at the
355                    // : after the drive letter!  FIXME: better to look for ": "?
356                    size_t colon = cur.find(':', 2);
357#endif
358                    if (colon != wxString::npos && colon < cur.size() - 2) {
359                        ++colon;
360                        size_t i = colon;
361                        while (i < cur.size() - 2 &&
362                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
363                            ++i;
364                        }
365                        if (i > colon && cur[i] == wxT(':') ) {
366                            colon = i;
367                            // Check for column number.
368                            while (++i < cur.size() - 2 &&
369                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
370                            if (i > colon + 1 && cur[i] == wxT(':') ) {
371                                colon = i;
372                            }
373                            wxString tag = wxT("<a href=\"");
374                            tag.append(cur, 0, colon);
375                            while (cur[++i] == wxT(' ')) { }
376                            tag += wxT("\" target=\"");
377                            tag.append(cur, i, wxString::npos);
378                            tag += wxT("\">");
379                            cur.insert(0, tag);
380                            size_t offset = colon + tag.size();
381                            cur.insert(offset, wxT("</a>"));
382                            offset += 4 + 2;
383                            if (cur.substr(offset, error_marker.size()) == error_marker) {
384                                // Show "error" marker in red.
385                                cur.insert(offset, wxT("<span style=\"color:red\">"));
386                                offset += 24 + error_marker.size() - 1;
387                                cur.insert(offset, wxT("</span>"));
388                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
389                                // Show "warning" marker in orange.
390                                cur.insert(offset, wxT("<span style=\"color:orange\">"));
391                                offset += 27 + warning_marker.size() - 1;
392                                cur.insert(offset, wxT("</span>"));
393                            }
394
395                            ++link_count;
396                        }
397                    }
398
399                    // Save the scrollbar positions.
400                    int scroll_x = 0, scroll_y = 0;
401                    GetViewStart(&scroll_x, &scroll_y);
402
403                    cur += wxT("<br>\n");
404                    AppendToPage(cur);
405
406                    if (!link_count) {
407                        // Auto-scroll the window until we've reported a warning or
408                        // error.
409                        int x, y;
410                        GetVirtualSize(&x, &y);
411                        int xs, ys;
412                        GetClientSize(&xs, &ys);
413                        y -= ys;
414                        int xu, yu;
415                        GetScrollPixelsPerUnit(&xu, &yu);
416                        Scroll(scroll_x, y / yu);
417                    } else {
418                        // Restore the scrollbar positions.
419                        Scroll(scroll_x, scroll_y);
420                    }
421
422                    cur.clear();
423                    break;
424                }
425                case '<':
426                    cur += wxT("&lt;");
427                    break;
428                case '>':
429                    cur += wxT("&gt;");
430                    break;
431                case '&':
432                    cur += wxT("&amp;");
433                    break;
434                case '"':
435                    cur += wxT("&#22;");
436                    continue;
437                default:
438                    if (ch >= 128) {
439                        cur += wxString::Format(wxT("&#%u;"), ch);
440                    } else {
441                        cur += (char)ch;
442                    }
443            }
444        }
445
446        size_t left = end - p;
447        end = buf + left;
448        if (left) memmove(buf, p, left);
449    }
450abort:
451
452    /* TRANSLATORS: Label for button in aven’s cavern log window which
453     * allows the user to save the log to a file. */
454    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
455                                  (int)LOG_SAVE,
456                                  wmsg(/*Save Log*/446).c_str()));
457    int retval = pclose(cavern_out);
458    if (retval) {
459        /* TRANSLATORS: Label for button in aven’s cavern log window which
460         * causes the survey data to be reprocessed. */
461        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
462                                      (int)LOG_REPROCESS,
463                                      wmsg(/*Reprocess*/184).c_str()));
464        if (retval == -1) {
465            wxString m = wxT("Problem running cavern: ");
466            m += wxString(strerror(errno), wxConvUTF8);
467            wxGetApp().ReportError(m);
468            return -2;
469        }
470        return -1;
471    }
472    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
473                                  (int)LOG_REPROCESS,
474                                  wmsg(/*Reprocess*/184).c_str()));
475    AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
476    Update();
477    init_done = false;
478    return link_count;
479}
480
481void
482CavernLogWindow::OnReprocess(wxCommandEvent & e)
483{
484    SetPage(wxString());
485    int result = process(filename);
486    if (result < 0) return;
487    mainfrm->AddToFileHistory(filename);
488    wxString file3d(filename, 0, filename.length() - 3);
489    file3d.append(wxT("3d"));
490    if (!mainfrm->LoadData(file3d, survey)) {
491        return;
492    }
493    if (result == 0) {
494        OnOK(e);
495    }
496}
497
498void
499CavernLogWindow::OnSave(wxCommandEvent &)
500{
501    wxString filelog(filename, 0, filename.length() - 3);
502    filelog += wxT("log");
503    AvenAllowOnTop ontop(mainfrm);
504#ifdef __WXMOTIF__
505    wxString ext(wxT("*.log"));
506#else
507    /* TRANSLATORS: Log files from cavern (extension .log) */
508    wxString ext = wmsg(/*Log files*/447);
509    ext += wxT("|*.log");
510#endif
511    wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
512                     wxString(), filelog, ext,
513                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
514    if (dlg.ShowModal() != wxID_OK) return;
515    filelog = dlg.GetPath();
516#ifdef __WXMSW__
517    FILE * fh_log = _wfopen(filelog.fn_str(), L"w");
518#else
519    FILE * fh_log = fopen(filelog.mb_str(), "w");
520#endif
521    if (!fh_log) {
522        wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
523        return;
524    }
525    fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
526    fclose(fh_log);
527}
528
529void
530CavernLogWindow::OnOK(wxCommandEvent &)
531{
532    if (init_done) {
533        mainfrm->HideLog(this);
534    } else {
535        mainfrm->InitialiseAfterLoad(filename);
536        init_done = true;
537    }
538}
Note: See TracBrowser for help on using the repository browser.