source: git/src/cavernlog.cc @ 736f7df

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-datawalls-data-hanging-as-warning
Last change on this file since 736f7df was 736f7df, checked in by Olly Betts <olly@…>, 10 years ago

lib/extract-msgs.pl,lib/survex.pot,src/: Insert "TRANSLATORS"
comments into source code.

  • Property mode set to 100644
File size: 10.7 KB
RevLine 
[6bec10c]1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
[3d3fb6c]4 * Copyright (C) 2005,2006,2010,2011,2012,2014 Olly Betts
[6bec10c]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"
[fb5887c]28#include "mainfrm.h"
[6bec10c]29#include "message.h"
30
[baeae66]31#include <errno.h>
[6bec10c]32#include <stdio.h>
[a90632c]33#include <stdlib.h>
[6bec10c]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
[81e1aa4]43enum { LOG_REPROCESS = 1234 };
[fb5887c]44
45BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
[81e1aa4]46    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
[fb5887c]47    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
48END_EVENT_TABLE()
49
[6bec10c]50static wxString escape_for_shell(wxString s, bool protect_dash = false)
51{
52    size_t p = 0;
53#ifdef __WXMSW__
54    bool needs_quotes = false;
55    while (p < s.size()) {
[eff69a7]56        wxChar ch = s[p];
57        if (ch < 127) {
58            if (ch == wxT('"')) {
[6baad4a]59                s.insert(p, 1, wxT('\\'));
[eff69a7]60                ++p;
61                needs_quotes = true;
62            } else if (strchr(" <>&|^", ch)) {
63                needs_quotes = true;
64            }
[6bec10c]65        }
66        ++p;
67    }
68    if (needs_quotes) {
[6baad4a]69        s.insert(0u, 1, wxT('"'));
[15322f2]70        s += wxT('"');
[6bec10c]71    }
72#else
73    if (protect_dash && !s.empty() && s[0u] == '-') {
74        // If the filename starts with a '-', protect it from being
75        // treated as an option by prepending "./".
[5627cbb]76        s.insert(0, wxT("./"));
[6bec10c]77        p = 2;
78    }
79    while (p < s.size()) {
80        // Exclude a few safe characters which are common in filenames
81        if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
[6baad4a]82            s.insert(p, 1, wxT('\\'));
[6bec10c]83            ++p;
84        }
85        ++p;
86    }
87#endif
88    return s;
89}
90
[fb5887c]91CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, wxWindow * parent)
92    : wxHtmlWindow(parent), mainfrm(mainfrm_)
93{
[93ff5cc]94    int fsize = parent->GetFont().GetPointSize();
95    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
[5627cbb]96    SetFonts(wxString(), wxString(), sizes);
[93ff5cc]97}
98
[6bec10c]99void
100CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
101{
102    wxString href = link.GetHref();
103    wxString title = link.GetTarget();
[5627cbb]104    size_t colon = href.rfind(wxT(':'));
[3d3fb6c]105    if (colon == wxString::npos)
106        return;
107    size_t colon2 = href.rfind(wxT(':'), colon - 1);
108    if (colon2 != wxString::npos) swap(colon, colon2);
[6bec10c]109#ifdef __WXMSW__
[3d3fb6c]110    wxString cmd = wxT("notepad $f");
[89a1b72]111#elif defined __WXMAC__
[3d3fb6c]112    wxString cmd = wxT("open -t $f");
[6bec10c]113#else
[3d3fb6c]114    wxString cmd = wxT("x-terminal-emulator -title $t -e vim +'call cursor($l,$c)' $f");
115    // wxString cmd = wxT("gedit -b $f +$l:$c $f");
116    // wxString cmd = wxT("x-terminal-emulator -title $t -e emacs +$l $f");
117    // wxString cmd = wxT("x-terminal-emulator -title $t -e nano +$l $f");
118    // wxString cmd = wxT("x-terminal-emulator -title $t -e jed -g $l $f");
[6bec10c]119#endif
[3d3fb6c]120    const char * p = getenv("SURVEXEDITOR");
121    if (p) {
122        cmd = wxString(p, wxConvUTF8);
123        if (!cmd.find(wxT("$f"))) {
124            cmd += wxT(" $f");
[6bec10c]125        }
[3d3fb6c]126    }
127    size_t i = 0;
128    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
129        if (++i >= cmd.size()) break;
130        switch ((int)cmd[i]) {
131            case wxT('$'):
132                cmd.erase(i, 1);
133                break;
134            case wxT('f'): {
135                wxString f = escape_for_shell(href.substr(0, colon), true);
136                cmd.replace(i - 1, 2, f);
137                i += f.size() - 1;
138                break;
139            }
140            case wxT('t'): {
141                wxString t = escape_for_shell(title);
142                cmd.replace(i - 1, 2, t);
143                i += t.size() - 1;
144                break;
145            }
146            case wxT('l'): {
147                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
148                cmd.replace(i - 1, 2, l);
149                i += l.size() - 1;
150                break;
[1d71195]151            }
[3d3fb6c]152            case wxT('c'): {
153                wxString l;
154                if (colon2 == wxString::npos)
155                    l = wxT("0");
156                else
157                    l = escape_for_shell(href.substr(colon2 + 1));
158                cmd.replace(i - 1, 2, l);
159                i += l.size() - 1;
160                break;
161            }
162            default:
163                ++i;
[6bec10c]164        }
[3d3fb6c]165    }
[fc364dc]166#ifdef __WXMSW__
[3d3fb6c]167    if (_wsystem(cmd.c_str()) >= 0)
168        return;
[fc364dc]169#else
[3d3fb6c]170    if (system(cmd.mb_str()) >= 0)
171        return;
[fc364dc]172#endif
[3d3fb6c]173    wxString m;
[736f7df]174    // TRANSLATORS: %s is replaced by the command we attempted to run.
[3d3fb6c]175    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
176    m += wxT(" (");
177    m += wxString(strerror(errno), wxConvUTF8);
178    m += wxT(')');
179    wxGetApp().ReportError(m);
[6bec10c]180}
181
[5627cbb]182int
[6bec10c]183CavernLogWindow::process(const wxString &file)
184{
[fb5887c]185    SetFocus();
186    filename = file;
187
[6bec10c]188#ifdef __WXMSW__
[15322f2]189    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
[6bec10c]190#else
[06b1227]191    setenv("SURVEX_UTF8", "1", 1);
[6bec10c]192#endif
[93ff5cc]193
[6bec10c]194    wxString escaped_file = escape_for_shell(file, true);
[a90632c]195#ifdef __WXMSW__
196    wxString cmd;
[9e50f755]197    {
[6baad4a]198        DWORD len = 256;
[6f8cb1c]199        wchar_t *buf = NULL;
[6baad4a]200        while (1) {
201            DWORD got;
202            buf = (wchar_t*)osrealloc(buf, len * 2);
203            got = GetModuleFileNameW(NULL, buf, len);
204            if (got < len) break;
205            len += len;
206        }
207        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
208        if (wcsncmp(buf, L"\\\\?\\", 4) == 0) buf += 4;
209        wchar_t * slash = wcsrchr(buf, L'\\');
[9e50f755]210        if (slash) {
[6baad4a]211            cmd.assign(buf, slash - buf + 1);
[9e50f755]212        }
[a90632c]213    }
214    cmd += L"cavern";
[9e50f755]215    cmd = escape_for_shell(cmd, false);
[a90632c]216#else
217    char *cavern = use_path(msg_exepth(), "cavern");
[2e4b8cd]218    wxString cmd = escape_for_shell(wxString(cavern, wxConvUTF8), false);
[08b4401]219    osfree(cavern);
[a90632c]220#endif
[5627cbb]221    cmd += wxT(" -o ");
[6bec10c]222    cmd += escaped_file;
[5627cbb]223    cmd += wxT(' ');
[6bec10c]224    cmd += escaped_file;
225
[fc364dc]226#ifdef __WXMSW__
227    FILE * cavern_out = _wpopen(cmd.c_str(), L"r");
228#else
[ffe7efe4]229    FILE * cavern_out = popen(cmd.mb_str(), "r");
[fc364dc]230#endif
[6bec10c]231    if (!cavern_out) {
[5627cbb]232        wxString m;
[3d3fb6c]233        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
[5627cbb]234        m += wxT(" (");
[2e4b8cd]235        m += wxString(strerror(errno), wxConvUTF8);
[5627cbb]236        m += wxT(')');
[6bec10c]237        wxGetApp().ReportError(m);
[5627cbb]238        return -2;
[6bec10c]239    }
240
241    int cavern_fd;
242#ifdef __WXMSW__
243    cavern_fd = _fileno(cavern_out);
244#else
245    cavern_fd = fileno(cavern_out);
246#endif
247    assert(cavern_fd < FD_SETSIZE); // FIXME we shouldn't just assert, but what else to do?
248    wxString cur;
[5627cbb]249    int link_count = 0;
[6bec10c]250    // We're only guaranteed one character of pushback by ungetc() but we
251    // need two so for portability we implement the second ourselves.
252    int left = EOF;
253    while (!feof(cavern_out)) {
254        fd_set rfds, efds;
255        FD_ZERO(&rfds);
256        FD_SET(cavern_fd, &rfds);
257        FD_ZERO(&efds);
258        FD_SET(cavern_fd, &efds);
[1e2499c]259        // Timeout instantly.
[6bec10c]260        struct timeval timeout;
261        timeout.tv_sec = 0;
[1e2499c]262        timeout.tv_usec = 0;
[6bec10c]263        if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
[1e2499c]264            Update();
265            FD_SET(cavern_fd, &rfds);
266            FD_SET(cavern_fd, &efds);
267            // Set timeout to 0.1 seconds.
268            timeout.tv_sec = 0;
269            timeout.tv_usec = 100000;
270            if (select(cavern_fd + 1, &rfds, NULL, &efds, &timeout) == 0) {
271                wxYield();
272                continue;
273            }
[6bec10c]274        }
[6b088f6]275        if (!FD_ISSET(cavern_fd, &rfds)) {
[6bec10c]276            // Error, which pclose() should report.
277            break;
278        }
279        int ch;
280        if (left == EOF) {
[0a85859]281            ch = GETC(cavern_out);
[6bec10c]282            if (ch == EOF) break;
283        } else {
284            ch = left;
285            left = EOF;
286        }
287        // Decode UTF-8 first to avoid security issues with <, >, &, etc
288        // encoded using multibyte encodings.
289        if (ch >= 0xc0 && ch < 0xf0) {
[0a85859]290            int ch1 = GETC(cavern_out);
[6bec10c]291            if ((ch1 & 0xc0) != 0x80) {
292                left = ch1;
293            } else if (ch < 0xe0) {
294                /* 2 byte sequence */
295                ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
296            } else {
297                /* 3 byte sequence */
[0a85859]298                int ch2 = GETC(cavern_out);
[6bec10c]299                if ((ch2 & 0xc0) != 0x80) {
300                    ungetc(ch2, cavern_out);
301                    left = ch1;
302                } else {
303                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6) | (ch2 & 0x3f);
304                }
305            }
306        }
307
308        switch (ch) {
309            case '\r':
310                // Ignore.
311                break;
312            case '\n': {
313                if (cur.empty()) continue;
[a07ee70]314#ifndef __WXMSW__
[6bec10c]315                size_t colon = cur.find(':');
[a07ee70]316#else
[6bec10c]317                // If the path is "C:\path\to\file.svx" then don't split at the
318                // : after the drive letter!  FIXME: better to look for ": "?
[a07ee70]319                size_t colon = cur.find(':', 2);
[6bec10c]320#endif
[bc8dfe7]321                if (colon != wxString::npos && colon < cur.size() - 2) {
[a07ee70]322                    ++colon;
323                    size_t i = colon;
[bc8dfe7]324                    while (i < cur.size() - 2 &&
[a07ee70]325                           cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
326                        ++i;
327                    }
328                    if (i > colon && cur[i] == wxT(':') ) {
329                        colon = i;
[da96015]330                        // Check for column number.
331                        while (++i < cur.size() - 2 &&
332                           cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
333                        if (i > colon + 1 && cur[i] == wxT(':') ) {
334                            colon = i;
335                        }
[5627cbb]336                        wxString tag = wxT("<a href=\"");
[a07ee70]337                        tag.append(cur, 0, colon);
338                        while (cur[++i] == wxT(' ')) { }
[5627cbb]339                        tag += wxT("\" target=\"");
[a07ee70]340                        tag.append(cur, i, wxString::npos);
[5627cbb]341                        tag += wxT("\">");
[6bec10c]342                        cur.insert(0, tag);
[a07ee70]343                        cur.insert(colon + tag.size(), wxT("</a>"));
[5627cbb]344                        ++link_count;
[6bec10c]345                    }
346                }
[93ff5cc]347
348                // Save the scrollbar positions.
349                int scroll_x = 0, scroll_y = 0;
350                GetViewStart(&scroll_x, &scroll_y);
351
[5627cbb]352                cur += wxT("<br>\n");
[6bec10c]353                AppendToPage(cur);
[93ff5cc]354
[5627cbb]355                if (!link_count) {
356                    // Auto-scroll the window until we've reported a warning or
357                    // error.
358                    int x, y;
359                    GetVirtualSize(&x, &y);
360                    int xs, ys;
361                    GetClientSize(&xs, &ys);
362                    y -= ys;
363                    int xu, yu;
364                    GetScrollPixelsPerUnit(&xu, &yu);
[a75f5a1]365                    Scroll(scroll_x, y / yu);
366                } else {
367                    // Restore the scrollbar positions.
368                    Scroll(scroll_x, scroll_y);
[5627cbb]369                }
[a75f5a1]370
[6bec10c]371                cur.clear();
372                break;
373            }
374            case '<':
[5627cbb]375                cur += wxT("&lt;");
[6bec10c]376                break;
377            case '>':
[5627cbb]378                cur += wxT("&gt;");
[6bec10c]379                break;
380            case '&':
[5627cbb]381                cur += wxT("&amp;");
[6bec10c]382                break;
[77b8654]383            case '"':
384                cur += wxT("&#22;");
385                continue;
[6bec10c]386            default:
387                if (ch >= 128) {
[5627cbb]388                    cur += wxString::Format(wxT("&#%u;"), ch);
[6bec10c]389                } else {
390                    cur += (char)ch;
391                }
392        }
393    }
[fb5887c]394
[93ff5cc]395    int retval = pclose(cavern_out);
396    if (retval) {
[736f7df]397        /* TRANSLATORS: Label for button in aven’s cavern log window which
398         * causes the survey data to be reprocessed. */
[2341559]399        AppendToPage(wxString::Format(wxT("<avenbutton default id=1234 name=\"%s\">"),
400                                      wmsg(/*Reprocess*/184).c_str()));
[93ff5cc]401        if (retval == -1) {
[5627cbb]402            wxString m = wxT("Problem running cavern: ");
403            m += wxString(strerror(errno), wxConvUTF8);
[93ff5cc]404            wxGetApp().ReportError(m);
[5627cbb]405            return -2;
[93ff5cc]406        }
[5627cbb]407        return -1;
[6bec10c]408    }
[fb5887c]409    if (link_count) {
[2341559]410        AppendToPage(wxString::Format(wxT("<avenbutton id=1234 name=\"%s\">"),
411                                      wmsg(/*Reprocess*/184).c_str()));
[fb5887c]412        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
413        Update();
414    }
[5627cbb]415    return link_count;
[6bec10c]416}
[fb5887c]417
418void
[81e1aa4]419CavernLogWindow::OnReprocess(wxCommandEvent & e)
[fb5887c]420{
421    SetPage(wxString());
422    if (process(filename) == 0) {
423        OnOK(e);
424    }
425}
426
427void
428CavernLogWindow::OnOK(wxCommandEvent &)
429{
430    mainfrm->InitialiseAfterLoad(filename);
431}
Note: See TracBrowser for help on using the repository browser.