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

tstack / lnav / 19092286039-2639

05 Nov 2025 05:26AM UTC coverage: 68.913% (-0.007%) from 68.92%
19092286039-2639

push

github

tstack
[session] skip restoring commands

Related to #1450

94 of 116 new or added lines in 8 files covered. (81.03%)

40 existing lines in 8 files now uncovered.

50578 of 73394 relevant lines covered (68.91%)

429603.27 hits per line

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

73.59
/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 "vtab_module.hh"
61
#include "yajlpp/yajlpp.hh"
62
#include "yajlpp/yajlpp_def.hh"
63

64
session_data_t session_data;
65
recent_refs_t recent_refs;
66

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

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

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

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

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

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

100
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
101

102
    PRIMARY KEY (netloc)
103
);
104

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

111
    PRIMARY KEY (format_name, regex_name),
112

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

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

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

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

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

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

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

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

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

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

170
    sqlite3_clear_bindings(stmt);
23✔
171

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

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

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

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

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

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

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

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

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

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

261
    session_info_list.sort();
25✔
262

263
    size_t session_loops = 0;
25✔
264

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

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

286
    session_info_list.sort();
25✔
287

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

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

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

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

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

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

337
    return h.to_string();
44✔
338
}
339

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

345
    cleanup_session_data();
25✔
346

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

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

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

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

385
    session_file_names.sort();
24✔
386

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

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

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

402
    return std::make_optional(session_file_names.back());
22✔
403
}
25✔
404

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

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

441
    static auto op = lnav_operation{__FUNCTION__};
25✔
442

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

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

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

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

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

478
        auto netloc_stmt = netloc_prep_res.unwrap();
23✔
479
        bool done = false;
23✔
480

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

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

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

519
        base_content_line = lss.get_file_base_content_line(file_iter);
24✔
520

521
        auto low_line_iter = lf->begin();
24✔
522
        auto high_line_iter = lf->end();
24✔
523

524
        --high_line_iter;
24✔
525

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

536
        date_time_scanner dts;
24✔
537
        bool done = false;
24✔
538
        int64_t last_mark_time = -1;
24✔
539

540
        while (!done) {
86✔
541
            int rc = sqlite3_step(stmt.in());
62✔
542

543
            switch (rc) {
62✔
544
                case SQLITE_OK:
24✔
545
                case SQLITE_DONE:
546
                    done = true;
24✔
547
                    break;
24✔
548

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

566
                    if (last_mark_time == -1) {
38✔
567
                        last_mark_time = mark_time;
23✔
568
                    } else if (last_mark_time != mark_time) {
15✔
569
                        done = true;
×
570
                        continue;
19✔
571
                    }
572

573
                    if (part_name == nullptr) {
38✔
574
                        continue;
19✔
575
                    }
576

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

783
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
24✔
784

785
        auto low_line_iter = lf->begin();
24✔
786
        auto high_line_iter = lf->end();
24✔
787

788
        --high_line_iter;
24✔
789

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

801
        date_time_scanner dts;
24✔
802
        auto done = false;
24✔
803
        int64_t last_mark_time = -1;
24✔
804

805
        while (!done) {
71✔
806
            const auto rc = sqlite3_step(stmt.in());
47✔
807

808
            switch (rc) {
47✔
809
                case SQLITE_OK:
24✔
810
                case SQLITE_DONE:
811
                    done = true;
24✔
812
                    break;
24✔
813

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

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

828
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
23✔
829
                        continue;
20✔
830
                    }
831

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

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

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

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

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

864
                        reload_needed = true;
3✔
865

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

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

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

885
    if (reload_needed) {
23✔
886
        lnav_data.ld_views[LNV_LOG].reload_data();
16✔
887
    }
888
}
33✔
889

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

899
static const json_path_container view_def_handlers = {
900
    json_path_handler("top_line").for_field(&view_state::vs_top),
901
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
902
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
903
    json_path_handler("search").for_field(&view_state::vs_search),
904
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
905
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
906
    json_path_handler("commands#").for_field(&view_state::vs_commands),
907
};
908

909
static const struct json_path_container view_handlers = {
910
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
911
        .with_obj_provider<view_state, session_data_t>(
912
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
1,606✔
913
                auto view_index_opt
914
                    = view_from_string(ypc.get_substr("view_name").c_str());
1,606✔
915
                if (view_index_opt) {
1,606✔
916
                    return &root->sd_view_states[view_index_opt.value()];
1,606✔
917
                }
918

919
                log_error("unknown view name: %s",
×
920
                          ypc.get_substr("view_name").c_str());
921
                static view_state dummy;
922
                return &dummy;
×
923
            })
924
        .with_children(view_def_handlers),
925
};
926

927
static const struct json_path_container file_state_handlers = {
928
    yajlpp::property_handler("visible")
929
        .with_description("Indicates whether the file is visible or not")
930
        .for_field(&file_state::fs_is_visible),
931
};
932

933
static const struct json_path_container file_states_handlers = {
934
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
935
        .with_description("Map of file names to file state objects")
936
        .with_obj_provider<file_state, session_data_t>(
937
            [](const auto& ypc, session_data_t* root) {
63✔
938
                auto fn = ypc.get_substr("filename");
63✔
939
                return &root->sd_file_states[fn];
126✔
940
            })
63✔
941
        .with_children(file_state_handlers),
942
};
943

944
static const typed_json_path_container<session_data_t> view_info_handlers = {
945
    yajlpp::property_handler("save-time")
946
        .for_field(&session_data_t::sd_save_time),
947
    yajlpp::property_handler("time-offset")
948
        .for_field(&session_data_t::sd_time_offset),
949
    json_path_handler("files#", read_files),
950
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
951
    yajlpp::property_handler("views").with_children(view_handlers),
952
};
953

954
void
955
load_session()
25✔
956
{
957
    static auto op = lnav_operation{"load_session"};
25✔
958

959
    auto op_guard = lnav_opid_guard::internal(op);
25✔
960
    log_info("BEGIN load_session");
25✔
961
    scan_sessions() | [](const auto pair) {
25✔
962
        lnav_data.ld_session_load_time = pair.first.second;
22✔
963
        const auto& view_info_path = pair.second;
22✔
964
        auto view_info_src = intern_string::lookup(view_info_path.string());
22✔
965

966
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
22✔
967
        if (open_res.isErr()) {
22✔
968
            log_error("cannot open session file: %s -- %s",
×
969
                      view_info_path.c_str(),
970
                      open_res.unwrapErr().c_str());
971
            return;
×
972
        }
973

974
        auto fd = open_res.unwrap();
22✔
975
        unsigned char buffer[1024];
976
        ssize_t rc;
977

978
        log_info("loading session file: %s", view_info_path.c_str());
22✔
979
        auto parser = view_info_handlers.parser_for(view_info_src);
22✔
980
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
68✔
981
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
23✔
982
            auto parse_res = parser.consume(buf_frag);
23✔
983
            if (parse_res.isErr()) {
23✔
984
                log_error("failed to load session: %s -- %s",
×
985
                          view_info_path.c_str(),
986
                          parse_res.unwrapErr()[0]
987
                              .to_attr_line()
988
                              .get_string()
989
                              .c_str());
990
                return;
×
991
            }
992
        }
993

994
        auto complete_res = parser.complete();
22✔
995
        if (complete_res.isErr()) {
22✔
996
            log_error("failed to load session: %s -- %s",
×
997
                      view_info_path.c_str(),
998
                      complete_res.unwrapErr()[0]
999
                          .to_attr_line()
1000
                          .get_string()
1001
                          .c_str());
1002
            return;
×
1003
        }
1004
        session_data = complete_res.unwrap();
22✔
1005

1006
        bool log_changes = false;
22✔
1007

1008
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
45✔
1009
            auto iter = session_data.sd_file_states.find(lf->get_filename());
23✔
1010

1011
            if (iter == session_data.sd_file_states.end()) {
23✔
1012
                continue;
2✔
1013
            }
1014

1015
            log_debug("found state for file: %s %d (%s)",
21✔
1016
                      lf->get_content_id().c_str(),
1017
                      iter->second.fs_is_visible,
1018
                      lf->get_filename_as_string().c_str());
1019
            lnav_data.ld_log_source.find_data(lf) |
21✔
1020
                [iter, &log_changes](auto ld) {
42✔
1021
                    if (ld->ld_visible != iter->second.fs_is_visible) {
21✔
1022
                        ld->get_file_ptr()->set_indexing(
1✔
1023
                            iter->second.fs_is_visible);
1✔
1024
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1025
                        log_changes = true;
1✔
1026
                    }
1027
                };
1028
        }
1029

1030
        if (log_changes) {
22✔
1031
            lnav_data.ld_log_source.text_filters_changed();
1✔
1032
        }
1033
    };
22✔
1034

1035
    lnav::events::publish(lnav_data.ld_db.in(),
25✔
1036
                          lnav::events::session::loaded{});
1037

1038
    log_info("END load_session");
25✔
1039
}
25✔
1040

1041
static void
1042
yajl_writer(void* context, const char* str, size_t len)
7,892✔
1043
{
1044
    FILE* file = (FILE*) context;
7,892✔
1045

1046
    fwrite(str, len, 1, file);
7,892✔
1047
}
7,892✔
1048

1049
static void
1050
save_user_bookmarks(sqlite3* db,
22✔
1051
                    sqlite3_stmt* stmt,
1052
                    bookmark_vector<content_line_t>& user_marks)
1053
{
1054
    auto& lss = lnav_data.ld_log_source;
22✔
1055

1056
    for (auto iter = user_marks.bv_tree.begin();
22✔
1057
         iter != user_marks.bv_tree.end();
23✔
1058
         ++iter)
1✔
1059
    {
1060
        content_line_t cl = *iter;
1✔
1061
        auto lf = lss.find(cl);
1✔
1062
        if (lf == nullptr) {
1✔
1063
            continue;
×
1064
        }
1065

1066
        sqlite3_clear_bindings(stmt);
1✔
1067

1068
        const auto line_iter = lf->begin() + cl;
1✔
1069
        auto fr = lf->get_file_range(line_iter, false);
1✔
1070
        auto read_result = lf->read_range(fr);
1✔
1071

1072
        if (read_result.isErr()) {
1✔
1073
            continue;
×
1074
        }
1075

1076
        auto line_hash = read_result
1077
                             .map([cl](auto sbr) {
2✔
1078
                                 return hasher()
2✔
1079
                                     .update(sbr.get_data(), sbr.length())
1✔
1080
                                     .update(cl)
1✔
1081
                                     .to_string();
2✔
1082
                             })
1083
                             .unwrap();
1✔
1084

1085
        if (bind_values(stmt,
3✔
1086
                        lf->original_line_time(line_iter),
1087
                        lf->get_format()->get_name(),
2✔
1088
                        line_hash,
1089
                        lnav_data.ld_session_time)
1090
            != SQLITE_OK)
1✔
1091
        {
1092
            continue;
×
1093
        }
1094

1095
        if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT) != SQLITE_OK) {
1✔
1096
            log_error("could not bind log hash -- %s", sqlite3_errmsg(db));
×
1097
            return;
×
1098
        }
1099

1100
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1101
            log_error("could not execute bookmark insert statement -- %s",
×
1102
                      sqlite3_errmsg(db));
1103
            return;
×
1104
        }
1105

1106
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1107
                                          lf->get_format_ptr()->get_name(),
1✔
1108
                                          line_hash);
1109

1110
        sqlite3_reset(stmt);
1✔
1111
    }
1✔
1112
}
1113

1114
static void
1115
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
23✔
1116
{
1117
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
30✔
1118
        auto cl = content_line_t(bm_pair.first);
7✔
1119
        sqlite3_clear_bindings(stmt);
7✔
1120

1121
        auto line_iter = lf->begin() + cl;
7✔
1122
        auto fr = lf->get_file_range(line_iter, false);
7✔
1123
        auto read_result = lf->read_range(fr);
7✔
1124

1125
        if (read_result.isErr()) {
7✔
1126
            continue;
×
1127
        }
1128

1129
        auto line_hash = read_result
1130
                             .map([cl](auto sbr) {
14✔
1131
                                 return hasher()
14✔
1132
                                     .update(sbr.get_data(), sbr.length())
7✔
1133
                                     .update(cl)
7✔
1134
                                     .to_string();
14✔
1135
                             })
1136
                             .unwrap();
7✔
1137

1138
        if (bind_values(stmt,
21✔
1139
                        lf->original_line_time(line_iter),
1140
                        lf->get_format()->get_name(),
14✔
1141
                        line_hash,
1142
                        lnav_data.ld_session_time)
1143
            != SQLITE_OK)
7✔
1144
        {
1145
            continue;
×
1146
        }
1147

1148
        const auto& line_meta = bm_pair.second;
7✔
1149
        if (line_meta.empty(bookmark_metadata::categories::any)) {
7✔
1150
            continue;
×
1151
        }
1152

1153
        if (sqlite3_bind_text(stmt,
7✔
1154
                              5,
1155
                              line_meta.bm_name.c_str(),
1156
                              line_meta.bm_name.length(),
7✔
1157
                              SQLITE_TRANSIENT)
1158
            != SQLITE_OK)
7✔
1159
        {
1160
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1161
            return;
×
1162
        }
1163

1164
        if (sqlite3_bind_text(stmt,
7✔
1165
                              6,
1166
                              line_meta.bm_comment.c_str(),
1167
                              line_meta.bm_comment.length(),
7✔
1168
                              SQLITE_TRANSIENT)
1169
            != SQLITE_OK)
7✔
1170
        {
1171
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1172
            return;
×
1173
        }
1174

1175
        std::string tags;
7✔
1176

1177
        if (!line_meta.bm_tags.empty()) {
7✔
1178
            yajlpp_gen gen;
4✔
1179

1180
            yajl_gen_config(gen, yajl_gen_beautify, false);
4✔
1181

1182
            {
1183
                yajlpp_array arr(gen);
4✔
1184

1185
                for (const auto& str : line_meta.bm_tags) {
8✔
1186
                    arr.gen(str);
4✔
1187
                }
1188
            }
4✔
1189

1190
            tags = gen.to_string_fragment().to_string();
4✔
1191
        }
4✔
1192

1193
        if (sqlite3_bind_text(
7✔
1194
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
7✔
1195
            != SQLITE_OK)
7✔
1196
        {
1197
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1198
            return;
×
1199
        }
1200

1201
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1202
            auto anno_str = logmsg_annotations_handlers.to_string(
1203
                line_meta.bm_annotations);
1✔
1204

1205
            if (sqlite3_bind_text(stmt,
1✔
1206
                                  8,
1207
                                  anno_str.c_str(),
1208
                                  anno_str.length(),
1✔
1209
                                  SQLITE_TRANSIENT)
1210
                != SQLITE_OK)
1✔
1211
            {
1212
                log_error("could not bind annotations -- %s",
×
1213
                          sqlite3_errmsg(db));
1214
                return;
×
1215
            }
1216
        } else {
1✔
1217
            sqlite3_bind_null(stmt, 8);
6✔
1218
        }
1219

1220
        if (line_meta.bm_opid.empty()) {
7✔
1221
            sqlite3_bind_null(stmt, 9);
7✔
1222
        } else {
1223
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1224
        }
1225

1226
        if (sqlite3_step(stmt) != SQLITE_DONE) {
7✔
1227
            log_error("could not execute bookmark insert statement -- %s",
×
1228
                      sqlite3_errmsg(db));
1229
            return;
×
1230
        }
1231

1232
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1233
                                          lf->get_format_ptr()->get_name(),
7✔
1234
                                          line_hash);
1235

1236
        sqlite3_reset(stmt);
7✔
1237
    }
7✔
1238
}
1239

1240
static void
1241
save_time_bookmarks()
22✔
1242
{
1243
    auto_sqlite3 db;
22✔
1244
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
22✔
1245
    auto_mem<char, sqlite3_free> errmsg;
22✔
1246
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
22✔
1247

1248
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
22✔
1249
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1250
        return;
×
1251
    }
1252

1253
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
22✔
1254
        != SQLITE_OK)
22✔
1255
    {
1256
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1257
        return;
×
1258
    }
1259

1260
    if (sqlite3_exec(
22✔
1261
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1262
        != SQLITE_OK)
22✔
1263
    {
1264
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1265
        return;
×
1266
    }
1267

1268
    {
1269
        static const char* UPDATE_NETLOCS_STMT
1270
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1271

1272
        std::set<std::string> netlocs;
22✔
1273

1274
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
22✔
1275
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
44✔
1276

1277
        if (sqlite3_prepare_v2(
22✔
1278
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1279
            != SQLITE_OK)
22✔
1280
        {
1281
            log_error("could not prepare recent_netlocs statement -- %s",
×
1282
                      sqlite3_errmsg(db));
1283
            return;
×
1284
        }
1285

1286
        for (const auto& netloc : netlocs) {
22✔
1287
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1288

1289
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1290
                log_error("could not execute bookmark insert statement -- %s",
×
1291
                          sqlite3_errmsg(db));
1292
                return;
×
1293
            }
1294

1295
            sqlite3_reset(stmt.in());
×
1296
        }
1297
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
22✔
1298
    }
22✔
1299

1300
    auto& lss = lnav_data.ld_log_source;
22✔
1301
    auto& bm = lss.get_user_bookmarks();
22✔
1302

1303
    if (sqlite3_prepare_v2(db.in(),
22✔
1304
                           "DELETE FROM bookmarks WHERE "
1305
                           " log_time = ? and log_format = ? and log_hash = ? "
1306
                           " and session_time = ?",
1307
                           -1,
1308
                           stmt.out(),
1309
                           nullptr)
1310
        != SQLITE_OK)
22✔
1311
    {
1312
        log_error("could not prepare bookmark delete statement -- %s",
×
1313
                  sqlite3_errmsg(db));
1314
        return;
×
1315
    }
1316

1317
    for (auto& marked_session_line : marked_session_lines) {
24✔
1318
        sqlite3_clear_bindings(stmt.in());
2✔
1319

1320
        if (bind_values(stmt,
4✔
1321
                        marked_session_line.sl_time,
1322
                        marked_session_line.sl_format_name,
1323
                        marked_session_line.sl_line_hash,
2✔
1324
                        lnav_data.ld_session_time)
1325
            != SQLITE_OK)
2✔
1326
        {
1327
            continue;
×
1328
        }
1329

1330
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1331
            log_error("could not execute bookmark insert statement -- %s",
×
1332
                      sqlite3_errmsg(db));
1333
            return;
×
1334
        }
1335

1336
        sqlite3_reset(stmt.in());
2✔
1337
    }
1338

1339
    marked_session_lines.clear();
22✔
1340

1341
    if (sqlite3_prepare_v2(db.in(),
22✔
1342
                           "REPLACE INTO bookmarks"
1343
                           " (log_time, log_format, log_hash, session_time, "
1344
                           "part_name, comment, tags, annotations, log_opid)"
1345
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
1346
                           -1,
1347
                           stmt.out(),
1348
                           nullptr)
1349
        != SQLITE_OK)
22✔
1350
    {
1351
        log_error("could not prepare bookmark replace statement -- %s",
×
1352
                  sqlite3_errmsg(db));
1353
        return;
×
1354
    }
1355

1356
    {
1357
        for (auto file_iter = lnav_data.ld_log_source.begin();
22✔
1358
             file_iter != lnav_data.ld_log_source.end();
45✔
1359
             ++file_iter)
23✔
1360
        {
1361
            auto lf = (*file_iter)->get_file();
23✔
1362

1363
            if (lf == nullptr) {
23✔
1364
                continue;
×
1365
            }
1366
            if (lf->size() == 0) {
23✔
1367
                continue;
×
1368
            }
1369

1370
            content_line_t base_content_line;
23✔
1371
            base_content_line = lss.get_file_base_content_line(file_iter);
23✔
1372
            base_content_line
1373
                = content_line_t(base_content_line + lf->size() - 1);
23✔
1374

1375
            if (!bind_line(db.in(),
23✔
1376
                           stmt.in(),
1377
                           base_content_line,
1378
                           lnav_data.ld_session_time))
1379
            {
1380
                continue;
×
1381
            }
1382

1383
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
23✔
1384
                log_error("could not bind log hash -- %s",
×
1385
                          sqlite3_errmsg(db.in()));
1386
                return;
×
1387
            }
1388

1389
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
23✔
1390
                log_error("could not execute bookmark insert statement -- %s",
×
1391
                          sqlite3_errmsg(db));
1392
                return;
×
1393
            }
1394

1395
            sqlite3_reset(stmt.in());
23✔
1396
        }
23✔
1397
    }
1398

1399
    save_user_bookmarks(db.in(), stmt.in(), bm[&textview_curses::BM_USER]);
22✔
1400
    for (const auto& ldd : lss) {
45✔
1401
        auto* lf = ldd->get_file_ptr();
23✔
1402
        if (lf == nullptr) {
23✔
1403
            continue;
×
1404
        }
1405

1406
        save_meta_bookmarks(db.in(), stmt.in(), lf);
23✔
1407
    }
1408

1409
    if (sqlite3_prepare_v2(db.in(),
22✔
1410
                           "DELETE FROM time_offset WHERE "
1411
                           " log_time = ? and log_format = ? and log_hash = ? "
1412
                           " and session_time = ?",
1413
                           -1,
1414
                           stmt.out(),
1415
                           NULL)
1416
        != SQLITE_OK)
22✔
1417
    {
1418
        log_error("could not prepare time_offset delete statement -- %s",
×
1419
                  sqlite3_errmsg(db));
1420
        return;
×
1421
    }
1422

1423
    for (auto& offset_session_line : offset_session_lines) {
23✔
1424
        sqlite3_clear_bindings(stmt.in());
1✔
1425

1426
        if (bind_values(stmt,
2✔
1427
                        offset_session_line.sl_time,
1428
                        offset_session_line.sl_format_name,
1429
                        offset_session_line.sl_line_hash,
1✔
1430
                        lnav_data.ld_session_time)
1431
            != SQLITE_OK)
1✔
1432
        {
1433
            continue;
×
1434
        }
1435

1436
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1437
            log_error("could not execute bookmark insert statement -- %s",
×
1438
                      sqlite3_errmsg(db));
1439
            return;
×
1440
        }
1441

1442
        sqlite3_reset(stmt.in());
1✔
1443
    }
1444

1445
    offset_session_lines.clear();
22✔
1446

1447
    if (sqlite3_prepare_v2(db.in(),
22✔
1448
                           "REPLACE INTO time_offset"
1449
                           " (log_time, log_format, log_hash, session_time, "
1450
                           "offset_sec, offset_usec)"
1451
                           " VALUES (?, ?, ?, ?, ?, ?)",
1452
                           -1,
1453
                           stmt.out(),
1454
                           nullptr)
1455
        != SQLITE_OK)
22✔
1456
    {
1457
        log_error("could not prepare time_offset replace statement -- %s",
×
1458
                  sqlite3_errmsg(db));
1459
        return;
×
1460
    }
1461

1462
    {
1463
        for (auto file_iter = lnav_data.ld_log_source.begin();
22✔
1464
             file_iter != lnav_data.ld_log_source.end();
45✔
1465
             ++file_iter)
23✔
1466
        {
1467
            auto lf = (*file_iter)->get_file();
23✔
1468
            if (lf == nullptr) {
23✔
1469
                continue;
×
1470
            }
1471
            if (lf->size() == 0) {
23✔
1472
                continue;
×
1473
            }
1474

1475
            if (bind_values(stmt,
69✔
1476
                            lf->original_line_time(lf->begin()),
1477
                            lf->get_format()->get_name(),
46✔
1478
                            lf->get_content_id(),
23✔
1479
                            lnav_data.ld_session_time)
1480
                != SQLITE_OK)
23✔
1481
            {
1482
                continue;
×
1483
            }
1484

1485
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
23✔
1486
                log_error("could not bind log hash -- %s",
×
1487
                          sqlite3_errmsg(db.in()));
1488
                return;
×
1489
            }
1490

1491
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
23✔
1492
                log_error("could not bind log hash -- %s",
×
1493
                          sqlite3_errmsg(db.in()));
1494
                return;
×
1495
            }
1496

1497
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
23✔
1498
                log_error("could not execute bookmark insert statement -- %s",
×
1499
                          sqlite3_errmsg(db));
1500
                return;
×
1501
            }
1502

1503
            sqlite3_reset(stmt.in());
23✔
1504
        }
23✔
1505
    }
1506

1507
    for (const auto& ls : lss) {
45✔
1508
        if (ls->get_file() == nullptr) {
23✔
1509
            continue;
20✔
1510
        }
1511

1512
        const auto lf = ls->get_file();
23✔
1513
        if (!lf->is_time_adjusted()) {
23✔
1514
            continue;
20✔
1515
        }
1516

1517
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
1518
        auto offset = lf->get_time_offset();
3✔
1519

1520
        bind_values(stmt.in(),
6✔
1521
                    lf->original_line_time(line_iter),
1522
                    lf->get_format()->get_name(),
6✔
1523
                    lf->get_content_id(),
3✔
1524
                    lnav_data.ld_session_time,
1525
                    offset.tv_sec,
1526
                    offset.tv_usec);
1527

1528
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
1529
            log_error("could not execute bookmark insert statement -- %s",
×
1530
                      sqlite3_errmsg(db));
1531
            return;
×
1532
        }
1533

1534
        sqlite3_reset(stmt.in());
3✔
1535
    }
23✔
1536

1537
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
22✔
1538
        != SQLITE_OK)
22✔
1539
    {
1540
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1541
        return;
×
1542
    }
1543

1544
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
22✔
1545
        != SQLITE_OK)
22✔
1546
    {
1547
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
1548
        return;
×
1549
    }
1550

1551
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
22✔
1552
        != SQLITE_OK)
22✔
1553
    {
1554
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
1555
        return;
×
1556
    }
1557
}
22✔
1558

1559
static void
1560
save_session_with_id(const std::string& session_id)
21✔
1561
{
1562
    auto_mem<FILE> file(fclose);
21✔
1563
    yajl_gen handle = nullptr;
21✔
1564

1565
    /* TODO: save the last search query */
1566

1567
    log_info("saving session with id: %s", session_id.c_str());
21✔
1568

1569
    auto view_base_name
1570
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
42✔
1571
                      session_id,
1572
                      lnav_data.ld_session_time,
1573
                      getppid());
21✔
1574
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
21✔
1575
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
21✔
1576

1577
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
21✔
1578
        perror("Unable to open session file");
×
1579
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
21✔
1580
        perror("Unable to create yajl_gen object");
×
1581
    } else {
1582
        yajl_gen_config(
21✔
1583
            handle, yajl_gen_print_callback, yajl_writer, file.in());
1584

1585
        {
1586
            yajlpp_map root_map(handle);
21✔
1587

1588
            root_map.gen("save-time");
21✔
1589
            root_map.gen((long long) time(nullptr));
21✔
1590

1591
            root_map.gen("time-offset");
21✔
1592
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
21✔
1593

1594
            root_map.gen("files");
21✔
1595

1596
            {
1597
                yajlpp_array file_list(handle);
21✔
1598

1599
                for (auto& ld_file_name :
21✔
1600
                     lnav_data.ld_active_files.fc_file_names)
65✔
1601
                {
1602
                    file_list.gen(ld_file_name.first);
23✔
1603
                }
1604
            }
21✔
1605

1606
            root_map.gen("file-states");
21✔
1607

1608
            {
1609
                yajlpp_map file_states(handle);
21✔
1610

1611
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
44✔
1612
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
23✔
1613

1614
                    file_states.gen(lf->get_filename().native());
23✔
1615

1616
                    {
1617
                        yajlpp_map file_state(handle);
23✔
1618

1619
                        file_state.gen("visible");
23✔
1620
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
23✔
1621
                    }
23✔
1622
                }
1623
            }
21✔
1624

1625
            root_map.gen("views");
21✔
1626

1627
            {
1628
                yajlpp_map top_view_map(handle);
21✔
1629

1630
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
210✔
1631
                    auto& tc = lnav_data.ld_views[lpc];
189✔
1632
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
189✔
1633

1634
                    top_view_map.gen(lnav_view_strings[lpc]);
189✔
1635

1636
                    yajlpp_map view_map(handle);
189✔
1637

1638
                    view_map.gen("top_line");
189✔
1639

1640
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
189✔
1641
                        view_map.gen(-1LL);
180✔
1642
                    } else {
1643
                        view_map.gen((long long) tc.get_top());
9✔
1644
                    }
1645

1646
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
236✔
1647
                        && tc.get_inner_height() > 0_vl
2✔
1648
                        && tc.get_selection() != tc.get_inner_height() - 1)
236✔
1649
                    {
1650
                        auto sel = tc.get_selection();
1✔
1651
                        if (sel) {
1✔
1652
                            view_map.gen("focused_line");
1✔
1653
                            view_map.gen((long long) sel.value());
1✔
1654

1655
                            if (ta != nullptr) {
1✔
1656
                                auto anchor_opt
1657
                                    = ta->anchor_for_row(sel.value());
1✔
1658
                                if (anchor_opt) {
1✔
1659
                                    view_map.gen("anchor");
1✔
1660
                                    view_map.gen(anchor_opt.value());
1✔
1661
                                }
1662
                            }
1✔
1663
                        }
1664
                    }
1665

1666
                    view_map.gen("search");
189✔
1667
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
189✔
1668

1669
                    view_map.gen("word_wrap");
189✔
1670
                    view_map.gen(tc.get_word_wrap());
189✔
1671

1672
                    auto* tss = tc.get_sub_source();
189✔
1673
                    if (tss == nullptr) {
189✔
1674
                        continue;
42✔
1675
                    }
1676

1677
                    view_map.gen("filtering");
147✔
1678
                    view_map.gen(tss->tss_apply_filters);
147✔
1679

1680
                    view_map.gen("commands");
147✔
1681
                    yajlpp_array cmd_array(handle);
147✔
1682

1683
                    tss->add_commands_for_session(
147✔
1684
                        [&](auto& cmd) { cmd_array.gen(cmd); });
157✔
1685
                }
189✔
1686
            }
21✔
1687
        }
21✔
1688

1689
        yajl_gen_clear(handle);
21✔
1690
        yajl_gen_free(handle);
21✔
1691

1692
        fclose(file.release());
21✔
1693

1694
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
21✔
1695

1696
        log_info("Saved session: %s", view_file_name.c_str());
21✔
1697
    }
1698
}
21✔
1699

1700
void
1701
save_session()
22✔
1702
{
1703
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
22✔
1704
        log_info("secure mode is enabled, not saving session");
×
1705
        return;
×
1706
    }
1707

1708
    static auto op = lnav_operation{"save_session"};
22✔
1709

1710
    auto op_guard = lnav_opid_guard::internal(op);
22✔
1711

1712
    log_debug("BEGIN save_session");
22✔
1713
    save_time_bookmarks();
22✔
1714

1715
    const auto opt_session_id = compute_session_id();
22✔
1716
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
42✔
1717
    for (const auto& pair : lnav_data.ld_session_id) {
26✔
1718
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
1719
            continue;
3✔
1720
        }
1721
        save_session_with_id(pair.first);
1✔
1722
    }
1723
    log_debug("END save_session");
22✔
1724
}
22✔
1725

1726
void
1727
reset_session()
6✔
1728
{
1729
    log_info("reset session: time=%d", lnav_data.ld_session_time);
6✔
1730

1731
    save_session();
6✔
1732

1733
    lnav_data.ld_session_time = time(nullptr);
6✔
1734
    session_data.sd_file_states.clear();
6✔
1735

1736
    for (auto& tc : lnav_data.ld_views) {
60✔
1737
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
1738
        auto& hmap = tc.get_highlights();
54✔
1739
        auto hl_iter = hmap.begin();
54✔
1740

1741
        while (hl_iter != hmap.end()) {
552✔
1742
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
1743
                ++hl_iter;
498✔
1744
            } else {
1745
                hmap.erase(hl_iter++);
×
1746
            }
1747
        }
1748

1749
        if (ttt != nullptr) {
54✔
1750
            ttt->clear_min_max_row_times();
36✔
1751
        }
1752
    }
1753

1754
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
1755
        lf->reset_state();
8✔
1756
    }
1757

1758
    // XXX clean this up
1759
    lnav_data.ld_log_source.set_force_rebuild();
6✔
1760
    lnav_data.ld_log_source.set_marked_only(false);
6✔
1761
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
1762
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
1763
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
1764
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
1765
    rebuild_indexes(std::nullopt);
6✔
1766

1767
    lnav_data.ld_db_row_source.reset_user_state();
6✔
1768

1769
    for (auto& tc : lnav_data.ld_views) {
60✔
1770
        text_sub_source* tss = tc.get_sub_source();
54✔
1771

1772
        if (tss == nullptr) {
54✔
1773
            continue;
12✔
1774
        }
1775
        tss->get_filters().clear_filters();
42✔
1776
        tss->tss_apply_filters = true;
42✔
1777
        tss->text_filters_changed();
42✔
1778
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
1779
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
1780
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
1781
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
1782
        tc.reload_data();
42✔
1783
    }
1784

1785
    lnav_data.ld_filter_view.reload_data();
6✔
1786
    lnav_data.ld_files_view.reload_data();
6✔
1787
    for (const auto& format : log_format::get_root_formats()) {
438✔
1788
        auto* elf = dynamic_cast<external_log_format*>(format.get());
432✔
1789

1790
        if (elf == nullptr) {
432✔
1791
            continue;
30✔
1792
        }
1793

1794
        bool changed = false;
402✔
1795
        for (const auto& vd : elf->elf_value_defs) {
5,826✔
1796
            if (vd.second->vd_meta.lvm_user_hidden) {
5,424✔
1797
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
1798
                changed = true;
×
1799
            }
1800
        }
1801
        if (changed) {
402✔
1802
            elf->elf_value_defs_state->vds_generation += 1;
×
1803
        }
1804
    }
1805
}
6✔
1806

1807
void
1808
lnav::session::restore_view_states()
24✔
1809
{
1810
    static auto op = lnav_operation{__FUNCTION__};
24✔
1811

1812
    auto op_guard = lnav_opid_guard::internal(op);
24✔
1813

1814
    log_debug("restoring view states");
24✔
1815
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
240✔
1816
        const auto& vs = session_data.sd_view_states[view_index];
216✔
1817
        auto& tview = lnav_data.ld_views[view_index];
216✔
1818
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
216✔
1819

1820
        if (!vs.vs_search.empty()) {
216✔
1821
            tview.execute_search(vs.vs_search);
×
1822
            tview.set_follow_search_for(-1, {});
×
1823
        }
1824
        tview.set_word_wrap(vs.vs_word_wrap);
216✔
1825
        lnav::set::small<std::string> curr_cmds;
216✔
1826
        auto* tss = tview.get_sub_source();
216✔
1827
        if (tview.get_sub_source() != nullptr) {
216✔
1828
            tss->tss_apply_filters = vs.vs_filtering;
168✔
1829
            tss->add_commands_for_session([&](auto& cmd) {
168✔
1830
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
1831
                                  .split_when(string_fragment::tag1{' '})
5✔
1832
                                  .first;
1833
                curr_cmds.insert(cmd_sf.to_string());
5✔
1834
            });
5✔
1835
        }
1836
        for (const auto& cmdline : vs.vs_commands) {
222✔
1837
            auto cmdline_sf = string_fragment::from_str(cmdline);
6✔
1838
            auto active = ensure_view(&tview);
6✔
1839
            auto [cmd_sf, _cmdline_rem]
6✔
1840
                = cmdline_sf.split_when(string_fragment::tag1{' '});
6✔
1841
            if (curr_cmds.contains(cmd_sf.to_string())) {
6✔
1842
                log_debug("view %s command '%.*s' already active",
3✔
1843
                          tview.get_title().c_str(),
1844
                          cmd_sf.length(),
1845
                          cmd_sf.data());
1846
                continue;
3✔
1847
            }
1848
            auto exec_cmd_res
1849
                = execute_command(lnav_data.ld_exec_context, cmdline);
3✔
1850
            if (exec_cmd_res.isOk()) {
3✔
1851
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
3✔
1852
            } else {
UNCOV
1853
                log_error("Result: %s",
×
1854
                          exec_cmd_res.unwrapErr()
1855
                              .to_attr_line()
1856
                              .get_string()
1857
                              .c_str());
1858
            }
1859
            if (!active) {
3✔
UNCOV
1860
                lnav_data.ld_view_stack.pop_back();
×
UNCOV
1861
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
1862
                    // XXX
UNCOV
1863
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
1864
                        auto tss = tc->get_sub_source();
×
1865
                        tss->text_filters_changed();
×
1866
                        tc->reload_data();
×
1867
                    }
1868
                };
1869
            }
1870
        }
3✔
1871

1872
        auto has_loc = tview.get_selection().has_value();
216✔
1873
        if (!has_loc && vs.vs_top >= 0
57✔
1874
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
273✔
1875
                || tview.get_top() == tview.get_top_for_last_row()))
216✔
1876
        {
1877
            log_info("restoring %s view top: %d",
13✔
1878
                     lnav_view_strings[view_index].data(),
1879
                     vs.vs_top);
1880
            tview.set_top(vis_line_t(vs.vs_top), true);
13✔
1881
        }
1882
        if (!has_loc && vs.vs_selection) {
216✔
1883
            log_info("restoring %s view selection: %d",
×
1884
                     lnav_view_strings[view_index].data(),
1885
                     vs.vs_selection.value());
1886
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
×
1887
        }
1888
        auto sel = tview.get_selection();
216✔
1889
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
57✔
1890
            && !vs.vs_anchor->empty())
273✔
1891
        {
1892
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
1893

1894
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
1895
                log_info("%s view anchor mismatch %s != %s",
×
1896
                         lnav_view_strings[view_index].data(),
1897
                         curr_anchor.value_or("").c_str(),
1898
                         vs.vs_anchor.value().c_str());
1899

1900
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
1901
                if (row_opt) {
×
1902
                    tview.set_selection(row_opt.value());
×
1903
                }
1904
            }
1905
        }
1906
        sel = tview.get_selection();
216✔
1907
        if (!sel) {
216✔
1908
            auto height = tview.get_inner_height();
57✔
1909
            if (height == 0) {
57✔
1910
            } else if (view_index == LNV_TEXT) {
1✔
1911
                auto lf = lnav_data.ld_text_source.current_file();
×
1912
                if (lf != nullptr) {
×
1913
                    switch (lf->get_text_format()) {
×
1914
                        case text_format_t::TF_UNKNOWN:
×
1915
                        case text_format_t::TF_LOG: {
1916
                            if (height > 0_vl) {
×
1917
                                tview.set_selection(height - 1_vl);
×
1918
                            }
1919
                            break;
×
1920
                        }
1921
                        default:
×
1922
                            tview.set_selection(0_vl);
×
1923
                            break;
×
1924
                    }
1925
                }
1926
            } else if (view_index == LNV_LOG) {
1✔
1927
                tview.set_selection(height - 1_vl);
×
1928
            } else {
1929
                tview.set_selection(0_vl);
1✔
1930
            }
1931
        }
1932
        log_info("%s view actual top/selection: %d/%d",
216✔
1933
                 lnav_view_strings[view_index].data(),
1934
                 tview.get_top(),
1935
                 tview.get_selection().value_or(-1_vl));
1936
    }
216✔
1937
}
24✔
1938

1939
void
1940
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
1941
{
1942
    constexpr const char* STMT = R"(
×
1943
       INSERT INTO regex101_entries
1944
          (format_name, regex_name, permalink, delete_code)
1945
          VALUES (?, ?, ?, ?);
1946
)";
1947

1948
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
1949
    auto_sqlite3 db;
×
1950

1951
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
1952
        return;
×
1953
    }
1954

1955
    auto_mem<char, sqlite3_free> errmsg;
×
1956
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
1957
        != SQLITE_OK)
×
1958
    {
1959
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1960
        return;
×
1961
    }
1962

1963
    auto prep_res = prepare_stmt(db.in(),
1964
                                 STMT,
1965
                                 ei.re_format_name,
×
1966
                                 ei.re_regex_name,
×
1967
                                 ei.re_permalink,
×
1968
                                 ei.re_delete_code);
×
1969

1970
    if (prep_res.isErr()) {
×
1971
        return;
×
1972
    }
1973

1974
    auto ps = prep_res.unwrap();
×
1975

1976
    ps.execute();
×
1977
}
1978

1979
template<>
1980
struct from_sqlite<lnav::session::regex101::entry> {
1981
    inline lnav::session::regex101::entry operator()(int argc,
1982
                                                     sqlite3_value** argv,
1983
                                                     int argi)
1984
    {
1985
        return {
1986
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
1987
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
1988
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
1989
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
1990
        };
1991
    }
1992
};
1993

1994
Result<std::vector<lnav::session::regex101::entry>, std::string>
1995
lnav::session::regex101::get_entries()
×
1996
{
1997
    constexpr const char* STMT = R"(
×
1998
       SELECT * FROM regex101_entries;
1999
)";
2000

2001
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2002
    auto_sqlite3 db;
×
2003

2004
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2005
        return Err(std::string());
×
2006
    }
2007

2008
    auto_mem<char, sqlite3_free> errmsg;
×
2009
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2010
        != SQLITE_OK)
×
2011
    {
2012
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2013
        return Err(std::string(errmsg));
×
2014
    }
2015

2016
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2017
    bool done = false;
×
2018
    std::vector<entry> retval;
×
2019

2020
    while (!done) {
×
2021
        auto fetch_res = ps.fetch_row<entry>();
×
2022

2023
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2024
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2025
        }
2026

2027
        fetch_res.match(
×
2028
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2029
            [](const prepared_stmt::fetch_error&) {},
×
2030
            [&retval](entry en) { retval.emplace_back(en); });
×
2031
    }
2032
    return Ok(retval);
×
2033
}
2034

2035
void
2036
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2037
                                      const std::string& regex_name)
2038
{
2039
    constexpr const char* STMT = R"(
×
2040
       DELETE FROM regex101_entries WHERE
2041
          format_name = ? AND regex_name = ?;
2042
)";
2043

2044
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2045
    auto_sqlite3 db;
×
2046

2047
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2048
        return;
×
2049
    }
2050

2051
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2052

2053
    if (prep_res.isErr()) {
×
2054
        return;
×
2055
    }
2056

2057
    auto ps = prep_res.unwrap();
×
2058

2059
    ps.execute();
×
2060
}
2061

2062
lnav::session::regex101::get_result_t
2063
lnav::session::regex101::get_entry(const std::string& format_name,
×
2064
                                   const std::string& regex_name)
2065
{
2066
    constexpr const char* STMT = R"(
×
2067
       SELECT * FROM regex101_entries WHERE
2068
          format_name = ? AND regex_name = ?;
2069
    )";
2070

2071
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2072
    auto_sqlite3 db;
×
2073

2074
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2075
        return error{std::string()};
×
2076
    }
2077

2078
    auto_mem<char, sqlite3_free> errmsg;
×
2079
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2080
        != SQLITE_OK)
×
2081
    {
2082
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2083
        return error{std::string(errmsg)};
×
2084
    }
2085

2086
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2087
    if (prep_res.isErr()) {
×
2088
        return error{prep_res.unwrapErr()};
×
2089
    }
2090

2091
    auto ps = prep_res.unwrap();
×
2092
    return ps.fetch_row<entry>().match(
×
2093
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2094
            return error{fe.fe_msg};
×
2095
        },
2096
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2097
            return no_entry{};
×
2098
        },
2099
        [](const entry& en) -> get_result_t { return en; });
×
2100
}
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