• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 22918307859-2824

10 Mar 2026 06:29PM UTC coverage: 68.919% (-0.04%) from 68.957%
22918307859-2824

push

github

tstack
[cmds] make :write-jsonlines-to work for the log view

84 of 110 new or added lines in 2 files covered. (76.36%)

38 existing lines in 5 files now uncovered.

52198 of 75738 relevant lines covered (68.92%)

450029.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.67
/src/session_data.cc
1
/**
2
 * Copyright (c) 2013, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file session_data.cc
30
 */
31

32
#include <algorithm>
33
#include <utility>
34

35
#include "session_data.hh"
36

37
#include <glob.h>
38
#include <stdio.h>
39
#include <sys/types.h>
40
#include <yajl/api/yajl_tree.h>
41

42
#include "base/fs_util.hh"
43
#include "base/isc.hh"
44
#include "base/opt_util.hh"
45
#include "base/paths.hh"
46
#include "bookmarks.json.hh"
47
#include "bound_tags.hh"
48
#include "command_executor.hh"
49
#include "config.h"
50
#include "hasher.hh"
51
#include "lnav.events.hh"
52
#include "lnav.hh"
53
#include "lnav.indexing.hh"
54
#include "log_format_ext.hh"
55
#include "logfile.hh"
56
#include "service_tags.hh"
57
#include "sql_util.hh"
58
#include "sqlitepp.client.hh"
59
#include "tailer/tailer.looper.hh"
60
#include "timeline_source.hh"
61
#include "vtab_module.hh"
62
#include "yajlpp/yajlpp.hh"
63
#include "yajlpp/yajlpp_def.hh"
64

65
session_data_t session_data;
66
recent_refs_t recent_refs;
67

68
static const char* const LOG_METADATA_NAME = "log_metadata.db";
69

70
static const char* const META_TABLE_DEF = R"(
71
CREATE TABLE IF NOT EXISTS bookmarks (
72
    log_time datetime,
73
    log_format varchar(64),
74
    log_hash varchar(128),
75
    session_time integer,
76
    part_name text,
77
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
78
    comment text DEFAULT '',
79
    tags text DEFAULT '',
80
    annotations text DEFAULT NULL,
81
    log_opid text DEFAULT NULL,
82

83
    PRIMARY KEY (log_time, log_format, log_hash, session_time)
84
);
85

86
CREATE TABLE IF NOT EXISTS time_offset (
87
    log_time datetime,
88
    log_format varchar(64),
89
    log_hash varchar(128),
90
    session_time integer,
91
    offset_sec integer,
92
    offset_usec integer,
93
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
94

95
    PRIMARY KEY (log_time, log_format, log_hash, session_time)
96
);
97

98
CREATE TABLE IF NOT EXISTS recent_netlocs (
99
    netloc text,
100

101
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
102

103
    PRIMARY KEY (netloc)
104
);
105

106
CREATE TABLE IF NOT EXISTS regex101_entries (
107
    format_name text NOT NULL,
108
    regex_name text NOT NULL,
109
    permalink text NOT NULL,
110
    delete_code text NOT NULL,
111

112
    PRIMARY KEY (format_name, regex_name),
113

114
    CHECK(
115
       format_name  <> '' AND
116
       regex_name   <> '' AND
117
       permalink    <> '')
118
);
119
)";
120

121
static const char* const BOOKMARK_LRU_STMT
122
    = "DELETE FROM bookmarks WHERE access_time <= "
123
      "  (SELECT DISTINCT access_time FROM bookmarks "
124
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 50000)";
125

126
static const char* const NETLOC_LRU_STMT
127
    = "DELETE FROM recent_netlocs WHERE access_time <= "
128
      "  (SELECT DISTINCT access_time FROM bookmarks "
129
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 10)";
130

131
static const char* const UPGRADE_STMTS[] = {
132
    R"(ALTER TABLE bookmarks ADD COLUMN comment text DEFAULT '';)",
133
    R"(ALTER TABLE bookmarks ADD COLUMN tags text DEFAULT '';)",
134
    R"(ALTER TABLE bookmarks ADD COLUMN annotations text DEFAULT NULL;)",
135
    R"(ALTER TABLE bookmarks ADD COLUMN log_opid text DEFAULT NULL;)",
136
};
137

138
static constexpr size_t MAX_SESSIONS = 8;
139
static constexpr size_t MAX_SESSION_FILE_COUNT = 256;
140

141
struct session_line {
142
    session_line(struct timeval tv,
13✔
143
                 intern_string_t format_name,
144
                 std::string line_hash)
145
        : sl_time(tv), sl_format_name(format_name),
13✔
146
          sl_line_hash(std::move(line_hash))
13✔
147
    {
148
    }
13✔
149

150
    struct timeval sl_time;
151
    intern_string_t sl_format_name;
152
    std::string sl_line_hash;
153
};
154

155
static std::vector<session_line> marked_session_lines;
156
static std::vector<session_line> offset_session_lines;
157

158
static bool
159
bind_line(sqlite3* db,
24✔
160
          sqlite3_stmt* stmt,
161
          content_line_t cl,
162
          time_t session_time)
163
{
164
    auto& lss = lnav_data.ld_log_source;
24✔
165
    auto lf = lss.find(cl);
24✔
166

167
    if (lf == nullptr) {
24✔
168
        return false;
×
169
    }
170

171
    sqlite3_clear_bindings(stmt);
24✔
172

173
    auto line_iter = lf->begin() + cl;
24✔
174
    auto fr = lf->get_file_range(line_iter, false);
24✔
175
    auto read_result = lf->read_range(fr);
24✔
176

177
    if (read_result.isErr()) {
24✔
178
        return false;
×
179
    }
180

181
    auto line_hash = read_result
182
                         .map([cl](auto sbr) {
48✔
183
                             return hasher()
48✔
184
                                 .update(sbr.get_data(), sbr.length())
24✔
185
                                 .update(cl)
24✔
186
                                 .to_string();
48✔
187
                         })
188
                         .unwrap();
24✔
189

190
    return bind_values(stmt,
48✔
191
                       lf->original_line_time(line_iter),
192
                       lf->get_format()->get_name(),
48✔
193
                       line_hash,
194
                       session_time)
195
        == SQLITE_OK;
24✔
196
}
24✔
197

198
struct session_file_info {
199
    session_file_info(int timestamp, std::string id, std::string path)
50✔
200
        : sfi_timestamp(timestamp), sfi_id(std::move(id)),
50✔
201
          sfi_path(std::move(path))
50✔
202
    {
203
    }
50✔
204

205
    bool operator<(const session_file_info& other) const
78✔
206
    {
207
        if (this->sfi_timestamp < other.sfi_timestamp) {
78✔
208
            return true;
×
209
        }
210
        if (this->sfi_path < other.sfi_path) {
78✔
211
            return true;
×
212
        }
213
        return false;
78✔
214
    }
215

216
    int sfi_timestamp;
217
    std::string sfi_id;
218
    std::string sfi_path;
219
};
220

221
static void
222
cleanup_session_data()
26✔
223
{
224
    static_root_mem<glob_t, globfree> session_file_list;
26✔
225
    std::list<struct session_file_info> session_info_list;
26✔
226
    std::map<std::string, int> session_count;
26✔
227
    auto session_file_pattern = lnav::paths::dotlnav() / "*-*.ts*.json";
26✔
228

229
    if (glob(
26✔
230
            session_file_pattern.c_str(), 0, nullptr, session_file_list.inout())
231
        == 0)
26✔
232
    {
233
        for (size_t lpc = 0; lpc < session_file_list->gl_pathc; lpc++) {
73✔
234
            const char* path = session_file_list->gl_pathv[lpc];
50✔
235
            char hash_id[64];
236
            int timestamp;
237
            const char* base;
238

239
            base = strrchr(path, '/');
50✔
240
            if (base == nullptr) {
50✔
241
                continue;
×
242
            }
243
            base += 1;
50✔
244
            if (sscanf(base, "file-%63[^.].ts%d.json", hash_id, &timestamp)
50✔
245
                == 2)
50✔
246
            {
247
                session_count[hash_id] += 1;
×
248
                session_info_list.emplace_back(timestamp, hash_id, path);
×
249
            }
250
            if (sscanf(base,
50✔
251
                       "view-info-%63[^.].ts%d.ppid%*d.json",
252
                       hash_id,
253
                       &timestamp)
254
                == 2)
50✔
255
            {
256
                session_count[hash_id] += 1;
50✔
257
                session_info_list.emplace_back(timestamp, hash_id, path);
50✔
258
            }
259
        }
260
    }
261

262
    session_info_list.sort();
26✔
263

264
    size_t session_loops = 0;
26✔
265

266
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
26✔
267
        const session_file_info& front = session_info_list.front();
×
268

269
        session_loops += 1;
×
270
        if (session_loops < MAX_SESSION_FILE_COUNT
×
271
            && session_count[front.sfi_id] == 1)
×
272
        {
273
            session_info_list.splice(session_info_list.end(),
×
274
                                     session_info_list,
275
                                     session_info_list.begin());
×
276
        } else {
277
            if (remove(front.sfi_path.c_str()) != 0) {
×
278
                log_error("Unable to remove session file: %s -- %s",
×
279
                          front.sfi_path.c_str(),
280
                          strerror(errno));
281
            }
282
            session_count[front.sfi_id] -= 1;
×
283
            session_info_list.pop_front();
×
284
        }
285
    }
286

287
    session_info_list.sort();
26✔
288

289
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
26✔
290
        const session_file_info& front = session_info_list.front();
×
291

292
        if (remove(front.sfi_path.c_str()) != 0) {
×
293
            log_error("Unable to remove session file: %s -- %s",
×
294
                      front.sfi_path.c_str(),
295
                      strerror(errno));
296
        }
297
        session_count[front.sfi_id] -= 1;
×
298
        session_info_list.pop_front();
×
299
    }
300
}
26✔
301

302
void
303
init_session()
636✔
304
{
305
    lnav_data.ld_session_time = time(nullptr);
636✔
306
    lnav_data.ld_session_id.clear();
636✔
307
    session_data.sd_view_states[LNV_LOG].vs_top = -1;
636✔
308
}
636✔
309

310
static std::optional<std::string>
311
compute_session_id()
49✔
312
{
313
    bool has_files = false;
49✔
314
    hasher h;
49✔
315

316
    for (auto& ld_file_name : lnav_data.ld_active_files.fc_file_names) {
99✔
317
        if (!ld_file_name.second.loo_include_in_session) {
50✔
318
            continue;
×
319
        }
320
        has_files = true;
50✔
321
        h.update(ld_file_name.first);
50✔
322
    }
323
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
99✔
324
        if (lf->is_valid_filename()) {
50✔
325
            continue;
49✔
326
        }
327
        if (!lf->get_open_options().loo_include_in_session) {
1✔
328
            continue;
×
329
        }
330

331
        has_files = true;
1✔
332
        h.update(lf->get_content_id());
1✔
333
    }
334
    if (!has_files) {
49✔
335
        return std::nullopt;
3✔
336
    }
337

338
    return h.to_string();
46✔
339
}
340

341
std::optional<session_pair_t>
342
scan_sessions()
26✔
343
{
344
    static_root_mem<glob_t, globfree> view_info_list;
26✔
345

346
    cleanup_session_data();
26✔
347

348
    const auto session_id = compute_session_id();
26✔
349
    if (!session_id) {
26✔
350
        return std::nullopt;
1✔
351
    }
352
    auto& session_file_names = lnav_data.ld_session_id[session_id.value()];
25✔
353
    session_file_names.clear();
25✔
354

355
    auto view_info_pattern_base
356
        = fmt::format(FMT_STRING("view-info-{}.*.json"), session_id.value());
75✔
357
    auto view_info_pattern = lnav::paths::dotlnav() / view_info_pattern_base;
25✔
358
    if (glob(view_info_pattern.c_str(), 0, nullptr, view_info_list.inout())
25✔
359
        == 0)
25✔
360
    {
361
        for (size_t lpc = 0; lpc < view_info_list->gl_pathc; lpc++) {
59✔
362
            const char* path = view_info_list->gl_pathv[lpc];
36✔
363
            int timestamp, ppid, rc;
364
            const char* base;
365

366
            base = strrchr(path, '/');
36✔
367
            if (base == nullptr) {
36✔
368
                continue;
×
369
            }
370
            base += 1;
36✔
371
            if ((rc = sscanf(base,
36✔
372
                             "view-info-%*[^.].ts%d.ppid%d.json",
373
                             &timestamp,
374
                             &ppid))
375
                == 2)
36✔
376
            {
377
                ppid_time_pair_t ptp;
36✔
378

379
                ptp.first = (ppid == getppid()) ? 1 : 0;
36✔
380
                ptp.second = timestamp;
36✔
381
                session_file_names.emplace_back(ptp, path);
36✔
382
            }
383
        }
384
    }
385

386
    session_file_names.sort();
25✔
387

388
    while (session_file_names.size() > MAX_SESSIONS) {
25✔
389
        const std::string& name = session_file_names.front().second;
×
390

391
        if (remove(name.c_str()) != 0) {
×
392
            log_error("Unable to remove session: %s -- %s",
×
393
                      name.c_str(),
394
                      strerror(errno));
395
        }
396
        session_file_names.pop_front();
×
397
    }
398

399
    if (session_file_names.empty()) {
25✔
400
        return std::nullopt;
2✔
401
    }
402

403
    return std::make_optional(session_file_names.back());
23✔
404
}
26✔
405

406
void
407
load_time_bookmarks()
26✔
408
{
409
    static const char* const BOOKMARK_STMT = R"(
410
       SELECT
411
         log_time,
412
         log_format,
413
         log_hash,
414
         session_time,
415
         part_name,
416
         access_time,
417
         comment,
418
         tags,
419
         annotations,
420
         log_opid,
421
         session_time=? AS same_session
422
       FROM bookmarks WHERE
423
         log_time BETWEEN ? AND ? AND
424
         log_format = ?
425
       ORDER BY same_session DESC, session_time DESC
426
)";
427

428
    static const char* const TIME_OFFSET_STMT = R"(
429
       SELECT
430
         *,
431
         session_time=? AS same_session
432
       FROM time_offset
433
       WHERE
434
         log_hash = ? AND
435
         log_time BETWEEN ? AND ? AND
436
         log_format = ?
437
       ORDER BY
438
         same_session DESC,
439
         session_time DESC
440
)";
441

442
    static auto op = lnav_operation{__FUNCTION__};
26✔
443

444
    auto op_guard = lnav_opid_guard::internal(op);
26✔
445
    auto& lss = lnav_data.ld_log_source;
26✔
446
    auto_sqlite3 db;
26✔
447
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
26✔
448
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
26✔
449
    bool reload_needed = false;
26✔
450
    auto_mem<char, sqlite3_free> errmsg;
26✔
451

452
    log_info("loading bookmark db: %s", db_path.c_str());
26✔
453

454
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
26✔
455
        return;
×
456
    }
457

458
    for (const char* upgrade_stmt : UPGRADE_STMTS) {
130✔
459
        auto rc = sqlite3_exec(
104✔
460
            db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out());
461
        if (rc != SQLITE_OK) {
104✔
462
            auto exterr = sqlite3_extended_errcode(db.in());
104✔
463
            log_error("unable to upgrade bookmark table -- (%d/%d) %s",
104✔
464
                      rc,
465
                      exterr,
466
                      errmsg.in());
467
        }
468
    }
469

470
    {
471
        auto netloc_prep_res
472
            = prepare_stmt(db.in(), "SELECT netloc FROM recent_netlocs");
26✔
473
        if (netloc_prep_res.isErr()) {
26✔
474
            log_error("unable to get netlocs: %s",
2✔
475
                      netloc_prep_res.unwrapErr().c_str());
476
            return;
2✔
477
        }
478

479
        auto netloc_stmt = netloc_prep_res.unwrap();
24✔
480
        bool done = false;
24✔
481

482
        while (!done) {
48✔
483
            done = netloc_stmt.fetch_row<std::string>().match(
48✔
484
                [](const std::string& netloc) {
×
485
                    recent_refs.rr_netlocs.insert(netloc);
×
486
                    return false;
×
487
                },
488
                [](const prepared_stmt::fetch_error& fe) {
×
489
                    log_error("failed to fetch netloc row: %s",
×
490
                              fe.fe_msg.c_str());
491
                    return true;
×
492
                },
493
                [](prepared_stmt::end_of_rows) { return true; });
48✔
494
        }
495
    }
26✔
496

497
    log_info("BEGIN select bookmarks");
24✔
498
    if (sqlite3_prepare_v2(db.in(), BOOKMARK_STMT, -1, stmt.out(), nullptr)
24✔
499
        != SQLITE_OK)
24✔
500
    {
501
        log_error("could not prepare bookmark select statement -- %s",
×
502
                  sqlite3_errmsg(db));
503
        return;
×
504
    }
505

506
    for (auto file_iter = lnav_data.ld_log_source.begin();
24✔
507
         file_iter != lnav_data.ld_log_source.end();
49✔
508
         ++file_iter)
25✔
509
    {
510
        auto lf = (*file_iter)->get_file();
25✔
511
        if (lf == nullptr) {
25✔
512
            continue;
×
513
        }
514
        if (lf->size() == 0) {
25✔
515
            continue;
×
516
        }
517
        const auto* format = lf->get_format_ptr();
25✔
518
        content_line_t base_content_line;
25✔
519

520
        base_content_line = lss.get_file_base_content_line(file_iter);
25✔
521

522
        auto low_line_iter = lf->begin();
25✔
523
        auto high_line_iter = lf->end();
25✔
524

525
        --high_line_iter;
25✔
526

527
        if (bind_values(stmt.in(),
25✔
528
                        lnav_data.ld_session_load_time,
529
                        lf->original_line_time(low_line_iter),
530
                        lf->original_line_time(high_line_iter),
531
                        lf->get_format()->get_name())
50✔
532
            != SQLITE_OK)
25✔
533
        {
534
            return;
×
535
        }
536

537
        date_time_scanner dts;
25✔
538
        bool done = false;
25✔
539
        int64_t last_mark_time = -1;
25✔
540

541
        while (!done) {
89✔
542
            int rc = sqlite3_step(stmt.in());
64✔
543

544
            switch (rc) {
64✔
545
                case SQLITE_OK:
25✔
546
                case SQLITE_DONE:
547
                    done = true;
25✔
548
                    break;
25✔
549

550
                case SQLITE_ROW: {
39✔
551
                    const char* log_time
552
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
39✔
553
                    const char* log_hash
554
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
39✔
555
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
39✔
556
                    const char* part_name
557
                        = (const char*) sqlite3_column_text(stmt.in(), 4);
39✔
558
                    const char* comment
559
                        = (const char*) sqlite3_column_text(stmt.in(), 6);
39✔
560
                    const char* tags
561
                        = (const char*) sqlite3_column_text(stmt.in(), 7);
39✔
562
                    const auto annotations = sqlite3_column_text(stmt.in(), 8);
39✔
563
                    const auto log_opid = sqlite3_column_text(stmt.in(), 9);
39✔
564
                    timeval log_tv;
565
                    exttm log_tm;
39✔
566

567
                    if (last_mark_time == -1) {
39✔
568
                        last_mark_time = mark_time;
24✔
569
                    } else if (last_mark_time != mark_time) {
15✔
570
                        done = true;
×
571
                        continue;
20✔
572
                    }
573

574
                    if (part_name == nullptr) {
39✔
575
                        continue;
20✔
576
                    }
577

578
                    if (dts.scan(log_time,
19✔
579
                                 strlen(log_time),
580
                                 nullptr,
581
                                 &log_tm,
582
                                 log_tv)
583
                        == nullptr)
19✔
584
                    {
585
                        log_warning("bad log time: %s", log_time);
×
586
                        continue;
×
587
                    }
588

589
                    auto line_iter = format->lf_time_ordered
19✔
590
                        ? std::lower_bound(lf->begin(), lf->end(), log_tv)
19✔
591
                        : lf->begin();
1✔
592
                    while (line_iter != lf->end()) {
74✔
593
                        const auto line_tv = line_iter->get_timeval();
74✔
594

595
                        // NB: only milliseconds were stored in the DB, but the
596
                        // internal rep stores micros now.
597
                        if (line_tv.tv_sec != log_tv.tv_sec
74✔
598
                            || line_tv.tv_usec / 1000 != log_tv.tv_usec / 1000)
39✔
599
                        {
600
                            if (format->lf_time_ordered) {
53✔
601
                                break;
19✔
602
                            }
603
                            ++line_iter;
53✔
604
                            continue;
55✔
605
                        }
606

607
                        auto cl = content_line_t(
608
                            std::distance(lf->begin(), line_iter));
42✔
609
                        auto fr = lf->get_file_range(line_iter, false);
21✔
610
                        auto read_result = lf->read_range(fr);
21✔
611

612
                        if (read_result.isErr()) {
21✔
613
                            break;
×
614
                        }
615

616
                        auto sbr = read_result.unwrap();
21✔
617

618
                        auto line_hash
619
                            = hasher()
21✔
620
                                  .update(sbr.get_data(), sbr.length())
21✔
621
                                  .update(cl)
21✔
622
                                  .to_string();
21✔
623

624
                        if (line_hash != log_hash) {
21✔
625
                            // Using the formatted line for JSON-lines logs was
626
                            // a mistake in earlier versions. To carry forward
627
                            // older bookmarks, we need to replicate the bad
628
                            // behavior.
629
                            auto hack_read_res
630
                                = lf->read_line(line_iter, {false, true});
2✔
631
                            if (hack_read_res.isErr()) {
2✔
632
                                break;
×
633
                            }
634
                            auto hack_sbr = hack_read_res.unwrap();
2✔
635
                            auto hack_hash = hasher()
2✔
636
                                                 .update(hack_sbr.get_data(),
2✔
637
                                                         hack_sbr.length())
638
                                                 .update(cl)
2✔
639
                                                 .to_string();
2✔
640
                            if (hack_hash == log_hash) {
2✔
641
                                log_trace("needed hack to match line: %s:%d",
×
642
                                          lf->get_filename_as_string().c_str(),
643
                                          (int) cl);
644
                            } else {
645
                                ++line_iter;
2✔
646
                                continue;
2✔
647
                            }
648
                        }
6✔
649
                        auto& bm_meta = lf->get_bookmark_metadata();
19✔
650
                        auto line_number = static_cast<uint32_t>(
651
                            std::distance(lf->begin(), line_iter));
19✔
652
                        content_line_t line_cl
653
                            = content_line_t(base_content_line + line_number);
19✔
654
                        bool meta = false;
19✔
655

656
                        if (part_name != nullptr && part_name[0] != '\0') {
19✔
657
                            lss.set_user_mark(&textview_curses::BM_PARTITION,
7✔
658
                                              line_cl);
659
                            bm_meta[line_number].bm_name = part_name;
7✔
660
                            meta = true;
7✔
661
                        }
662
                        if (comment != nullptr && comment[0] != '\0') {
19✔
663
                            lss.set_user_mark(&textview_curses::BM_META,
7✔
664
                                              line_cl);
665
                            bm_meta[line_number].bm_comment = comment;
7✔
666
                            meta = true;
7✔
667
                        }
668
                        if (tags != nullptr && tags[0] != '\0') {
19✔
669
                            auto_mem<yajl_val_s> tag_list(yajl_tree_free);
9✔
670
                            char error_buffer[1024];
671

672
                            tag_list = yajl_tree_parse(
673
                                tags, error_buffer, sizeof(error_buffer));
9✔
674
                            if (!YAJL_IS_ARRAY(tag_list.in())) {
9✔
675
                                log_error("invalid tags column: %s", tags);
×
676
                            } else {
677
                                lss.set_user_mark(&textview_curses::BM_META,
9✔
678
                                                  line_cl);
679
                                for (size_t lpc = 0;
18✔
680
                                     lpc < tag_list.in()->u.array.len;
18✔
681
                                     lpc++)
682
                                {
683
                                    yajl_val elem
684
                                        = tag_list.in()->u.array.values[lpc];
9✔
685

686
                                    if (!YAJL_IS_STRING(elem)) {
9✔
687
                                        continue;
×
688
                                    }
689
                                    bookmark_metadata::KNOWN_TAGS.insert(
9✔
690
                                        elem->u.string);
9✔
691
                                    bm_meta[line_number].add_tag(
27✔
692
                                        elem->u.string);
9✔
693
                                }
694
                            }
695
                            meta = true;
9✔
696
                        }
9✔
697
                        if (annotations != nullptr && annotations[0] != '\0') {
19✔
698
                            static const intern_string_t SRC
699
                                = intern_string::lookup("annotations");
3✔
700

701
                            const auto anno_sf
702
                                = string_fragment::from_c_str(annotations);
1✔
703
                            auto parse_res
704
                                = logmsg_annotations_handlers.parser_for(SRC)
2✔
705
                                      .of(anno_sf);
1✔
706
                            if (parse_res.isErr()) {
1✔
707
                                log_error(
×
708
                                    "unable to parse annotations JSON -- "
709
                                    "%s",
710
                                    parse_res.unwrapErr()[0]
711
                                        .to_attr_line()
712
                                        .get_string()
713
                                        .c_str());
714
                            } else if (bm_meta.find(line_number)
1✔
715
                                       == bm_meta.end())
2✔
716
                            {
717
                                lss.set_user_mark(&textview_curses::BM_META,
1✔
718
                                                  line_cl);
719
                                bm_meta[line_number].bm_annotations
1✔
720
                                    = parse_res.unwrap();
2✔
721
                                meta = true;
1✔
722
                            } else {
723
                                meta = true;
×
724
                            }
725
                        }
1✔
726
                        if (log_opid != nullptr && log_opid[0] != '\0') {
19✔
727
                            auto opid_sf
728
                                = string_fragment::from_c_str(log_opid);
×
729
                            lf->set_logline_opid(line_number, opid_sf);
×
730
                            meta = true;
×
731
                        }
732
                        if (!meta) {
19✔
733
                            marked_session_lines.emplace_back(
2✔
734
                                lf->original_line_time(line_iter),
2✔
735
                                format->get_name(),
2✔
736
                                line_hash);
737
                            lss.set_user_mark(&textview_curses::BM_USER,
2✔
738
                                              line_cl);
739
                        }
740
                        reload_needed = true;
19✔
741
                        break;
19✔
742
                    }
63✔
743
                    break;
19✔
744
                }
745

746
                default: {
×
747
                    const char* errmsg;
748

749
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
750
                    log_error(
×
751
                        "bookmark select error: code %d -- %s", rc, errmsg);
752
                    done = true;
×
753
                } break;
×
754
            }
755
        }
756

757
        sqlite3_reset(stmt.in());
25✔
758
    }
25✔
759
    log_info("END select bookmarks");
24✔
760

761
    log_info("BEGIN select time_offset");
24✔
762
    if (sqlite3_prepare_v2(db.in(), TIME_OFFSET_STMT, -1, stmt.out(), nullptr)
24✔
763
        != SQLITE_OK)
24✔
764
    {
765
        log_error("could not prepare time_offset select statement -- %s",
×
766
                  sqlite3_errmsg(db));
767
        return;
×
768
    }
769

770
    for (auto file_iter = lnav_data.ld_log_source.begin();
24✔
771
         file_iter != lnav_data.ld_log_source.end();
49✔
772
         ++file_iter)
25✔
773
    {
774
        auto lf = (*file_iter)->get_file();
25✔
775
        content_line_t base_content_line;
25✔
776

777
        if (lf == nullptr) {
25✔
778
            continue;
×
779
        }
780
        if (lf->size() == 0) {
25✔
781
            continue;
×
782
        }
783

784
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
25✔
785

786
        auto low_line_iter = lf->begin();
25✔
787
        auto high_line_iter = lf->end();
25✔
788

789
        --high_line_iter;
25✔
790

791
        if (bind_values(stmt.in(),
50✔
792
                        lnav_data.ld_session_load_time,
793
                        lf->get_content_id(),
25✔
794
                        lf->original_line_time(low_line_iter),
795
                        lf->original_line_time(high_line_iter),
796
                        lf->get_format()->get_name())
50✔
797
            != SQLITE_OK)
25✔
798
        {
799
            return;
×
800
        }
801

802
        date_time_scanner dts;
25✔
803
        auto done = false;
25✔
804
        int64_t last_mark_time = -1;
25✔
805

806
        while (!done) {
74✔
807
            const auto rc = sqlite3_step(stmt.in());
49✔
808

809
            switch (rc) {
49✔
810
                case SQLITE_OK:
25✔
811
                case SQLITE_DONE:
812
                    done = true;
25✔
813
                    break;
25✔
814

815
                case SQLITE_ROW: {
24✔
816
                    const auto* log_time
817
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
24✔
818
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
24✔
819
                    timeval log_tv;
820
                    exttm log_tm;
24✔
821

822
                    if (last_mark_time == -1) {
24✔
823
                        last_mark_time = mark_time;
24✔
824
                    } else if (last_mark_time != mark_time) {
×
825
                        done = true;
×
826
                        continue;
21✔
827
                    }
828

829
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
24✔
830
                        continue;
21✔
831
                    }
832

833
                    if (!dts.scan(log_time,
3✔
834
                                  strlen(log_time),
835
                                  nullptr,
836
                                  &log_tm,
837
                                  log_tv))
838
                    {
839
                        continue;
×
840
                    }
841

842
                    auto line_iter
843
                        = lower_bound(lf->begin(), lf->end(), log_tv);
3✔
844
                    while (line_iter != lf->end()) {
6✔
845
                        auto line_tv = line_iter->get_timeval();
6✔
846

847
                        if ((line_tv.tv_sec != log_tv.tv_sec)
6✔
848
                            || (line_tv.tv_usec / 1000
3✔
849
                                != log_tv.tv_usec / 1000))
3✔
850
                        {
851
                            break;
852
                        }
853

854
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
855
                        timeval offset;
856

857
                        offset_session_lines.emplace_back(
3✔
858
                            lf->original_line_time(line_iter),
3✔
859
                            lf->get_format_ptr()->get_name(),
3✔
860
                            lf->get_content_id());
3✔
861
                        offset.tv_sec = sqlite3_column_int64(stmt.in(), 4);
3✔
862
                        offset.tv_usec = sqlite3_column_int64(stmt.in(), 5);
3✔
863
                        lf->adjust_content_time(file_line, offset);
3✔
864

865
                        reload_needed = true;
3✔
866

867
                        ++line_iter;
3✔
868
                    }
869
                    break;
3✔
870
                }
871

872
                default: {
×
873
                    const auto* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
874
                    log_error(
×
875
                        "bookmark select error: code %d -- %s", rc, errmsg);
876
                    done = true;
×
877
                    break;
×
878
                }
879
            }
880
        }
881

882
        sqlite3_reset(stmt.in());
25✔
883
    }
25✔
884
    log_info("END select time_offset");
24✔
885

886
    if (reload_needed) {
24✔
887
        lnav_data.ld_views[LNV_LOG].reload_data();
16✔
888
    }
889
}
34✔
890

891
static int
892
read_files(yajlpp_parse_context* ypc,
22✔
893
           const unsigned char* str,
894
           size_t len,
895
           yajl_string_props_t*)
896
{
897
    return 1;
22✔
898
}
899

900
const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
901
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
902
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
903
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
904
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
905
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
906
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
907
    {level_names[LEVEL_INFO], LEVEL_INFO},
908
    {level_names[LEVEL_STATS], LEVEL_STATS},
909
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
910
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
911
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
912
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
913
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
914

915
    json_path_handler_base::ENUM_TERMINATOR,
916
};
917

918
static const json_path_container view_def_handlers = {
919
    json_path_handler("top_line").for_field(&view_state::vs_top),
920
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
921
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
922
    json_path_handler("search").for_field(&view_state::vs_search),
923
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
924
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
925
    json_path_handler("min_level")
926
        .with_enum_values(LEVEL_ENUM)
927
        .for_field(&view_state::vs_min_log_level),
928
    json_path_handler("commands#").for_field(&view_state::vs_commands),
929
};
930

931
static const json_path_container view_handlers = {
932
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
933
        .with_obj_provider<view_state, session_data_t>(
934
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
1,679✔
935
                auto view_index_opt
936
                    = view_from_string(ypc.get_substr("view_name").c_str());
1,679✔
937
                if (view_index_opt) {
1,679✔
938
                    return &root->sd_view_states[view_index_opt.value()];
1,679✔
939
                }
940

941
                log_error("unknown view name: %s",
×
942
                          ypc.get_substr("view_name").c_str());
943
                static view_state dummy;
944
                return &dummy;
×
945
            })
946
        .with_children(view_def_handlers),
947
};
948

949
static const json_path_container file_state_handlers = {
950
    yajlpp::property_handler("visible")
951
        .with_description("Indicates whether the file is visible or not")
952
        .for_field(&file_state::fs_is_visible),
953
};
954

955
static const json_path_container file_states_handlers = {
956
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
957
        .with_description("Map of file names to file state objects")
958
        .with_obj_provider<file_state, session_data_t>(
959
            [](const auto& ypc, session_data_t* root) {
66✔
960
                auto fn = ypc.get_substr("filename");
66✔
961
                return &root->sd_file_states[fn];
132✔
962
            })
66✔
963
        .with_children(file_state_handlers),
964
};
965

966
static const typed_json_path_container<session_data_t> view_info_handlers = {
967
    yajlpp::property_handler("save-time")
968
        .for_field(&session_data_t::sd_save_time),
969
    yajlpp::property_handler("time-offset")
970
        .for_field(&session_data_t::sd_time_offset),
971
    json_path_handler("files#", read_files),
972
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
973
    yajlpp::property_handler("views").with_children(view_handlers),
974
};
975

976
void
977
load_session()
26✔
978
{
979
    static auto op = lnav_operation{"load_session"};
26✔
980

981
    auto op_guard = lnav_opid_guard::internal(op);
26✔
982
    log_info("BEGIN load_session");
26✔
983
    scan_sessions() | [](const auto pair) {
26✔
984
        lnav_data.ld_session_load_time = pair.first.second;
23✔
985
        const auto& view_info_path = pair.second;
23✔
986
        auto view_info_src = intern_string::lookup(view_info_path.string());
23✔
987

988
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
23✔
989
        if (open_res.isErr()) {
23✔
990
            log_error("cannot open session file: %s -- %s",
×
991
                      view_info_path.c_str(),
992
                      open_res.unwrapErr().c_str());
993
            return;
×
994
        }
995

996
        auto fd = open_res.unwrap();
23✔
997
        unsigned char buffer[1024];
998
        ssize_t rc;
999

1000
        log_info("loading session file: %s", view_info_path.c_str());
23✔
1001
        auto parser = view_info_handlers.parser_for(view_info_src);
23✔
1002
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
71✔
1003
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
24✔
1004
            auto parse_res = parser.consume(buf_frag);
24✔
1005
            if (parse_res.isErr()) {
24✔
1006
                log_error("failed to load session: %s -- %s",
×
1007
                          view_info_path.c_str(),
1008
                          parse_res.unwrapErr()[0]
1009
                              .to_attr_line()
1010
                              .get_string()
1011
                              .c_str());
1012
                return;
×
1013
            }
1014
        }
1015

1016
        auto complete_res = parser.complete();
23✔
1017
        if (complete_res.isErr()) {
23✔
1018
            log_error("failed to load session: %s -- %s",
×
1019
                      view_info_path.c_str(),
1020
                      complete_res.unwrapErr()[0]
1021
                          .to_attr_line()
1022
                          .get_string()
1023
                          .c_str());
1024
            return;
×
1025
        }
1026
        session_data = complete_res.unwrap();
23✔
1027

1028
        bool log_changes = false;
23✔
1029

1030
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
47✔
1031
            auto iter = session_data.sd_file_states.find(lf->get_filename());
24✔
1032

1033
            if (iter == session_data.sd_file_states.end()) {
24✔
1034
                continue;
2✔
1035
            }
1036

1037
            log_debug("found state for file: %s %d (%s)",
22✔
1038
                      lf->get_content_id().c_str(),
1039
                      iter->second.fs_is_visible,
1040
                      lf->get_filename_as_string().c_str());
1041
            lnav_data.ld_log_source.find_data(lf) |
22✔
1042
                [iter, &log_changes](auto ld) {
44✔
1043
                    if (ld->ld_visible != iter->second.fs_is_visible) {
22✔
1044
                        ld->get_file_ptr()->set_indexing(
1✔
1045
                            iter->second.fs_is_visible);
1✔
1046
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1047
                        log_changes = true;
1✔
1048
                    }
1049
                };
1050
        }
1051

1052
        if (log_changes) {
23✔
1053
            lnav_data.ld_log_source.text_filters_changed();
1✔
1054
        }
1055
    };
23✔
1056

1057
    lnav::events::publish(lnav_data.ld_db.in(),
26✔
1058
                          lnav::events::session::loaded{});
1059

1060
    log_info("END load_session");
26✔
1061
}
26✔
1062

1063
static void
1064
yajl_writer(void* context, const char* str, size_t len)
8,267✔
1065
{
1066
    FILE* file = (FILE*) context;
8,267✔
1067

1068
    fwrite(str, len, 1, file);
8,267✔
1069
}
8,267✔
1070

1071
static void
1072
save_user_bookmarks(sqlite3* db,
23✔
1073
                    sqlite3_stmt* stmt,
1074
                    bookmark_vector<content_line_t>& user_marks)
1075
{
1076
    auto& lss = lnav_data.ld_log_source;
23✔
1077

1078
    for (auto iter = user_marks.bv_tree.begin();
23✔
1079
         iter != user_marks.bv_tree.end();
24✔
1080
         ++iter)
1✔
1081
    {
1082
        content_line_t cl = *iter;
1✔
1083
        auto lf = lss.find(cl);
1✔
1084
        if (lf == nullptr) {
1✔
1085
            continue;
×
1086
        }
1087

1088
        sqlite3_clear_bindings(stmt);
1✔
1089

1090
        const auto line_iter = lf->begin() + cl;
1✔
1091
        auto fr = lf->get_file_range(line_iter, false);
1✔
1092
        auto read_result = lf->read_range(fr);
1✔
1093

1094
        if (read_result.isErr()) {
1✔
1095
            continue;
×
1096
        }
1097

1098
        auto line_hash = read_result
1099
                             .map([cl](auto sbr) {
2✔
1100
                                 return hasher()
2✔
1101
                                     .update(sbr.get_data(), sbr.length())
1✔
1102
                                     .update(cl)
1✔
1103
                                     .to_string();
2✔
1104
                             })
1105
                             .unwrap();
1✔
1106

1107
        if (bind_values(stmt,
3✔
1108
                        lf->original_line_time(line_iter),
1109
                        lf->get_format()->get_name(),
2✔
1110
                        line_hash,
1111
                        lnav_data.ld_session_time)
1112
            != SQLITE_OK)
1✔
1113
        {
1114
            continue;
×
1115
        }
1116

1117
        if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT) != SQLITE_OK) {
1✔
1118
            log_error("could not bind log hash -- %s", sqlite3_errmsg(db));
×
1119
            return;
×
1120
        }
1121

1122
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1123
            log_error("could not execute bookmark insert statement -- %s",
×
1124
                      sqlite3_errmsg(db));
1125
            return;
×
1126
        }
1127

1128
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1129
                                          lf->get_format_ptr()->get_name(),
1✔
1130
                                          line_hash);
1131

1132
        sqlite3_reset(stmt);
1✔
1133
    }
1✔
1134
}
1135

1136
static void
1137
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
24✔
1138
{
1139
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
31✔
1140
        auto cl = content_line_t(bm_pair.first);
7✔
1141
        sqlite3_clear_bindings(stmt);
7✔
1142

1143
        auto line_iter = lf->begin() + cl;
7✔
1144
        auto fr = lf->get_file_range(line_iter, false);
7✔
1145
        auto read_result = lf->read_range(fr);
7✔
1146

1147
        if (read_result.isErr()) {
7✔
1148
            continue;
×
1149
        }
1150

1151
        auto line_hash = read_result
1152
                             .map([cl](auto sbr) {
14✔
1153
                                 return hasher()
14✔
1154
                                     .update(sbr.get_data(), sbr.length())
7✔
1155
                                     .update(cl)
7✔
1156
                                     .to_string();
14✔
1157
                             })
1158
                             .unwrap();
7✔
1159

1160
        if (bind_values(stmt,
21✔
1161
                        lf->original_line_time(line_iter),
1162
                        lf->get_format()->get_name(),
14✔
1163
                        line_hash,
1164
                        lnav_data.ld_session_time)
1165
            != SQLITE_OK)
7✔
1166
        {
1167
            continue;
×
1168
        }
1169

1170
        const auto& line_meta = bm_pair.second;
7✔
1171
        if (line_meta.empty(bookmark_metadata::categories::any)) {
7✔
1172
            continue;
×
1173
        }
1174

1175
        if (sqlite3_bind_text(stmt,
7✔
1176
                              5,
1177
                              line_meta.bm_name.c_str(),
1178
                              line_meta.bm_name.length(),
7✔
1179
                              SQLITE_TRANSIENT)
1180
            != SQLITE_OK)
7✔
1181
        {
1182
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1183
            return;
×
1184
        }
1185

1186
        if (sqlite3_bind_text(stmt,
7✔
1187
                              6,
1188
                              line_meta.bm_comment.c_str(),
1189
                              line_meta.bm_comment.length(),
7✔
1190
                              SQLITE_TRANSIENT)
1191
            != SQLITE_OK)
7✔
1192
        {
1193
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1194
            return;
×
1195
        }
1196

1197
        std::string tags;
7✔
1198

1199
        if (!line_meta.bm_tags.empty()) {
7✔
1200
            yajlpp_gen gen;
4✔
1201

1202
            yajl_gen_config(gen, yajl_gen_beautify, false);
4✔
1203

1204
            {
1205
                yajlpp_array arr(gen);
4✔
1206

1207
                for (const auto& str : line_meta.bm_tags) {
8✔
1208
                    arr.gen(str);
4✔
1209
                }
1210
            }
4✔
1211

1212
            tags = gen.to_string_fragment().to_string();
4✔
1213
        }
4✔
1214

1215
        if (sqlite3_bind_text(
7✔
1216
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
7✔
1217
            != SQLITE_OK)
7✔
1218
        {
1219
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1220
            return;
×
1221
        }
1222

1223
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1224
            auto anno_str = logmsg_annotations_handlers.to_string(
1225
                line_meta.bm_annotations);
1✔
1226

1227
            if (sqlite3_bind_text(stmt,
1✔
1228
                                  8,
1229
                                  anno_str.c_str(),
1230
                                  anno_str.length(),
1✔
1231
                                  SQLITE_TRANSIENT)
1232
                != SQLITE_OK)
1✔
1233
            {
1234
                log_error("could not bind annotations -- %s",
×
1235
                          sqlite3_errmsg(db));
1236
                return;
×
1237
            }
1238
        } else {
1✔
1239
            sqlite3_bind_null(stmt, 8);
6✔
1240
        }
1241

1242
        if (line_meta.bm_opid.empty()) {
7✔
1243
            sqlite3_bind_null(stmt, 9);
7✔
1244
        } else {
1245
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1246
        }
1247

1248
        if (sqlite3_step(stmt) != SQLITE_DONE) {
7✔
1249
            log_error("could not execute bookmark insert statement -- %s",
×
1250
                      sqlite3_errmsg(db));
1251
            return;
×
1252
        }
1253

1254
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1255
                                          lf->get_format_ptr()->get_name(),
7✔
1256
                                          line_hash);
1257

1258
        sqlite3_reset(stmt);
7✔
1259
    }
7✔
1260
}
1261

1262
static void
1263
save_time_bookmarks()
23✔
1264
{
1265
    auto_sqlite3 db;
23✔
1266
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
23✔
1267
    auto_mem<char, sqlite3_free> errmsg;
23✔
1268
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
23✔
1269

1270
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
23✔
1271
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1272
        return;
×
1273
    }
1274

1275
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
23✔
1276
        != SQLITE_OK)
23✔
1277
    {
1278
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1279
        return;
×
1280
    }
1281

1282
    if (sqlite3_exec(
23✔
1283
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1284
        != SQLITE_OK)
23✔
1285
    {
1286
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1287
        return;
×
1288
    }
1289

1290
    {
1291
        static const char* UPDATE_NETLOCS_STMT
1292
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1293

1294
        std::set<std::string> netlocs;
23✔
1295

1296
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
23✔
1297
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
46✔
1298

1299
        if (sqlite3_prepare_v2(
23✔
1300
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1301
            != SQLITE_OK)
23✔
1302
        {
1303
            log_error("could not prepare recent_netlocs statement -- %s",
×
1304
                      sqlite3_errmsg(db));
1305
            return;
×
1306
        }
1307

1308
        for (const auto& netloc : netlocs) {
23✔
1309
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1310

1311
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1312
                log_error("could not execute bookmark insert statement -- %s",
×
1313
                          sqlite3_errmsg(db));
1314
                return;
×
1315
            }
1316

1317
            sqlite3_reset(stmt.in());
×
1318
        }
1319
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
23✔
1320
    }
23✔
1321

1322
    auto& lss = lnav_data.ld_log_source;
23✔
1323
    auto& bm = lss.get_user_bookmarks();
23✔
1324

1325
    if (sqlite3_prepare_v2(db.in(),
23✔
1326
                           "DELETE FROM bookmarks WHERE "
1327
                           " log_time = ? and log_format = ? and log_hash = ? "
1328
                           " and session_time = ?",
1329
                           -1,
1330
                           stmt.out(),
1331
                           nullptr)
1332
        != SQLITE_OK)
23✔
1333
    {
1334
        log_error("could not prepare bookmark delete statement -- %s",
×
1335
                  sqlite3_errmsg(db));
1336
        return;
×
1337
    }
1338

1339
    for (auto& marked_session_line : marked_session_lines) {
25✔
1340
        sqlite3_clear_bindings(stmt.in());
2✔
1341

1342
        if (bind_values(stmt,
4✔
1343
                        marked_session_line.sl_time,
1344
                        marked_session_line.sl_format_name,
1345
                        marked_session_line.sl_line_hash,
2✔
1346
                        lnav_data.ld_session_time)
1347
            != SQLITE_OK)
2✔
1348
        {
1349
            continue;
×
1350
        }
1351

1352
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1353
            log_error("could not execute bookmark insert statement -- %s",
×
1354
                      sqlite3_errmsg(db));
1355
            return;
×
1356
        }
1357

1358
        sqlite3_reset(stmt.in());
2✔
1359
    }
1360

1361
    marked_session_lines.clear();
23✔
1362

1363
    if (sqlite3_prepare_v2(db.in(),
23✔
1364
                           "REPLACE INTO bookmarks"
1365
                           " (log_time, log_format, log_hash, session_time, "
1366
                           "part_name, comment, tags, annotations, log_opid)"
1367
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1368
                           -1,
1369
                           stmt.out(),
1370
                           nullptr)
1371
        != SQLITE_OK)
23✔
1372
    {
1373
        log_error("could not prepare bookmark replace statement -- %s",
×
1374
                  sqlite3_errmsg(db));
1375
        return;
×
1376
    }
1377

1378
    {
1379
        for (auto file_iter = lnav_data.ld_log_source.begin();
23✔
1380
             file_iter != lnav_data.ld_log_source.end();
47✔
1381
             ++file_iter)
24✔
1382
        {
1383
            auto lf = (*file_iter)->get_file();
24✔
1384

1385
            if (lf == nullptr) {
24✔
1386
                continue;
×
1387
            }
1388
            if (lf->size() == 0) {
24✔
1389
                continue;
×
1390
            }
1391

1392
            content_line_t base_content_line;
24✔
1393
            base_content_line = lss.get_file_base_content_line(file_iter);
24✔
1394
            base_content_line
1395
                = content_line_t(base_content_line + lf->size() - 1);
24✔
1396

1397
            if (!bind_line(db.in(),
24✔
1398
                           stmt.in(),
1399
                           base_content_line,
1400
                           lnav_data.ld_session_time))
24✔
1401
            {
1402
                continue;
×
1403
            }
1404

1405
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
24✔
1406
                log_error("could not bind log hash -- %s",
×
1407
                          sqlite3_errmsg(db.in()));
1408
                return;
×
1409
            }
1410

1411
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
24✔
1412
                log_error("could not execute bookmark insert statement -- %s",
×
1413
                          sqlite3_errmsg(db));
1414
                return;
×
1415
            }
1416

1417
            sqlite3_reset(stmt.in());
24✔
1418
        }
24✔
1419
    }
1420

1421
    save_user_bookmarks(db.in(), stmt.in(), bm[&textview_curses::BM_USER]);
23✔
1422
    for (const auto& ldd : lss) {
47✔
1423
        auto* lf = ldd->get_file_ptr();
24✔
1424
        if (lf == nullptr) {
24✔
1425
            continue;
×
1426
        }
1427

1428
        save_meta_bookmarks(db.in(), stmt.in(), lf);
24✔
1429
    }
1430

1431
    if (sqlite3_prepare_v2(db.in(),
23✔
1432
                           "DELETE FROM time_offset WHERE "
1433
                           " log_time = ? and log_format = ? and log_hash = ? "
1434
                           " and session_time = ?",
1435
                           -1,
1436
                           stmt.out(),
1437
                           NULL)
1438
        != SQLITE_OK)
23✔
1439
    {
1440
        log_error("could not prepare time_offset delete statement -- %s",
×
1441
                  sqlite3_errmsg(db));
1442
        return;
×
1443
    }
1444

1445
    for (auto& offset_session_line : offset_session_lines) {
24✔
1446
        sqlite3_clear_bindings(stmt.in());
1✔
1447

1448
        if (bind_values(stmt,
2✔
1449
                        offset_session_line.sl_time,
1450
                        offset_session_line.sl_format_name,
1451
                        offset_session_line.sl_line_hash,
1✔
1452
                        lnav_data.ld_session_time)
1453
            != SQLITE_OK)
1✔
1454
        {
1455
            continue;
×
1456
        }
1457

1458
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1459
            log_error("could not execute bookmark insert statement -- %s",
×
1460
                      sqlite3_errmsg(db));
1461
            return;
×
1462
        }
1463

1464
        sqlite3_reset(stmt.in());
1✔
1465
    }
1466

1467
    offset_session_lines.clear();
23✔
1468

1469
    if (sqlite3_prepare_v2(db.in(),
23✔
1470
                           "REPLACE INTO time_offset"
1471
                           " (log_time, log_format, log_hash, session_time, "
1472
                           "offset_sec, offset_usec)"
1473
                           " VALUES (?, ?, ?, ?, ?, ?)",
1474
                           -1,
1475
                           stmt.out(),
1476
                           nullptr)
1477
        != SQLITE_OK)
23✔
1478
    {
1479
        log_error("could not prepare time_offset replace statement -- %s",
×
1480
                  sqlite3_errmsg(db));
1481
        return;
×
1482
    }
1483

1484
    {
1485
        for (auto file_iter = lnav_data.ld_log_source.begin();
23✔
1486
             file_iter != lnav_data.ld_log_source.end();
47✔
1487
             ++file_iter)
24✔
1488
        {
1489
            auto lf = (*file_iter)->get_file();
24✔
1490
            if (lf == nullptr) {
24✔
1491
                continue;
×
1492
            }
1493
            if (lf->size() == 0) {
24✔
1494
                continue;
×
1495
            }
1496

1497
            if (bind_values(stmt,
72✔
1498
                            lf->original_line_time(lf->begin()),
1499
                            lf->get_format()->get_name(),
48✔
1500
                            lf->get_content_id(),
24✔
1501
                            lnav_data.ld_session_time)
1502
                != SQLITE_OK)
24✔
1503
            {
1504
                continue;
×
1505
            }
1506

1507
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
24✔
1508
                log_error("could not bind log hash -- %s",
×
1509
                          sqlite3_errmsg(db.in()));
1510
                return;
×
1511
            }
1512

1513
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
24✔
1514
                log_error("could not bind log hash -- %s",
×
1515
                          sqlite3_errmsg(db.in()));
1516
                return;
×
1517
            }
1518

1519
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
24✔
1520
                log_error("could not execute bookmark insert statement -- %s",
×
1521
                          sqlite3_errmsg(db));
1522
                return;
×
1523
            }
1524

1525
            sqlite3_reset(stmt.in());
24✔
1526
        }
24✔
1527
    }
1528

1529
    for (const auto& ls : lss) {
47✔
1530
        if (ls->get_file() == nullptr) {
24✔
1531
            continue;
21✔
1532
        }
1533

1534
        const auto lf = ls->get_file();
24✔
1535
        if (!lf->is_time_adjusted()) {
24✔
1536
            continue;
21✔
1537
        }
1538

1539
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
1540
        auto offset = lf->get_time_offset();
3✔
1541

1542
        bind_values(stmt.in(),
6✔
1543
                    lf->original_line_time(line_iter),
1544
                    lf->get_format()->get_name(),
6✔
1545
                    lf->get_content_id(),
3✔
1546
                    lnav_data.ld_session_time,
1547
                    offset.tv_sec,
1548
                    offset.tv_usec);
1549

1550
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
1551
            log_error("could not execute bookmark insert statement -- %s",
×
1552
                      sqlite3_errmsg(db));
1553
            return;
×
1554
        }
1555

1556
        sqlite3_reset(stmt.in());
3✔
1557
    }
24✔
1558

1559
    log_info("saved %d bookmarks", sqlite3_changes(db.in()));
23✔
1560

1561
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
23✔
1562
        != SQLITE_OK)
23✔
1563
    {
1564
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1565
        return;
×
1566
    }
1567

1568
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
23✔
1569
        != SQLITE_OK)
23✔
1570
    {
1571
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
1572
        return;
×
1573
    }
1574
    auto bookmark_changes = sqlite3_changes(db.in());
23✔
1575
    if (bookmark_changes > 0) {
23✔
NEW
1576
        log_info("deleted %d old bookmarks", bookmark_changes);
×
1577
    }
1578

1579
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
23✔
1580
        != SQLITE_OK)
23✔
1581
    {
1582
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
1583
        return;
×
1584
    }
1585
    auto netloc_changes = sqlite3_changes(db.in());
23✔
1586
    if (netloc_changes > 0) {
23✔
NEW
1587
        log_info("deleted %d old netlocs", netloc_changes);
×
1588
    }
1589
}
23✔
1590

1591
static void
1592
save_session_with_id(const std::string& session_id)
22✔
1593
{
1594
    auto_mem<FILE> file(fclose);
22✔
1595
    yajl_gen handle = nullptr;
22✔
1596

1597
    /* TODO: save the last search query */
1598

1599
    log_info("saving session with id: %s", session_id.c_str());
22✔
1600

1601
    auto view_base_name
1602
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
44✔
1603
                      session_id,
1604
                      lnav_data.ld_session_time,
1605
                      getppid());
22✔
1606
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
22✔
1607
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
22✔
1608

1609
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
22✔
1610
        perror("Unable to open session file");
×
1611
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
22✔
1612
        perror("Unable to create yajl_gen object");
×
1613
    } else {
1614
        yajl_gen_config(
22✔
1615
            handle, yajl_gen_print_callback, yajl_writer, file.in());
1616

1617
        {
1618
            yajlpp_map root_map(handle);
22✔
1619

1620
            root_map.gen("save-time");
22✔
1621
            root_map.gen((long long) time(nullptr));
22✔
1622

1623
            root_map.gen("time-offset");
22✔
1624
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
22✔
1625

1626
            root_map.gen("files");
22✔
1627

1628
            {
1629
                yajlpp_array file_list(handle);
22✔
1630

1631
                for (auto& ld_file_name :
22✔
1632
                     lnav_data.ld_active_files.fc_file_names)
68✔
1633
                {
1634
                    file_list.gen(ld_file_name.first);
24✔
1635
                }
1636
            }
22✔
1637

1638
            root_map.gen("file-states");
22✔
1639

1640
            {
1641
                yajlpp_map file_states(handle);
22✔
1642

1643
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
46✔
1644
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
24✔
1645

1646
                    file_states.gen(lf->get_filename().native());
24✔
1647

1648
                    {
1649
                        yajlpp_map file_state(handle);
24✔
1650

1651
                        file_state.gen("visible");
24✔
1652
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
24✔
1653
                    }
24✔
1654
                }
1655
            }
22✔
1656

1657
            root_map.gen("views");
22✔
1658

1659
            {
1660
                yajlpp_map top_view_map(handle);
22✔
1661

1662
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
220✔
1663
                    auto& tc = lnav_data.ld_views[lpc];
198✔
1664
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
198✔
1665

1666
                    top_view_map.gen(lnav_view_strings[lpc]);
198✔
1667

1668
                    yajlpp_map view_map(handle);
198✔
1669

1670
                    view_map.gen("top_line");
198✔
1671

1672
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
198✔
1673
                        view_map.gen(-1LL);
185✔
1674
                    } else {
1675
                        view_map.gen((long long) tc.get_top());
13✔
1676
                    }
1677

1678
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
247✔
1679
                        && tc.get_inner_height() > 0_vl
2✔
1680
                        && tc.get_selection() != tc.get_inner_height() - 1)
247✔
1681
                    {
1682
                        auto sel = tc.get_selection();
1✔
1683
                        if (sel) {
1✔
1684
                            view_map.gen("focused_line");
1✔
1685
                            view_map.gen((long long) sel.value());
1✔
1686

1687
                            if (ta != nullptr) {
1✔
1688
                                auto anchor_opt
1689
                                    = ta->anchor_for_row(sel.value());
1✔
1690
                                if (anchor_opt) {
1✔
1691
                                    view_map.gen("anchor");
1✔
1692
                                    view_map.gen(anchor_opt.value());
1✔
1693
                                }
1694
                            }
1✔
1695
                        }
1696
                    }
1697

1698
                    view_map.gen("search");
198✔
1699
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
198✔
1700

1701
                    view_map.gen("word_wrap");
198✔
1702
                    view_map.gen(tc.get_word_wrap());
198✔
1703

1704
                    auto* tss = tc.get_sub_source();
198✔
1705
                    if (tss == nullptr) {
198✔
1706
                        continue;
44✔
1707
                    }
1708

1709
                    view_map.gen("filtering");
154✔
1710
                    view_map.gen(tss->tss_apply_filters);
154✔
1711

1712
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
154✔
1713
                        view_map.gen("min_level");
×
1714
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
1715
                    }
1716

1717
                    view_map.gen("commands");
154✔
1718
                    yajlpp_array cmd_array(handle);
154✔
1719

1720
                    tss->add_commands_for_session(
154✔
1721
                        [&](auto& cmd) { cmd_array.gen(cmd); });
165✔
1722
                }
198✔
1723
            }
22✔
1724
        }
22✔
1725

1726
        yajl_gen_clear(handle);
22✔
1727
        yajl_gen_free(handle);
22✔
1728

1729
        fclose(file.release());
22✔
1730

1731
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
22✔
1732

1733
        log_info("Saved session: %s", view_file_name.c_str());
22✔
1734
    }
1735
}
22✔
1736

1737
void
1738
save_session()
23✔
1739
{
1740
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
23✔
1741
        log_info("secure mode is enabled, not saving session");
×
1742
        return;
×
1743
    }
1744

1745
    static auto op = lnav_operation{"save_session"};
23✔
1746

1747
    auto op_guard = lnav_opid_guard::internal(op);
23✔
1748

1749
    log_debug("BEGIN save_session");
23✔
1750
    save_time_bookmarks();
23✔
1751

1752
    const auto opt_session_id = compute_session_id();
23✔
1753
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
44✔
1754
    for (const auto& pair : lnav_data.ld_session_id) {
27✔
1755
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
1756
            continue;
3✔
1757
        }
1758
        save_session_with_id(pair.first);
1✔
1759
    }
1760
    log_debug("END save_session");
23✔
1761
}
23✔
1762

1763
void
1764
reset_session()
6✔
1765
{
1766
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
1767

1768
    save_session();
6✔
1769

1770
    lnav_data.ld_session_time = time(nullptr);
6✔
1771
    session_data.sd_file_states.clear();
6✔
1772

1773
    for (auto& tc : lnav_data.ld_views) {
60✔
1774
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
1775
        auto& hmap = tc.get_highlights();
54✔
1776
        auto hl_iter = hmap.begin();
54✔
1777

1778
        while (hl_iter != hmap.end()) {
552✔
1779
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
1780
                ++hl_iter;
498✔
1781
            } else {
1782
                hmap.erase(hl_iter++);
×
1783
            }
1784
        }
1785

1786
        if (ttt != nullptr) {
54✔
1787
            ttt->clear_min_max_row_times();
36✔
1788
        }
1789
    }
1790

1791
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
1792
        lf->reset_state();
8✔
1793
    }
1794

1795
    // XXX clean this up
1796
    lnav_data.ld_log_source.lss_highlighters.clear();
6✔
1797
    lnav_data.ld_log_source.set_force_rebuild();
6✔
1798
    lnav_data.ld_log_source.set_marked_only(false);
6✔
1799
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
1800
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
1801
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
1802
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
1803
    rebuild_indexes(std::nullopt);
6✔
1804

1805
    lnav_data.ld_db_row_source.reset_user_state();
6✔
1806

1807
    auto* tss = static_cast<timeline_source*>(
1808
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
6✔
1809
    if (tss != nullptr) {
6✔
1810
        tss->ts_hidden_row_types.clear();
6✔
1811
    }
1812

1813
    for (auto& tc : lnav_data.ld_views) {
60✔
1814
        text_sub_source* tss = tc.get_sub_source();
54✔
1815

1816
        if (tss == nullptr) {
54✔
1817
            continue;
12✔
1818
        }
1819
        tss->get_filters().clear_filters();
42✔
1820
        tss->tss_apply_filters = true;
42✔
1821
        tss->text_filters_changed();
42✔
1822
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
1823
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
1824
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
1825
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
1826
        tc.reload_data();
42✔
1827
    }
1828

1829
    lnav_data.ld_filter_view.reload_data();
6✔
1830
    lnav_data.ld_files_view.reload_data();
6✔
1831
    for (const auto& format : log_format::get_root_formats()) {
456✔
1832
        auto* elf = dynamic_cast<external_log_format*>(format.get());
450✔
1833

1834
        if (elf == nullptr) {
450✔
1835
            continue;
30✔
1836
        }
1837

1838
        bool changed = false;
420✔
1839
        for (const auto& vd : elf->elf_value_defs) {
6,078✔
1840
            if (vd.second->vd_meta.lvm_user_hidden) {
5,658✔
1841
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
1842
                changed = true;
×
1843
            }
1844
        }
1845
        if (changed) {
420✔
1846
            elf->elf_value_defs_state->vds_generation += 1;
×
1847
        }
1848
    }
1849
}
6✔
1850

1851
void
1852
lnav::session::apply_view_commands()
25✔
1853
{
1854
    static auto op = lnav_operation{__FUNCTION__};
25✔
1855

1856
    auto op_guard = lnav_opid_guard::internal(op);
25✔
1857

1858
    log_debug("applying view commands");
25✔
1859
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
250✔
1860
        const auto& vs = session_data.sd_view_states[view_index];
225✔
1861
        auto& tview = lnav_data.ld_views[view_index];
225✔
1862

1863
        lnav::set::small<std::string> curr_cmds;
225✔
1864
        auto* tss = tview.get_sub_source();
225✔
1865
        if (tview.get_sub_source() != nullptr) {
225✔
1866
            tss->tss_apply_filters = vs.vs_filtering;
175✔
1867
            if (vs.vs_min_log_level) {
175✔
1868
                tss->set_min_log_level(vs.vs_min_log_level.value());
×
1869
            }
1870
            tss->add_commands_for_session([&](auto& cmd) {
175✔
1871
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
1872
                                  .split_when(string_fragment::tag1{' '})
5✔
1873
                                  .first;
1874
                curr_cmds.insert(cmd_sf.to_string());
5✔
1875
            });
5✔
1876
        }
1877
        for (const auto& cmdline : vs.vs_commands) {
232✔
1878
            auto cmdline_sf = string_fragment::from_str(cmdline);
7✔
1879
            auto active = ensure_view(&tview);
7✔
1880
            auto [cmd_sf, _cmdline_rem]
7✔
1881
                = cmdline_sf.split_when(string_fragment::tag1{' '});
7✔
1882
            if (curr_cmds.contains(cmd_sf.to_string())) {
7✔
1883
                log_debug("view %s command '%.*s' already active",
3✔
1884
                          tview.get_title().c_str(),
1885
                          cmd_sf.length(),
1886
                          cmd_sf.data());
1887
                continue;
3✔
1888
            }
1889
            auto exec_cmd_res
1890
                = execute_command(lnav_data.ld_exec_context, cmdline);
4✔
1891
            if (exec_cmd_res.isOk()) {
4✔
1892
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
4✔
1893
            } else {
1894
                log_error("Result: %s",
×
1895
                          exec_cmd_res.unwrapErr()
1896
                              .to_attr_line()
1897
                              .get_string()
1898
                              .c_str());
1899
            }
1900
            if (!active) {
4✔
1901
                lnav_data.ld_view_stack.pop_back();
×
1902
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
1903
                    // XXX
1904
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
1905
                        auto tss = tc->get_sub_source();
×
1906
                        tss->text_filters_changed();
×
1907
                        tc->reload_data();
×
1908
                    }
1909
                };
1910
            }
1911
        }
4✔
1912
    }
225✔
1913
}
25✔
1914

1915
void
1916
lnav::session::restore_view_states()
25✔
1917
{
1918
    static auto op = lnav_operation{__FUNCTION__};
25✔
1919

1920
    auto op_guard = lnav_opid_guard::internal(op);
25✔
1921

1922
    log_debug("restoring view states");
25✔
1923
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
250✔
1924
        const auto& vs = session_data.sd_view_states[view_index];
225✔
1925
        auto& tview = lnav_data.ld_views[view_index];
225✔
1926
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
225✔
1927

1928
        if (!vs.vs_search.empty()) {
225✔
1929
            tview.execute_search(vs.vs_search);
×
1930
            tview.set_follow_search_for(-1, {});
×
1931
        }
1932
        tview.set_word_wrap(vs.vs_word_wrap);
225✔
1933
        auto has_loc = tview.get_selection().has_value();
225✔
1934
        if (!has_loc && vs.vs_top >= 0
59✔
1935
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
284✔
1936
                || tview.get_top() == tview.get_top_for_last_row()))
225✔
1937
        {
1938
            log_info("restoring %s view top: %d",
16✔
1939
                     lnav_view_strings[view_index].data(),
1940
                     (int) vs.vs_top);
1941
            tview.set_top(vis_line_t(vs.vs_top), true);
16✔
1942
        }
1943
        if (!has_loc && vs.vs_selection) {
225✔
1944
            log_info("restoring %s view selection: %d",
×
1945
                     lnav_view_strings[view_index].data(),
1946
                     (int) vs.vs_selection.value_or(-1_vl));
1947
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
×
1948
        }
1949
        auto sel = tview.get_selection();
225✔
1950
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
59✔
1951
            && !vs.vs_anchor->empty())
284✔
1952
        {
1953
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
1954

1955
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
1956
                log_info("%s view anchor mismatch %s != %s",
×
1957
                         lnav_view_strings[view_index].data(),
1958
                         curr_anchor.value_or("").c_str(),
1959
                         vs.vs_anchor.value().c_str());
1960

1961
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
1962
                if (row_opt) {
×
1963
                    tview.set_selection(row_opt.value());
×
1964
                }
1965
            }
1966
        }
1967
        sel = tview.get_selection();
225✔
1968
        if (!sel) {
225✔
1969
            auto height = tview.get_inner_height();
59✔
1970
            if (height == 0) {
59✔
1971
            } else if (view_index == LNV_TEXT) {
1✔
1972
                auto lf = lnav_data.ld_text_source.current_file();
×
1973
                if (lf != nullptr) {
×
1974
                    switch (lf->get_text_format().value_or(
×
1975
                        text_format_t::TF_BINARY))
×
1976
                    {
1977
                        case text_format_t::TF_PLAINTEXT:
×
1978
                        case text_format_t::TF_LOG: {
1979
                            if (height > 0_vl) {
×
1980
                                tview.set_selection(height - 1_vl);
×
1981
                            }
1982
                            break;
×
1983
                        }
1984
                        default:
×
1985
                            tview.set_selection(0_vl);
×
1986
                            break;
×
1987
                    }
1988
                }
1989
            } else if (view_index == LNV_LOG) {
1✔
1990
                tview.set_selection(height - 1_vl);
×
1991
            } else {
1992
                tview.set_selection(0_vl);
1✔
1993
            }
1994
        }
1995
        log_info("%s view actual top/selection: %d/%d",
225✔
1996
                 lnav_view_strings[view_index].data(),
1997
                 (int) tview.get_top(),
1998
                 (int) tview.get_selection().value_or(-1_vl));
1999
    }
2000
}
25✔
2001

2002
void
2003
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2004
{
2005
    constexpr const char* STMT = R"(
×
2006
       INSERT INTO regex101_entries
2007
          (format_name, regex_name, permalink, delete_code)
2008
          VALUES (?, ?, ?, ?);
2009
)";
2010

2011
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2012
    auto_sqlite3 db;
×
2013

2014
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2015
        return;
×
2016
    }
2017

2018
    auto_mem<char, sqlite3_free> errmsg;
×
2019
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2020
        != SQLITE_OK)
×
2021
    {
2022
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2023
        return;
×
2024
    }
2025

2026
    auto prep_res = prepare_stmt(db.in(),
2027
                                 STMT,
2028
                                 ei.re_format_name,
×
2029
                                 ei.re_regex_name,
×
2030
                                 ei.re_permalink,
×
2031
                                 ei.re_delete_code);
×
2032

2033
    if (prep_res.isErr()) {
×
2034
        return;
×
2035
    }
2036

2037
    auto ps = prep_res.unwrap();
×
2038

2039
    ps.execute();
×
2040
}
2041

2042
template<>
2043
struct from_sqlite<lnav::session::regex101::entry> {
2044
    inline lnav::session::regex101::entry operator()(int argc,
2045
                                                     sqlite3_value** argv,
2046
                                                     int argi)
2047
    {
2048
        return {
2049
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
2050
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2051
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2052
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2053
        };
2054
    }
2055
};
2056

2057
Result<std::vector<lnav::session::regex101::entry>, std::string>
2058
lnav::session::regex101::get_entries()
×
2059
{
2060
    constexpr const char* STMT = R"(
×
2061
       SELECT * FROM regex101_entries;
2062
)";
2063

2064
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2065
    auto_sqlite3 db;
×
2066

2067
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2068
        return Err(std::string());
×
2069
    }
2070

2071
    auto_mem<char, sqlite3_free> errmsg;
×
2072
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2073
        != SQLITE_OK)
×
2074
    {
2075
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2076
        return Err(std::string(errmsg));
×
2077
    }
2078

2079
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2080
    bool done = false;
×
2081
    std::vector<entry> retval;
×
2082

2083
    while (!done) {
×
2084
        auto fetch_res = ps.fetch_row<entry>();
×
2085

2086
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2087
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2088
        }
2089

2090
        fetch_res.match(
×
2091
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2092
            [](const prepared_stmt::fetch_error&) {},
×
2093
            [&retval](entry en) { retval.emplace_back(en); });
×
2094
    }
2095
    return Ok(retval);
×
2096
}
2097

2098
void
2099
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2100
                                      const std::string& regex_name)
2101
{
2102
    constexpr const char* STMT = R"(
×
2103
       DELETE FROM regex101_entries WHERE
2104
          format_name = ? AND regex_name = ?;
2105
)";
2106

2107
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2108
    auto_sqlite3 db;
×
2109

2110
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2111
        return;
×
2112
    }
2113

2114
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2115

2116
    if (prep_res.isErr()) {
×
2117
        return;
×
2118
    }
2119

2120
    auto ps = prep_res.unwrap();
×
2121

2122
    ps.execute();
×
2123
}
2124

2125
lnav::session::regex101::get_result_t
2126
lnav::session::regex101::get_entry(const std::string& format_name,
×
2127
                                   const std::string& regex_name)
2128
{
2129
    constexpr const char* STMT = R"(
×
2130
       SELECT * FROM regex101_entries WHERE
2131
          format_name = ? AND regex_name = ?;
2132
    )";
2133

2134
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2135
    auto_sqlite3 db;
×
2136

2137
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2138
        return error{std::string()};
×
2139
    }
2140

2141
    auto_mem<char, sqlite3_free> errmsg;
×
2142
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2143
        != SQLITE_OK)
×
2144
    {
2145
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2146
        return error{std::string(errmsg)};
×
2147
    }
2148

2149
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2150
    if (prep_res.isErr()) {
×
2151
        return error{prep_res.unwrapErr()};
×
2152
    }
2153

2154
    auto ps = prep_res.unwrap();
×
2155
    return ps.fetch_row<entry>().match(
×
2156
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2157
            return error{fe.fe_msg};
×
2158
        },
2159
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2160
            return no_entry{};
×
2161
        },
2162
        [](const entry& en) -> get_result_t { return en; });
×
2163
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc