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

tstack / lnav / 23819016647-2908

31 Mar 2026 08:53PM UTC coverage: 69.077% (-0.008%) from 69.085%
23819016647-2908

push

github

tstack
[all_opids] add the name of the opid description to the table

53 of 93 new or added lines in 9 files covered. (56.99%)

5 existing lines in 4 files now uncovered.

53260 of 77102 relevant lines covered (69.08%)

535699.83 hits per line

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

74.52
/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
    sticky integer DEFAULT 0,
83

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

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

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

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

102
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
103

104
    PRIMARY KEY (netloc)
105
);
106

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

113
    PRIMARY KEY (format_name, regex_name),
114

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

121
CREATE TABLE IF NOT EXISTS text_bookmarks2 (
122
    file_path    text NOT NULL,
123
    line_number  integer NOT NULL,
124
    line_hash    text NOT NULL,
125
    mark_type    text NOT NULL DEFAULT 'user',
126
    session_time integer NOT NULL,
127

128
    PRIMARY KEY (file_path, line_number, mark_type, session_time),
129

130
    CHECK(line_number >= 0),
131
    CHECK(mark_type IN ('user', 'sticky'))
132
);
133

134
CREATE TABLE IF NOT EXISTS timeline_bookmarks (
135
    row_type     text NOT NULL,
136
    row_name     text NOT NULL,
137
    mark_type    text NOT NULL DEFAULT 'sticky',
138
    session_time integer NOT NULL,
139

140
    PRIMARY KEY (row_type, row_name, mark_type, session_time),
141

142
    CHECK(mark_type IN ('user', 'sticky'))
143
);
144
)";
145

146
static const char* const BOOKMARK_LRU_STMT
147
    = "DELETE FROM bookmarks WHERE access_time <= "
148
      "  (SELECT DISTINCT access_time FROM bookmarks "
149
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 50000)";
150

151
static const char* const NETLOC_LRU_STMT
152
    = "DELETE FROM recent_netlocs WHERE access_time <= "
153
      "  (SELECT DISTINCT access_time FROM bookmarks "
154
      "   ORDER BY access_time DESC LIMIT 1 OFFSET 10)";
155

156
static const char* const TEXT_BOOKMARK_LRU_STMT
157
    = "DELETE FROM text_bookmarks2 WHERE session_time <= "
158
      "  (SELECT DISTINCT session_time FROM text_bookmarks2 "
159
      "   ORDER BY session_time DESC LIMIT 1 OFFSET 50000)";
160

161
static const char* const TIMELINE_BOOKMARK_LRU_STMT
162
    = "DELETE FROM timeline_bookmarks WHERE session_time <= "
163
      "  (SELECT DISTINCT session_time FROM timeline_bookmarks "
164
      "   ORDER BY session_time DESC LIMIT 1 OFFSET 50000)";
165

166
static const char* const UPGRADE_STMTS[] = {
167
    R"(ALTER TABLE bookmarks ADD COLUMN comment text DEFAULT '';)",
168
    R"(ALTER TABLE bookmarks ADD COLUMN tags text DEFAULT '';)",
169
    R"(ALTER TABLE bookmarks ADD COLUMN annotations text DEFAULT NULL;)",
170
    R"(ALTER TABLE bookmarks ADD COLUMN log_opid text DEFAULT NULL;)",
171
    R"(ALTER TABLE bookmarks ADD COLUMN sticky integer DEFAULT 0;)",
172
    R"(DROP TABLE IF EXISTS text_bookmarks;)",
173
};
174

175
static constexpr size_t MAX_SESSIONS = 8;
176
static constexpr size_t MAX_SESSION_FILE_COUNT = 256;
177

178
struct session_line {
179
    session_line(struct timeval tv,
14✔
180
                 intern_string_t format_name,
181
                 std::string line_hash)
182
        : sl_time(tv), sl_format_name(format_name),
14✔
183
          sl_line_hash(std::move(line_hash))
14✔
184
    {
185
    }
14✔
186

187
    struct timeval sl_time;
188
    intern_string_t sl_format_name;
189
    std::string sl_line_hash;
190
};
191

192
static std::vector<session_line> marked_session_lines;
193
static std::vector<session_line> offset_session_lines;
194

195
static bool
196
bind_line(sqlite3* db,
28✔
197
          sqlite3_stmt* stmt,
198
          content_line_t cl,
199
          time_t session_time)
200
{
201
    auto& lss = lnav_data.ld_log_source;
28✔
202
    auto lf = lss.find(cl);
28✔
203

204
    if (lf == nullptr) {
28✔
205
        return false;
×
206
    }
207

208
    sqlite3_clear_bindings(stmt);
28✔
209

210
    auto line_iter = lf->begin() + cl;
28✔
211
    auto fr = lf->get_file_range(line_iter, false);
28✔
212
    auto read_result = lf->read_range(fr);
28✔
213

214
    if (read_result.isErr()) {
28✔
215
        return false;
×
216
    }
217

218
    auto line_hash = read_result
219
                         .map([cl](auto sbr) {
56✔
220
                             return hasher()
56✔
221
                                 .update(sbr.get_data(), sbr.length())
28✔
222
                                 .update(cl)
28✔
223
                                 .to_string();
56✔
224
                         })
225
                         .unwrap();
28✔
226

227
    return bind_values(stmt,
56✔
228
                       lf->original_line_time(line_iter),
229
                       lf->get_format()->get_name(),
56✔
230
                       line_hash,
231
                       session_time)
232
        == SQLITE_OK;
28✔
233
}
28✔
234

235
struct session_file_info {
236
    session_file_info(int timestamp, std::string id, std::string path)
55✔
237
        : sfi_timestamp(timestamp), sfi_id(std::move(id)),
55✔
238
          sfi_path(std::move(path))
55✔
239
    {
240
    }
55✔
241

242
    bool operator<(const session_file_info& other) const
78✔
243
    {
244
        if (this->sfi_timestamp < other.sfi_timestamp) {
78✔
245
            return true;
×
246
        }
247
        if (this->sfi_path < other.sfi_path) {
78✔
248
            return true;
×
249
        }
250
        return false;
78✔
251
    }
252

253
    int sfi_timestamp;
254
    std::string sfi_id;
255
    std::string sfi_path;
256
};
257

258
static void
259
cleanup_session_data()
31✔
260
{
261
    static_root_mem<glob_t, globfree> session_file_list;
31✔
262
    std::list<struct session_file_info> session_info_list;
31✔
263
    std::map<std::string, int> session_count;
31✔
264
    auto session_file_pattern = lnav::paths::dotlnav() / "*-*.ts*.json";
31✔
265

266
    if (glob(
31✔
267
            session_file_pattern.c_str(), 0, nullptr, session_file_list.inout())
268
        == 0)
31✔
269
    {
270
        for (size_t lpc = 0; lpc < session_file_list->gl_pathc; lpc++) {
83✔
271
            const char* path = session_file_list->gl_pathv[lpc];
55✔
272
            char hash_id[64];
273
            int timestamp;
274
            const char* base;
275

276
            base = strrchr(path, '/');
55✔
277
            if (base == nullptr) {
55✔
278
                continue;
×
279
            }
280
            base += 1;
55✔
281
            if (sscanf(base, "file-%63[^.].ts%d.json", hash_id, &timestamp)
55✔
282
                == 2)
55✔
283
            {
284
                session_count[hash_id] += 1;
×
285
                session_info_list.emplace_back(timestamp, hash_id, path);
×
286
            }
287
            if (sscanf(base,
55✔
288
                       "view-info-%63[^.].ts%d.ppid%*d.json",
289
                       hash_id,
290
                       &timestamp)
291
                == 2)
55✔
292
            {
293
                session_count[hash_id] += 1;
55✔
294
                session_info_list.emplace_back(timestamp, hash_id, path);
55✔
295
            }
296
        }
297
    }
298

299
    session_info_list.sort();
31✔
300

301
    size_t session_loops = 0;
31✔
302

303
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
31✔
304
        const session_file_info& front = session_info_list.front();
×
305

306
        session_loops += 1;
×
307
        if (session_loops < MAX_SESSION_FILE_COUNT
×
308
            && session_count[front.sfi_id] == 1)
×
309
        {
310
            session_info_list.splice(session_info_list.end(),
×
311
                                     session_info_list,
312
                                     session_info_list.begin());
×
313
        } else {
314
            if (remove(front.sfi_path.c_str()) != 0) {
×
315
                log_error("Unable to remove session file: %s -- %s",
×
316
                          front.sfi_path.c_str(),
317
                          strerror(errno));
318
            }
319
            session_count[front.sfi_id] -= 1;
×
320
            session_info_list.pop_front();
×
321
        }
322
    }
323

324
    session_info_list.sort();
31✔
325

326
    while (session_info_list.size() > MAX_SESSION_FILE_COUNT) {
31✔
327
        const session_file_info& front = session_info_list.front();
×
328

329
        if (remove(front.sfi_path.c_str()) != 0) {
×
330
            log_error("Unable to remove session file: %s -- %s",
×
331
                      front.sfi_path.c_str(),
332
                      strerror(errno));
333
        }
334
        session_count[front.sfi_id] -= 1;
×
335
        session_info_list.pop_front();
×
336
    }
337
}
31✔
338

339
void
340
init_session()
677✔
341
{
342
    lnav_data.ld_session_time = time(nullptr);
677✔
343
    lnav_data.ld_session_id.clear();
677✔
344
    session_data.sd_view_states[LNV_LOG].vs_top = -1;
677✔
345
}
677✔
346

347
static std::optional<std::string>
348
compute_session_id()
59✔
349
{
350
    bool has_files = false;
59✔
351
    hasher h;
59✔
352

353
    for (auto& ld_file_name : lnav_data.ld_active_files.fc_file_names) {
119✔
354
        if (!ld_file_name.second.loo_include_in_session) {
60✔
355
            continue;
×
356
        }
357
        has_files = true;
60✔
358
        h.update(ld_file_name.first);
60✔
359
    }
360
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
119✔
361
        if (lf->is_valid_filename()) {
60✔
362
            continue;
59✔
363
        }
364
        if (!lf->get_open_options().loo_include_in_session) {
1✔
365
            continue;
×
366
        }
367

368
        has_files = true;
1✔
369
        h.update(lf->get_content_id());
1✔
370
    }
371
    if (!has_files) {
59✔
372
        return std::nullopt;
3✔
373
    }
374

375
    return h.to_string();
56✔
376
}
377

378
std::optional<session_pair_t>
379
scan_sessions()
31✔
380
{
381
    static_root_mem<glob_t, globfree> view_info_list;
31✔
382

383
    cleanup_session_data();
31✔
384

385
    const auto session_id = compute_session_id();
31✔
386
    if (!session_id) {
31✔
387
        return std::nullopt;
1✔
388
    }
389
    auto& session_file_names = lnav_data.ld_session_id[session_id.value()];
30✔
390
    session_file_names.clear();
30✔
391

392
    auto view_info_pattern_base
393
        = fmt::format(FMT_STRING("view-info-{}.*.json"), session_id.value());
90✔
394
    auto view_info_pattern = lnav::paths::dotlnav() / view_info_pattern_base;
30✔
395
    if (glob(view_info_pattern.c_str(), 0, nullptr, view_info_list.inout())
30✔
396
        == 0)
30✔
397
    {
398
        for (size_t lpc = 0; lpc < view_info_list->gl_pathc; lpc++) {
69✔
399
            const char* path = view_info_list->gl_pathv[lpc];
41✔
400
            int timestamp, ppid, rc;
401
            const char* base;
402

403
            base = strrchr(path, '/');
41✔
404
            if (base == nullptr) {
41✔
405
                continue;
×
406
            }
407
            base += 1;
41✔
408
            if ((rc = sscanf(base,
41✔
409
                             "view-info-%*[^.].ts%d.ppid%d.json",
410
                             &timestamp,
411
                             &ppid))
412
                == 2)
41✔
413
            {
414
                ppid_time_pair_t ptp;
41✔
415

416
                ptp.first = (ppid == getppid()) ? 1 : 0;
41✔
417
                ptp.second = timestamp;
41✔
418
                session_file_names.emplace_back(ptp, path);
41✔
419
            }
420
        }
421
    }
422

423
    session_file_names.sort();
30✔
424

425
    while (session_file_names.size() > MAX_SESSIONS) {
30✔
426
        const std::string& name = session_file_names.front().second;
×
427

428
        if (remove(name.c_str()) != 0) {
×
429
            log_error("Unable to remove session: %s -- %s",
×
430
                      name.c_str(),
431
                      strerror(errno));
432
        }
433
        session_file_names.pop_front();
×
434
    }
435

436
    if (session_file_names.empty()) {
30✔
437
        return std::nullopt;
2✔
438
    }
439

440
    return std::make_optional(session_file_names.back());
28✔
441
}
31✔
442

443
static void load_text_bookmarks(sqlite3* db);
444
static void load_timeline_bookmarks(sqlite3* db);
445

446
void
447
load_time_bookmarks()
31✔
448
{
449
    static const char* const BOOKMARK_STMT = R"(
450
       SELECT
451
         log_time,
452
         log_format,
453
         log_hash,
454
         session_time,
455
         part_name,
456
         access_time,
457
         comment,
458
         tags,
459
         annotations,
460
         log_opid,
461
         sticky,
462
         session_time=? AS same_session
463
       FROM bookmarks WHERE
464
         log_time BETWEEN ? AND ? AND
465
         log_format = ?
466
       ORDER BY same_session DESC, session_time DESC
467
)";
468

469
    static const char* const TIME_OFFSET_STMT = R"(
470
       SELECT
471
         *,
472
         session_time=? AS same_session
473
       FROM time_offset
474
       WHERE
475
         log_hash = ? AND
476
         log_time BETWEEN ? AND ? AND
477
         log_format = ?
478
       ORDER BY
479
         same_session DESC,
480
         session_time DESC
481
)";
482

483
    static auto op = lnav_operation{__FUNCTION__};
31✔
484

485
    auto op_guard = lnav_opid_guard::internal(op);
31✔
486
    auto& lss = lnav_data.ld_log_source;
31✔
487
    auto_sqlite3 db;
31✔
488
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
31✔
489
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
31✔
490
    bool reload_needed = false;
31✔
491
    bool meta_loaded = false;
31✔
492
    auto_mem<char, sqlite3_free> errmsg;
31✔
493

494
    log_info("loading bookmark db: %s", db_path.c_str());
31✔
495

496
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
31✔
497
        return;
×
498
    }
499

500
    for (const char* upgrade_stmt : UPGRADE_STMTS) {
217✔
501
        auto rc = sqlite3_exec(
186✔
502
            db.in(), upgrade_stmt, nullptr, nullptr, errmsg.out());
503
        if (rc != SQLITE_OK) {
186✔
504
            auto exterr = sqlite3_extended_errcode(db.in());
155✔
505
            log_error("unable to upgrade bookmark table -- (%d/%d) %s",
155✔
506
                      rc,
507
                      exterr,
508
                      errmsg.in());
509
        }
510
    }
511

512
    {
513
        auto netloc_prep_res
514
            = prepare_stmt(db.in(), "SELECT netloc FROM recent_netlocs");
31✔
515
        if (netloc_prep_res.isErr()) {
31✔
516
            log_error("unable to get netlocs: %s",
2✔
517
                      netloc_prep_res.unwrapErr().c_str());
518
            return;
2✔
519
        }
520

521
        auto netloc_stmt = netloc_prep_res.unwrap();
29✔
522
        bool done = false;
29✔
523

524
        while (!done) {
58✔
525
            done = netloc_stmt.fetch_row<std::string>().match(
58✔
526
                [](const std::string& netloc) {
×
527
                    recent_refs.rr_netlocs.insert(netloc);
×
528
                    return false;
×
529
                },
530
                [](const prepared_stmt::fetch_error& fe) {
×
531
                    log_error("failed to fetch netloc row: %s",
×
532
                              fe.fe_msg.c_str());
533
                    return true;
×
534
                },
535
                [](prepared_stmt::end_of_rows) { return true; });
58✔
536
        }
537
    }
31✔
538

539
    log_info("BEGIN select bookmarks");
29✔
540
    if (sqlite3_prepare_v2(db.in(), BOOKMARK_STMT, -1, stmt.out(), nullptr)
29✔
541
        != SQLITE_OK)
29✔
542
    {
543
        log_error("could not prepare bookmark select statement -- %s",
×
544
                  sqlite3_errmsg(db));
545
        return;
×
546
    }
547

548
    for (auto file_iter = lnav_data.ld_log_source.begin();
29✔
549
         file_iter != lnav_data.ld_log_source.end();
58✔
550
         ++file_iter)
29✔
551
    {
552
        auto lf = (*file_iter)->get_file();
29✔
553
        if (lf == nullptr) {
29✔
554
            continue;
×
555
        }
556
        if (lf->size() == 0) {
29✔
557
            continue;
×
558
        }
559
        const auto* format = lf->get_format_ptr();
29✔
560
        content_line_t base_content_line;
29✔
561

562
        base_content_line = lss.get_file_base_content_line(file_iter);
29✔
563

564
        auto low_line_iter = lf->begin();
29✔
565
        auto high_line_iter = lf->end();
29✔
566

567
        --high_line_iter;
29✔
568

569
        if (bind_values(stmt.in(),
29✔
570
                        lnav_data.ld_session_load_time,
571
                        lf->original_line_time(low_line_iter),
572
                        lf->original_line_time(high_line_iter),
573
                        lf->get_format()->get_name())
58✔
574
            != SQLITE_OK)
29✔
575
        {
576
            return;
×
577
        }
578

579
        date_time_scanner dts;
29✔
580
        bool done = false;
29✔
581
        int64_t last_mark_time = -1;
29✔
582

583
        while (!done) {
102✔
584
            int rc = sqlite3_step(stmt.in());
73✔
585

586
            switch (rc) {
73✔
587
                case SQLITE_OK:
29✔
588
                case SQLITE_DONE:
589
                    done = true;
29✔
590
                    break;
29✔
591

592
                case SQLITE_ROW: {
44✔
593
                    const char* log_time
594
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
44✔
595
                    const char* log_hash
596
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
44✔
597
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
44✔
598
                    const char* part_name
599
                        = (const char*) sqlite3_column_text(stmt.in(), 4);
44✔
600
                    const char* comment
601
                        = (const char*) sqlite3_column_text(stmt.in(), 6);
44✔
602
                    const char* tags
603
                        = (const char*) sqlite3_column_text(stmt.in(), 7);
44✔
604
                    const auto annotations = sqlite3_column_text(stmt.in(), 8);
44✔
605
                    const auto log_opid = sqlite3_column_text(stmt.in(), 9);
44✔
606
                    int sticky = sqlite3_column_int(stmt.in(), 10);
44✔
607
                    timeval log_tv;
608
                    exttm log_tm;
44✔
609

610
                    if (last_mark_time == -1) {
44✔
611
                        last_mark_time = mark_time;
28✔
612
                    } else if (last_mark_time != mark_time) {
16✔
613
                        done = true;
×
614
                        continue;
24✔
615
                    }
616

617
                    if (part_name == nullptr && !sticky) {
44✔
618
                        continue;
24✔
619
                    }
620

621
                    if (dts.scan(log_time,
20✔
622
                                 strlen(log_time),
623
                                 nullptr,
624
                                 &log_tm,
625
                                 log_tv)
626
                        == nullptr)
20✔
627
                    {
628
                        log_warning("bad log time: %s", log_time);
×
629
                        continue;
×
630
                    }
631

632
                    auto line_iter = format->lf_time_ordered
20✔
633
                        ? std::lower_bound(lf->begin(), lf->end(), log_tv)
20✔
634
                        : lf->begin();
1✔
635
                    while (line_iter != lf->end()) {
75✔
636
                        const auto line_tv = line_iter->get_timeval();
75✔
637

638
                        // NB: only milliseconds were stored in the DB, but the
639
                        // internal rep stores micros now.
640
                        if (line_tv.tv_sec != log_tv.tv_sec
75✔
641
                            || line_tv.tv_usec / 1000 != log_tv.tv_usec / 1000)
40✔
642
                        {
643
                            if (format->lf_time_ordered) {
53✔
644
                                break;
20✔
645
                            }
646
                            ++line_iter;
53✔
647
                            continue;
55✔
648
                        }
649

650
                        auto cl = content_line_t(
651
                            std::distance(lf->begin(), line_iter));
44✔
652
                        auto fr = lf->get_file_range(line_iter, false);
22✔
653
                        auto read_result = lf->read_range(fr);
22✔
654

655
                        if (read_result.isErr()) {
22✔
656
                            break;
×
657
                        }
658

659
                        auto sbr = read_result.unwrap();
22✔
660

661
                        auto line_hash
662
                            = hasher()
22✔
663
                                  .update(sbr.get_data(), sbr.length())
22✔
664
                                  .update(cl)
22✔
665
                                  .to_string();
22✔
666

667
                        if (line_hash != log_hash) {
22✔
668
                            // Using the formatted line for JSON-lines logs was
669
                            // a mistake in earlier versions. To carry forward
670
                            // older bookmarks, we need to replicate the bad
671
                            // behavior.
672
                            auto hack_read_res
673
                                = lf->read_line(line_iter, {false, true});
2✔
674
                            if (hack_read_res.isErr()) {
2✔
675
                                break;
×
676
                            }
677
                            auto hack_sbr = hack_read_res.unwrap();
2✔
678
                            auto hack_hash = hasher()
2✔
679
                                                 .update(hack_sbr.get_data(),
2✔
680
                                                         hack_sbr.length())
681
                                                 .update(cl)
2✔
682
                                                 .to_string();
2✔
683
                            if (hack_hash == log_hash) {
2✔
684
                                log_trace("needed hack to match line: %s:%d",
×
685
                                          lf->get_filename_as_string().c_str(),
686
                                          (int) cl);
687
                            } else {
688
                                ++line_iter;
2✔
689
                                continue;
2✔
690
                            }
691
                        }
6✔
692
                        auto& bm_meta = lf->get_bookmark_metadata();
20✔
693
                        auto line_number = static_cast<uint32_t>(
694
                            std::distance(lf->begin(), line_iter));
20✔
695
                        content_line_t line_cl
696
                            = content_line_t(base_content_line + line_number);
20✔
697
                        bool meta = false;
20✔
698

699
                        if (part_name != nullptr && part_name[0] != '\0') {
20✔
700
                            lss.set_user_mark(&textview_curses::BM_PARTITION,
7✔
701
                                              line_cl);
702
                            bm_meta[line_number].bm_name = part_name;
7✔
703
                            if (!meta) {
7✔
704
                                log_debug("  loaded partition bookmark");
7✔
705
                            }
706
                            meta = true;
7✔
707
                        }
708
                        if (comment != nullptr && comment[0] != '\0') {
20✔
709
                            lss.set_user_mark(&textview_curses::BM_META,
7✔
710
                                              line_cl);
711
                            bm_meta[line_number].bm_comment = comment;
7✔
712
                            if (!meta) {
7✔
713
                                log_debug("  loaded message comment");
7✔
714
                            }
715
                            meta = true;
7✔
716
                        }
717
                        if (tags != nullptr && tags[0] != '\0') {
20✔
718
                            auto_mem<yajl_val_s> tag_list(yajl_tree_free);
10✔
719
                            char error_buffer[1024];
720

721
                            tag_list = yajl_tree_parse(
722
                                tags, error_buffer, sizeof(error_buffer));
10✔
723
                            if (!YAJL_IS_ARRAY(tag_list.in())) {
10✔
724
                                log_error("invalid tags column: %s", tags);
×
725
                            } else {
726
                                lss.set_user_mark(&textview_curses::BM_META,
10✔
727
                                                  line_cl);
728
                                for (size_t lpc = 0;
20✔
729
                                     lpc < tag_list.in()->u.array.len;
20✔
730
                                     lpc++)
731
                                {
732
                                    yajl_val elem
733
                                        = tag_list.in()->u.array.values[lpc];
10✔
734

735
                                    if (!YAJL_IS_STRING(elem)) {
10✔
736
                                        continue;
×
737
                                    }
738
                                    bookmark_metadata::KNOWN_TAGS.insert(
10✔
739
                                        elem->u.string);
10✔
740
                                    bm_meta[line_number].add_tag(
30✔
741
                                        elem->u.string);
10✔
742
                                }
743
                            }
744
                            if (!meta) {
10✔
745
                                log_debug("  loaded tags");
3✔
746
                            }
747
                            meta = true;
10✔
748
                        }
10✔
749
                        if (annotations != nullptr && annotations[0] != '\0') {
20✔
750
                            static const intern_string_t SRC
751
                                = intern_string::lookup("annotations");
3✔
752

753
                            const auto anno_sf
754
                                = string_fragment::from_c_str(annotations);
1✔
755
                            auto parse_res
756
                                = logmsg_annotations_handlers.parser_for(SRC)
2✔
757
                            .of(anno_sf);
1✔
758
                            if (!meta) {
1✔
759
                                log_debug("  loaded message annotation");
1✔
760
                            }
761
                            if (parse_res.isErr()) {
1✔
762
                                log_error(
×
763
                                    "unable to parse annotations JSON -- "
764
                                    "%s",
765
                                    parse_res.unwrapErr()[0]
766
                                        .to_attr_line()
767
                                        .get_string()
768
                                        .c_str());
769
                            } else if (bm_meta.find(line_number)
1✔
770
                                       == bm_meta.end())
2✔
771
                            {
772
                                lss.set_user_mark(&textview_curses::BM_META,
1✔
773
                                                  line_cl);
774
                                bm_meta[line_number].bm_annotations
1✔
775
                                    = parse_res.unwrap();
2✔
776
                                meta = true;
1✔
777
                            } else {
778
                                meta = true;
×
779
                            }
780
                        }
1✔
781
                        if (log_opid != nullptr && log_opid[0] != '\0') {
20✔
782
                            auto opid_sf
783
                                = string_fragment::from_c_str(log_opid);
×
784
                            lf->set_logline_opid(line_number, opid_sf);
×
NEW
785
                            if (!meta) {
×
NEW
786
                                log_debug("  loaded user opid");
×
787
                            }
UNCOV
788
                            meta = true;
×
789
                        }
790
                        if (meta) {
20✔
791
                            meta_loaded = true;
18✔
792
                        } else if (part_name != nullptr) {
2✔
793
                            marked_session_lines.emplace_back(
2✔
794
                                lf->original_line_time(line_iter),
2✔
795
                                format->get_name(),
2✔
796
                                line_hash);
797
                            lss.set_user_mark(&textview_curses::BM_USER,
2✔
798
                                              line_cl);
799
                        }
800
                        if (sticky) {
20✔
801
                            lss.set_user_mark(&textview_curses::BM_STICKY,
×
802
                                              line_cl);
803
                        }
804
                        reload_needed = true;
20✔
805
                        break;
20✔
806
                    }
66✔
807
                    break;
20✔
808
                }
809

810
                default: {
×
811
                    const char* errmsg;
812

813
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
814
                    log_error(
×
815
                        "bookmark select error: code %d -- %s", rc, errmsg);
816
                    done = true;
×
817
                } break;
×
818
            }
819
        }
820

821
        sqlite3_reset(stmt.in());
29✔
822
    }
29✔
823
    log_info("END select bookmarks");
29✔
824

825
    log_info("BEGIN select time_offset");
29✔
826
    if (sqlite3_prepare_v2(db.in(), TIME_OFFSET_STMT, -1, stmt.out(), nullptr)
29✔
827
        != SQLITE_OK)
29✔
828
    {
829
        log_error("could not prepare time_offset select statement -- %s",
×
830
                  sqlite3_errmsg(db));
831
        return;
×
832
    }
833

834
    for (auto file_iter = lnav_data.ld_log_source.begin();
29✔
835
         file_iter != lnav_data.ld_log_source.end();
58✔
836
         ++file_iter)
29✔
837
    {
838
        auto lf = (*file_iter)->get_file();
29✔
839
        content_line_t base_content_line;
29✔
840

841
        if (lf == nullptr) {
29✔
842
            continue;
×
843
        }
844
        if (lf->size() == 0) {
29✔
845
            continue;
×
846
        }
847

848
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
29✔
849

850
        auto low_line_iter = lf->begin();
29✔
851
        auto high_line_iter = lf->end();
29✔
852

853
        --high_line_iter;
29✔
854

855
        if (bind_values(stmt.in(),
58✔
856
                        lnav_data.ld_session_load_time,
857
                        lf->get_content_id(),
29✔
858
                        lf->original_line_time(low_line_iter),
859
                        lf->original_line_time(high_line_iter),
860
                        lf->get_format()->get_name())
58✔
861
            != SQLITE_OK)
29✔
862
        {
863
            return;
×
864
        }
865

866
        date_time_scanner dts;
29✔
867
        auto done = false;
29✔
868
        int64_t last_mark_time = -1;
29✔
869

870
        while (!done) {
86✔
871
            const auto rc = sqlite3_step(stmt.in());
57✔
872

873
            switch (rc) {
57✔
874
                case SQLITE_OK:
29✔
875
                case SQLITE_DONE:
876
                    done = true;
29✔
877
                    break;
29✔
878

879
                case SQLITE_ROW: {
28✔
880
                    const auto* log_time
881
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
28✔
882
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
28✔
883
                    timeval log_tv;
884
                    exttm log_tm;
28✔
885

886
                    if (last_mark_time == -1) {
28✔
887
                        last_mark_time = mark_time;
28✔
888
                    } else if (last_mark_time != mark_time) {
×
889
                        done = true;
×
890
                        continue;
25✔
891
                    }
892

893
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
28✔
894
                        continue;
25✔
895
                    }
896

897
                    if (!dts.scan(log_time,
3✔
898
                                  strlen(log_time),
899
                                  nullptr,
900
                                  &log_tm,
901
                                  log_tv))
902
                    {
903
                        continue;
×
904
                    }
905

906
                    auto line_iter
907
                        = lower_bound(lf->begin(), lf->end(), log_tv);
3✔
908
                    while (line_iter != lf->end()) {
6✔
909
                        auto line_tv = line_iter->get_timeval();
6✔
910

911
                        if ((line_tv.tv_sec != log_tv.tv_sec)
6✔
912
                            || (line_tv.tv_usec / 1000
3✔
913
                                != log_tv.tv_usec / 1000))
3✔
914
                        {
915
                            break;
916
                        }
917

918
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
919
                        timeval offset;
920

921
                        offset_session_lines.emplace_back(
3✔
922
                            lf->original_line_time(line_iter),
3✔
923
                            lf->get_format_ptr()->get_name(),
3✔
924
                            lf->get_content_id());
3✔
925
                        offset.tv_sec = sqlite3_column_int64(stmt.in(), 4);
3✔
926
                        offset.tv_usec = sqlite3_column_int64(stmt.in(), 5);
3✔
927
                        lf->adjust_content_time(file_line, offset);
3✔
928

929
                        reload_needed = true;
3✔
930

931
                        ++line_iter;
3✔
932
                    }
933
                    break;
3✔
934
                }
935

936
                default: {
×
937
                    const auto* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
938
                    log_error(
×
939
                        "bookmark select error: code %d -- %s", rc, errmsg);
940
                    done = true;
×
941
                    break;
×
942
                }
943
            }
944
        }
945

946
        sqlite3_reset(stmt.in());
29✔
947
    }
29✔
948
    log_info("END select time_offset");
29✔
949

950
    if (reload_needed) {
29✔
951
        if (meta_loaded) {
17✔
952
            lss.set_line_meta_changed();
17✔
953
            lss.text_filters_changed();
17✔
954
        }
955
        lnav_data.ld_views[LNV_LOG].reload_data();
17✔
956
    }
957

958
    load_text_bookmarks(db.in());
29✔
959
    load_timeline_bookmarks(db.in());
29✔
960
}
39✔
961

962
static int
963
read_files(yajlpp_parse_context* ypc,
27✔
964
           const unsigned char* str,
965
           size_t len,
966
           yajl_string_props_t*)
967
{
968
    return 1;
27✔
969
}
970

971
const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
972
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
973
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
974
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
975
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
976
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
977
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
978
    {level_names[LEVEL_INFO], LEVEL_INFO},
979
    {level_names[LEVEL_STATS], LEVEL_STATS},
980
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
981
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
982
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
983
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
984
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
985

986
    json_path_handler_base::ENUM_TERMINATOR,
987
};
988

989
static const json_path_container view_def_handlers = {
990
    json_path_handler("top_line").for_field(&view_state::vs_top),
991
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
992
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
993
    json_path_handler("search").for_field(&view_state::vs_search),
994
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
995
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
996
    json_path_handler("min_level")
997
        .with_enum_values(LEVEL_ENUM)
998
        .for_field(&view_state::vs_min_log_level),
999
    json_path_handler("commands#").for_field(&view_state::vs_commands),
1000
};
1001

1002
static const json_path_container view_handlers = {
1003
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
1004
        .with_obj_provider<view_state, session_data_t>(
1005
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
2,046✔
1006
                auto view_index_opt
1007
                    = view_from_string(ypc.get_substr("view_name").c_str());
2,046✔
1008
                if (view_index_opt) {
2,046✔
1009
                    return &root->sd_view_states[view_index_opt.value()];
2,046✔
1010
                }
1011

1012
                log_error("unknown view name: %s",
×
1013
                          ypc.get_substr("view_name").c_str());
1014
                static view_state dummy;
1015
                return &dummy;
×
1016
            })
1017
        .with_children(view_def_handlers),
1018
};
1019

1020
static const json_path_container file_state_handlers = {
1021
    yajlpp::property_handler("visible")
1022
        .with_description("Indicates whether the file is visible or not")
1023
        .for_field(&file_state::fs_is_visible),
1024
};
1025

1026
static const json_path_container file_states_handlers = {
1027
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
1028
        .with_description("Map of file names to file state objects")
1029
        .with_obj_provider<file_state, session_data_t>(
1030
            [](const auto& ypc, session_data_t* root) {
81✔
1031
                auto fn = ypc.get_substr("filename");
81✔
1032
                return &root->sd_file_states[fn];
162✔
1033
            })
81✔
1034
        .with_children(file_state_handlers),
1035
};
1036

1037
static const typed_json_path_container<session_data_t> view_info_handlers = {
1038
    yajlpp::property_handler("save-time")
1039
        .for_field(&session_data_t::sd_save_time),
1040
    yajlpp::property_handler("time-offset")
1041
        .for_field(&session_data_t::sd_time_offset),
1042
    json_path_handler("files#", read_files),
1043
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
1044
    yajlpp::property_handler("views").with_children(view_handlers),
1045
};
1046

1047
void
1048
load_session()
31✔
1049
{
1050
    static auto op = lnav_operation{"load_session"};
31✔
1051

1052
    auto op_guard = lnav_opid_guard::internal(op);
31✔
1053
    log_info("BEGIN load_session");
31✔
1054
    scan_sessions() | [](const auto pair) {
31✔
1055
        lnav_data.ld_session_load_time = pair.first.second;
28✔
1056
        const auto& view_info_path = pair.second;
28✔
1057
        auto view_info_src = intern_string::lookup(view_info_path.string());
28✔
1058

1059
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
28✔
1060
        if (open_res.isErr()) {
28✔
1061
            log_error("cannot open session file: %s -- %s",
×
1062
                      view_info_path.c_str(),
1063
                      open_res.unwrapErr().c_str());
1064
            return;
×
1065
        }
1066

1067
        auto fd = open_res.unwrap();
28✔
1068
        unsigned char buffer[1024];
1069
        ssize_t rc;
1070

1071
        log_info("loading session file: %s", view_info_path.c_str());
28✔
1072
        auto parser = view_info_handlers.parser_for(view_info_src);
28✔
1073
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
86✔
1074
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
29✔
1075
            auto parse_res = parser.consume(buf_frag);
29✔
1076
            if (parse_res.isErr()) {
29✔
1077
                log_error("failed to load session: %s -- %s",
×
1078
                          view_info_path.c_str(),
1079
                          parse_res.unwrapErr()[0]
1080
                              .to_attr_line()
1081
                              .get_string()
1082
                              .c_str());
1083
                return;
×
1084
            }
1085
        }
1086

1087
        auto complete_res = parser.complete();
28✔
1088
        if (complete_res.isErr()) {
28✔
1089
            log_error("failed to load session: %s -- %s",
×
1090
                      view_info_path.c_str(),
1091
                      complete_res.unwrapErr()[0]
1092
                          .to_attr_line()
1093
                          .get_string()
1094
                          .c_str());
1095
            return;
×
1096
        }
1097
        session_data = complete_res.unwrap();
28✔
1098

1099
        bool log_changes = false;
28✔
1100

1101
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
57✔
1102
            auto iter = session_data.sd_file_states.find(lf->get_filename());
29✔
1103

1104
            if (iter == session_data.sd_file_states.end()) {
29✔
1105
                continue;
2✔
1106
            }
1107

1108
            log_debug("found state for file: %s %d (%s)",
27✔
1109
                      lf->get_content_id().c_str(),
1110
                      iter->second.fs_is_visible,
1111
                      lf->get_filename_as_string().c_str());
1112
            lnav_data.ld_log_source.find_data(lf) |
27✔
1113
                [iter, &log_changes](auto ld) {
52✔
1114
                    if (ld->ld_visible != iter->second.fs_is_visible) {
26✔
1115
                        ld->get_file_ptr()->set_indexing(
1✔
1116
                            iter->second.fs_is_visible);
1✔
1117
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1118
                        log_changes = true;
1✔
1119
                    }
1120
                };
1121
        }
1122

1123
        if (log_changes) {
28✔
1124
            lnav_data.ld_log_source.text_filters_changed();
1✔
1125
        }
1126
    };
28✔
1127

1128
    lnav::events::publish(lnav_data.ld_db.in(),
31✔
1129
                          lnav::events::session::loaded{});
1130

1131
    log_info("END load_session");
31✔
1132
}
31✔
1133

1134
static void
1135
yajl_writer(void* context, const char* str, size_t len)
10,145✔
1136
{
1137
    FILE* file = (FILE*) context;
10,145✔
1138

1139
    fwrite(str, len, 1, file);
10,145✔
1140
}
10,145✔
1141

1142
static void
1143
save_user_bookmarks(sqlite3* db,
28✔
1144
                    sqlite3_stmt* stmt,
1145
                    bookmark_vector<content_line_t>& user_marks,
1146
                    bookmark_vector<content_line_t>& sticky_marks)
1147
{
1148
    auto& lss = lnav_data.ld_log_source;
28✔
1149

1150
    // Collect all lines that are either user-marked or sticky
1151
    tlx::btree_set<content_line_t> all_lines;
28✔
1152
    for (const auto& cl : user_marks.bv_tree) {
29✔
1153
        all_lines.insert(cl);
1✔
1154
    }
1155
    for (const auto& cl : sticky_marks.bv_tree) {
28✔
1156
        all_lines.insert(cl);
×
1157
    }
1158

1159
    for (const auto& cl_const : all_lines) {
29✔
1160
        auto cl = cl_const;
1✔
1161
        auto lf = lss.find(cl);
1✔
1162
        if (lf == nullptr) {
1✔
1163
            continue;
×
1164
        }
1165

1166
        sqlite3_clear_bindings(stmt);
1✔
1167

1168
        const auto line_iter = lf->begin() + cl;
1✔
1169
        auto fr = lf->get_file_range(line_iter, false);
1✔
1170
        auto read_result = lf->read_range(fr);
1✔
1171

1172
        if (read_result.isErr()) {
1✔
1173
            continue;
×
1174
        }
1175

1176
        auto line_hash = read_result
1177
                             .map([cl](auto sbr) {
2✔
1178
                                 return hasher()
2✔
1179
                                     .update(sbr.get_data(), sbr.length())
1✔
1180
                                     .update(cl)
1✔
1181
                                     .to_string();
2✔
1182
                             })
1183
                             .unwrap();
1✔
1184

1185
        if (bind_values(stmt,
3✔
1186
                        lf->original_line_time(line_iter),
1187
                        lf->get_format()->get_name(),
2✔
1188
                        line_hash,
1189
                        lnav_data.ld_session_time)
1190
            != SQLITE_OK)
1✔
1191
        {
1192
            continue;
×
1193
        }
1194

1195
        auto is_user = user_marks.bv_tree.find(cl) != user_marks.bv_tree.end();
1✔
1196
        auto is_sticky
1197
            = sticky_marks.bv_tree.find(cl) != sticky_marks.bv_tree.end();
1✔
1198

1199
        // Use part_name "" for user marks, null for sticky-only
1200
        if (is_user) {
1✔
1201
            if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT)
1✔
1202
                != SQLITE_OK)
1✔
1203
            {
1204
                log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1205
                return;
×
1206
            }
1207
        } else {
1208
            sqlite3_bind_null(stmt, 5);
×
1209
        }
1210

1211
        if (sqlite3_bind_int(stmt, 10, is_sticky ? 1 : 0) != SQLITE_OK) {
1✔
1212
            log_error("could not bind sticky -- %s", sqlite3_errmsg(db));
×
1213
            return;
×
1214
        }
1215

1216
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1217
            log_error("could not execute bookmark insert statement -- %s",
×
1218
                      sqlite3_errmsg(db));
1219
            return;
×
1220
        }
1221

1222
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1223
                                          lf->get_format_ptr()->get_name(),
1✔
1224
                                          line_hash);
1225

1226
        sqlite3_reset(stmt);
1✔
1227
    }
1✔
1228
}
28✔
1229

1230
static void
1231
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
28✔
1232
{
1233
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
36✔
1234
        auto cl = content_line_t(bm_pair.first);
8✔
1235
        sqlite3_clear_bindings(stmt);
8✔
1236

1237
        auto line_iter = lf->begin() + cl;
8✔
1238
        auto fr = lf->get_file_range(line_iter, false);
8✔
1239
        auto read_result = lf->read_range(fr);
8✔
1240

1241
        if (read_result.isErr()) {
8✔
1242
            continue;
×
1243
        }
1244

1245
        auto line_hash = read_result
1246
                             .map([cl](auto sbr) {
16✔
1247
                                 return hasher()
16✔
1248
                                     .update(sbr.get_data(), sbr.length())
8✔
1249
                                     .update(cl)
8✔
1250
                                     .to_string();
16✔
1251
                             })
1252
                             .unwrap();
8✔
1253

1254
        if (bind_values(stmt,
24✔
1255
                        lf->original_line_time(line_iter),
1256
                        lf->get_format()->get_name(),
16✔
1257
                        line_hash,
1258
                        lnav_data.ld_session_time)
1259
            != SQLITE_OK)
8✔
1260
        {
1261
            continue;
×
1262
        }
1263

1264
        const auto& line_meta = bm_pair.second;
8✔
1265
        if (line_meta.empty(bookmark_metadata::categories::any)) {
8✔
1266
            continue;
×
1267
        }
1268

1269
        if (sqlite3_bind_text(stmt,
8✔
1270
                              5,
1271
                              line_meta.bm_name.c_str(),
1272
                              line_meta.bm_name.length(),
8✔
1273
                              SQLITE_TRANSIENT)
1274
            != SQLITE_OK)
8✔
1275
        {
1276
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1277
            return;
×
1278
        }
1279

1280
        if (sqlite3_bind_text(stmt,
8✔
1281
                              6,
1282
                              line_meta.bm_comment.c_str(),
1283
                              line_meta.bm_comment.length(),
8✔
1284
                              SQLITE_TRANSIENT)
1285
            != SQLITE_OK)
8✔
1286
        {
1287
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1288
            return;
×
1289
        }
1290

1291
        std::string tags;
8✔
1292

1293
        if (!line_meta.bm_tags.empty()) {
8✔
1294
            yajlpp_gen gen;
5✔
1295

1296
            yajl_gen_config(gen, yajl_gen_beautify, false);
5✔
1297

1298
            {
1299
                yajlpp_array arr(gen);
5✔
1300

1301
                for (const auto& str : line_meta.bm_tags) {
10✔
1302
                    arr.gen(str);
5✔
1303
                }
1304
            }
5✔
1305

1306
            tags = gen.to_string_fragment().to_string();
5✔
1307
        }
5✔
1308

1309
        if (sqlite3_bind_text(
8✔
1310
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
8✔
1311
            != SQLITE_OK)
8✔
1312
        {
1313
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1314
            return;
×
1315
        }
1316

1317
        if (!line_meta.bm_annotations.la_pairs.empty()) {
8✔
1318
            auto anno_str = logmsg_annotations_handlers.to_string(
1319
                line_meta.bm_annotations);
1✔
1320

1321
            if (sqlite3_bind_text(stmt,
1✔
1322
                                  8,
1323
                                  anno_str.c_str(),
1324
                                  anno_str.length(),
1✔
1325
                                  SQLITE_TRANSIENT)
1326
                != SQLITE_OK)
1✔
1327
            {
1328
                log_error("could not bind annotations -- %s",
×
1329
                          sqlite3_errmsg(db));
1330
                return;
×
1331
            }
1332
        } else {
1✔
1333
            sqlite3_bind_null(stmt, 8);
7✔
1334
        }
1335

1336
        if (line_meta.bm_opid.empty()) {
8✔
1337
            sqlite3_bind_null(stmt, 9);
8✔
1338
        } else {
1339
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1340
        }
1341

1342
        sqlite3_bind_int(stmt, 10, 0);
8✔
1343

1344
        if (sqlite3_step(stmt) != SQLITE_DONE) {
8✔
1345
            log_error("could not execute bookmark insert statement -- %s",
×
1346
                      sqlite3_errmsg(db));
1347
            return;
×
1348
        }
1349

1350
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
8✔
1351
                                          lf->get_format_ptr()->get_name(),
8✔
1352
                                          line_hash);
1353

1354
        sqlite3_reset(stmt);
8✔
1355
    }
8✔
1356
}
1357

1358
static void
1359
save_text_bookmarks(sqlite3* db)
28✔
1360
{
1361
    auto& tss = lnav_data.ld_text_source;
28✔
1362

1363
    if (tss.empty()) {
28✔
1364
        return;
27✔
1365
    }
1366

1367
    tss.copy_bookmarks_to_current_file();
1✔
1368

1369
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1370

1371
    if (sqlite3_prepare_v2(db,
1✔
1372
                           "DELETE FROM text_bookmarks2 WHERE session_time = ?",
1373
                           -1,
1374
                           stmt.out(),
1375
                           nullptr)
1376
        != SQLITE_OK)
1✔
1377
    {
1378
        log_error("could not prepare text_bookmarks delete -- %s",
×
1379
                  sqlite3_errmsg(db));
1380
        return;
×
1381
    }
1382
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
1✔
1383
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1384
        log_error("could not execute text_bookmarks delete -- %s",
×
1385
                  sqlite3_errmsg(db));
1386
        return;
×
1387
    }
1388

1389
    if (sqlite3_prepare_v2(
1✔
1390
            db,
1391
            "REPLACE INTO text_bookmarks2"
1392
            " (file_path, line_number, line_hash, mark_type, session_time)"
1393
            " VALUES (?, ?, ?, ?, ?)",
1394
            -1,
1395
            stmt.out(),
1396
            nullptr)
1397
        != SQLITE_OK)
1✔
1398
    {
1399
        log_error("could not prepare text_bookmarks replace statement -- %s",
×
1400
                  sqlite3_errmsg(db));
1401
        return;
×
1402
    }
1403

1404
    for (const auto& fvs : tss.get_file_states()) {
2✔
1405
        auto& lf = fvs->fvs_file;
1✔
1406
        if (lf == nullptr || lf->size() == 0) {
1✔
1407
            continue;
×
1408
        }
1409

1410
        auto file_path = lf->get_path_for_key().string();
1✔
1411
        auto line_count = static_cast<int>(lf->size());
1✔
1412

1413
        static const bookmark_type_t* SAVE_TYPES[] = {
1414
            &textview_curses::BM_USER,
1415
            &textview_curses::BM_STICKY,
1416
        };
1417

1418
        for (const auto* bm_type : SAVE_TYPES) {
3✔
1419
            auto& bv = fvs->fvs_content_marks[bm_type];
2✔
1420
            if (bv.empty()) {
2✔
1421
                continue;
1✔
1422
            }
1423

1424
            for (const auto& vl : bv.bv_tree) {
2✔
1425
                if (static_cast<int>(vl) >= line_count) {
1✔
1426
                    continue;
×
1427
                }
1428

1429
                auto line_iter = lf->begin() + static_cast<int>(vl);
1✔
1430
                auto fr = lf->get_file_range(line_iter, false);
1✔
1431
                auto read_result = lf->read_range(fr);
1✔
1432

1433
                if (read_result.isErr()) {
1✔
1434
                    continue;
×
1435
                }
1436

1437
                auto line_hash
1438
                    = read_result
1439
                          .map([](auto sbr) {
2✔
1440
                              return hasher()
2✔
1441
                                  .update(sbr.get_data(), sbr.length())
1✔
1442
                                  .to_string();
2✔
1443
                          })
1444
                          .unwrap();
1✔
1445

1446
                sqlite3_clear_bindings(stmt.in());
1✔
1447
                bind_to_sqlite(stmt.in(), 1, file_path);
1✔
1448
                sqlite3_bind_int(stmt.in(), 2, static_cast<int>(vl));
1✔
1449
                bind_to_sqlite(stmt.in(), 3, line_hash);
1✔
1450
                bind_to_sqlite(stmt.in(), 4, bm_type->get_name());
1✔
1451
                sqlite3_bind_int64(stmt.in(), 5, lnav_data.ld_session_time);
1✔
1452

1453
                if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1454
                    log_error("could not execute text_bookmarks insert -- %s",
×
1455
                              sqlite3_errmsg(db));
1456
                    return;
×
1457
                }
1458

1459
                sqlite3_reset(stmt.in());
1✔
1460
            }
1✔
1461
        }
1462
    }
1✔
1463
}
1✔
1464

1465
static void
1466
load_text_bookmarks(sqlite3* db)
29✔
1467
{
1468
    static const char* const TEXT_BOOKMARK_STMT = R"(
1469
        SELECT line_number, line_hash, mark_type, session_time,
1470
               session_time=? AS same_session
1471
        FROM text_bookmarks2
1472
        WHERE file_path = ?
1473
        ORDER BY same_session DESC, session_time DESC
1474
    )";
1475

1476
    auto& tss = lnav_data.ld_text_source;
29✔
1477
    auto& tc = lnav_data.ld_views[LNV_TEXT];
29✔
1478

1479
    if (tss.empty()) {
29✔
1480
        return;
28✔
1481
    }
1482

1483
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1484
    if (sqlite3_prepare_v2(db, TEXT_BOOKMARK_STMT, -1, stmt.out(), nullptr)
1✔
1485
        != SQLITE_OK)
1✔
1486
    {
1487
        log_error("could not prepare text_bookmarks select -- %s",
×
1488
                  sqlite3_errmsg(db));
1489
        return;
×
1490
    }
1491

1492
    for (const auto& fvs : tss.get_file_states()) {
2✔
1493
        auto& lf = fvs->fvs_file;
1✔
1494
        if (lf == nullptr || lf->size() == 0) {
1✔
1495
            continue;
×
1496
        }
1497

1498
        auto file_path = lf->get_path_for_key().string();
1✔
1499
        auto line_count = static_cast<int>(lf->size());
1✔
1500
        sqlite3_reset(stmt.in());
1✔
1501
        sqlite3_clear_bindings(stmt.in());
1✔
1502
        sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
1✔
1503
        bind_to_sqlite(stmt.in(), 2, file_path);
1✔
1504

1505
        size_t mark_count = 0;
1✔
1506
        int64_t last_session_time = -1;
1✔
1507
        bool done = false;
1✔
1508
        while (!done) {
3✔
1509
            auto rc = sqlite3_step(stmt.in());
2✔
1510

1511
            switch (rc) {
2✔
1512
                case SQLITE_OK:
1✔
1513
                case SQLITE_DONE:
1514
                    done = true;
1✔
1515
                    break;
1✔
1516

1517
                case SQLITE_ROW: {
1✔
1518
                    auto line_number = sqlite3_column_int(stmt.in(), 0);
1✔
1519
                    const auto* stored_hash
1520
                        = (const char*) sqlite3_column_text(stmt.in(), 1);
1✔
1521
                    const auto* mark_type
1522
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
1✔
1523
                    auto session_time = sqlite3_column_int64(stmt.in(), 3);
1✔
1524

1525
                    if (last_session_time == -1) {
1✔
1526
                        last_session_time = session_time;
1✔
1527
                    } else if (last_session_time != session_time) {
×
1528
                        done = true;
×
1529
                        continue;
×
1530
                    }
1531

1532
                    if (line_number >= line_count) {
1✔
1533
                        continue;
×
1534
                    }
1535

1536
                    auto bm_type_opt = bookmark_type_t::find_type(mark_type);
1✔
1537
                    if (!bm_type_opt) {
1✔
1538
                        continue;
×
1539
                    }
1540

1541
                    auto line_iter = lf->begin() + line_number;
1✔
1542
                    auto fr = lf->get_file_range(line_iter, false);
1✔
1543
                    auto read_result = lf->read_range(fr);
1✔
1544

1545
                    if (read_result.isErr()) {
1✔
1546
                        continue;
×
1547
                    }
1548

1549
                    auto line_hash
1550
                        = read_result
1551
                              .map([](auto sbr) {
2✔
1552
                                  return hasher()
2✔
1553
                                      .update(sbr.get_data(), sbr.length())
1✔
1554
                                      .to_string();
2✔
1555
                              })
1556
                              .unwrap();
1✔
1557

1558
                    if (line_hash != stored_hash) {
1✔
1559
                        log_warning("text bookmark hash mismatch at %s:%d",
×
1560
                                    file_path.c_str(),
1561
                                    line_number);
1562
                        continue;
×
1563
                    }
1564

1565
                    auto vl = vis_line_t(line_number);
1✔
1566
                    fvs->fvs_bookmarks[bm_type_opt.value()].insert_once(vl);
1✔
1567
                    fvs->fvs_content_marks[bm_type_opt.value()].insert_once(
1✔
1568
                        static_cast<uint32_t>(line_number));
1569
                    mark_count += 1;
1✔
1570
                    break;
1✔
1571
                }
2✔
1572

1573
                default:
×
1574
                    log_error("text bookmark select error: %d -- %s",
×
1575
                              rc,
1576
                              sqlite3_errmsg(db));
1577
                    done = true;
×
1578
                    break;
×
1579
            }
1580
        }
1581
        log_debug(
1✔
1582
            "loaded %zu text bookmarks from %s", mark_count, file_path.c_str());
1583
    }
1✔
1584

1585
    // Copy the front file's bookmarks into the textview
1586
    if (!tss.get_file_states().empty()) {
1✔
1587
        auto& front = tss.get_file_states().front();
1✔
1588
        tc.get_bookmarks()[&textview_curses::BM_USER]
1✔
1589
            = front->fvs_bookmarks[&textview_curses::BM_USER];
2✔
1590
        tc.get_bookmarks()[&textview_curses::BM_STICKY]
1✔
1591
            = front->fvs_bookmarks[&textview_curses::BM_STICKY];
2✔
1592
    }
1593

1594
    tc.reload_data();
1✔
1595
}
1✔
1596

1597
static void
1598
save_timeline_bookmarks(sqlite3* db)
28✔
1599
{
1600
    auto& tc = lnav_data.ld_views[LNV_TIMELINE];
28✔
1601
    auto* tss = static_cast<timeline_source*>(tc.get_sub_source());
28✔
1602

1603
    if (tss == nullptr) {
28✔
1604
        return;
×
1605
    }
1606

1607
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
1608

1609
    if (sqlite3_prepare_v2(db,
28✔
1610
                           "DELETE FROM timeline_bookmarks"
1611
                           " WHERE session_time = ?",
1612
                           -1,
1613
                           stmt.out(),
1614
                           nullptr)
1615
        != SQLITE_OK)
28✔
1616
    {
1617
        log_error("could not prepare timeline_bookmarks delete -- %s",
×
1618
                  sqlite3_errmsg(db));
1619
        return;
×
1620
    }
1621
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
28✔
1622
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
1623
        log_error("could not execute timeline_bookmarks delete -- %s",
×
1624
                  sqlite3_errmsg(db));
1625
        return;
×
1626
    }
1627

1628
    if (sqlite3_prepare_v2(db,
28✔
1629
                           "REPLACE INTO timeline_bookmarks"
1630
                           " (row_type, row_name, mark_type, session_time)"
1631
                           " VALUES (?, ?, ?, ?)",
1632
                           -1,
1633
                           stmt.out(),
1634
                           nullptr)
1635
        != SQLITE_OK)
28✔
1636
    {
1637
        log_error(
×
1638
            "could not prepare timeline_bookmarks replace statement -- %s",
1639
            sqlite3_errmsg(db));
1640
        return;
×
1641
    }
1642

1643
    static const bookmark_type_t* SAVE_TYPES[] = {
1644
        &textview_curses::BM_USER,
1645
        &textview_curses::BM_STICKY,
1646
    };
1647

1648
    for (const auto* bm_type : SAVE_TYPES) {
84✔
1649
        const auto& bv = tc.get_bookmarks()[bm_type];
56✔
1650
        if (bv.empty()) {
56✔
1651
            continue;
54✔
1652
        }
1653

1654
        for (const auto& vl : bv.bv_tree) {
4✔
1655
            auto line = static_cast<size_t>(vl);
2✔
1656
            if (line >= tss->ts_time_order.size()) {
2✔
1657
                continue;
×
1658
            }
1659

1660
            const auto& row = *tss->ts_time_order[line];
2✔
1661

1662
            sqlite3_clear_bindings(stmt.in());
2✔
1663
            bind_to_sqlite(
2✔
1664
                stmt.in(), 1, timeline_source::row_type_to_string(row.or_type));
2✔
1665
            bind_to_sqlite(stmt.in(), 2, row.or_name.to_string());
2✔
1666
            bind_to_sqlite(stmt.in(), 3, bm_type->get_name());
2✔
1667
            sqlite3_bind_int64(stmt.in(), 4, lnav_data.ld_session_time);
2✔
1668

1669
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1670
                log_error("could not execute timeline_bookmarks insert -- %s",
×
1671
                          sqlite3_errmsg(db));
1672
                return;
×
1673
            }
1674

1675
            sqlite3_reset(stmt.in());
2✔
1676
        }
1677
    }
1678
}
28✔
1679

1680
static void
1681
load_timeline_bookmarks(sqlite3* db)
29✔
1682
{
1683
    static const char* const TIMELINE_BOOKMARK_STMT = R"(
1684
        SELECT row_type, row_name, mark_type, session_time,
1685
               session_time=? AS same_session
1686
        FROM timeline_bookmarks
1687
        ORDER BY same_session DESC, session_time DESC
1688
    )";
1689

1690
    auto* tss = static_cast<timeline_source*>(
1691
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
29✔
1692

1693
    if (tss == nullptr) {
29✔
1694
        return;
×
1695
    }
1696

1697
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
29✔
1698
    if (sqlite3_prepare_v2(db, TIMELINE_BOOKMARK_STMT, -1, stmt.out(), nullptr)
29✔
1699
        != SQLITE_OK)
29✔
1700
    {
1701
        log_debug("could not prepare timeline_bookmarks select -- %s",
×
1702
                  sqlite3_errmsg(db));
1703
        return;
×
1704
    }
1705

1706
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
29✔
1707

1708
    int64_t last_session_time = -1;
29✔
1709
    bool done = false;
29✔
1710
    while (!done) {
60✔
1711
        auto rc = sqlite3_step(stmt.in());
31✔
1712

1713
        switch (rc) {
31✔
1714
            case SQLITE_OK:
29✔
1715
            case SQLITE_DONE:
1716
                done = true;
29✔
1717
                break;
29✔
1718

1719
            case SQLITE_ROW: {
2✔
1720
                auto* row_type_str
1721
                    = (const char*) sqlite3_column_text(stmt.in(), 0);
2✔
1722
                auto* row_name
1723
                    = (const char*) sqlite3_column_text(stmt.in(), 1);
2✔
1724
                auto* mark_type
1725
                    = (const char*) sqlite3_column_text(stmt.in(), 2);
2✔
1726
                auto session_time = sqlite3_column_int64(stmt.in(), 3);
2✔
1727

1728
                if (last_session_time == -1) {
2✔
1729
                    last_session_time = session_time;
2✔
1730
                } else if (last_session_time != session_time) {
×
1731
                    done = true;
×
1732
                    continue;
×
1733
                }
1734

1735
                if (row_type_str == nullptr || row_name == nullptr
2✔
1736
                    || mark_type == nullptr)
2✔
1737
                {
1738
                    continue;
×
1739
                }
1740

1741
                auto bm_type_opt = bookmark_type_t::find_type(mark_type);
2✔
1742
                if (!bm_type_opt) {
2✔
1743
                    continue;
×
1744
                }
1745

1746
                auto rt_opt = timeline_source::row_type_from_string(
2✔
1747
                    row_type_str);
1748
                if (!rt_opt) {
2✔
1749
                    continue;
×
1750
                }
1751

1752
                tss->ts_pending_bookmarks.emplace_back(
4✔
1753
                    timeline_source::pending_bookmark{
4✔
1754
                        rt_opt.value(),
2✔
1755
                        row_name,
1756
                        bm_type_opt.value(),
2✔
1757
                    });
1758
                break;
2✔
1759
            }
1760

1761
            default:
×
1762
                log_error("timeline bookmark select error: %d -- %s",
×
1763
                          rc,
1764
                          sqlite3_errmsg(db));
1765
                done = true;
×
1766
                break;
×
1767
        }
1768
    }
1769
}
29✔
1770

1771
static void
1772
save_time_bookmarks()
28✔
1773
{
1774
    auto_sqlite3 db;
28✔
1775
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
28✔
1776
    auto_mem<char, sqlite3_free> errmsg;
28✔
1777
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
1778

1779
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
28✔
1780
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1781
        return;
×
1782
    }
1783

1784
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
28✔
1785
        != SQLITE_OK)
28✔
1786
    {
1787
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1788
        return;
×
1789
    }
1790

1791
    if (sqlite3_exec(
28✔
1792
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1793
        != SQLITE_OK)
28✔
1794
    {
1795
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1796
        return;
×
1797
    }
1798

1799
    {
1800
        static const char* UPDATE_NETLOCS_STMT
1801
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1802

1803
        std::set<std::string> netlocs;
28✔
1804

1805
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
28✔
1806
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
56✔
1807

1808
        if (sqlite3_prepare_v2(
28✔
1809
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1810
            != SQLITE_OK)
28✔
1811
        {
1812
            log_error("could not prepare recent_netlocs statement -- %s",
×
1813
                      sqlite3_errmsg(db));
1814
            return;
×
1815
        }
1816

1817
        for (const auto& netloc : netlocs) {
28✔
1818
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1819

1820
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1821
                log_error("could not execute bookmark insert statement -- %s",
×
1822
                          sqlite3_errmsg(db));
1823
                return;
×
1824
            }
1825

1826
            sqlite3_reset(stmt.in());
×
1827
        }
1828
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
28✔
1829
    }
28✔
1830

1831
    auto& lss = lnav_data.ld_log_source;
28✔
1832
    auto& bm = lss.get_user_bookmarks();
28✔
1833

1834
    if (sqlite3_prepare_v2(db.in(),
28✔
1835
                           "DELETE FROM bookmarks WHERE "
1836
                           " log_time = ? and log_format = ? and log_hash = ? "
1837
                           " and session_time = ?",
1838
                           -1,
1839
                           stmt.out(),
1840
                           nullptr)
1841
        != SQLITE_OK)
28✔
1842
    {
1843
        log_error("could not prepare bookmark delete statement -- %s",
×
1844
                  sqlite3_errmsg(db));
1845
        return;
×
1846
    }
1847

1848
    for (auto& marked_session_line : marked_session_lines) {
30✔
1849
        sqlite3_clear_bindings(stmt.in());
2✔
1850

1851
        if (bind_values(stmt,
4✔
1852
                        marked_session_line.sl_time,
1853
                        marked_session_line.sl_format_name,
1854
                        marked_session_line.sl_line_hash,
2✔
1855
                        lnav_data.ld_session_time)
1856
            != SQLITE_OK)
2✔
1857
        {
1858
            continue;
×
1859
        }
1860

1861
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1862
            log_error("could not execute bookmark insert statement -- %s",
×
1863
                      sqlite3_errmsg(db));
1864
            return;
×
1865
        }
1866

1867
        sqlite3_reset(stmt.in());
2✔
1868
    }
1869

1870
    marked_session_lines.clear();
28✔
1871

1872
    if (sqlite3_prepare_v2(db.in(),
28✔
1873
                           "REPLACE INTO bookmarks"
1874
                           " (log_time, log_format, log_hash, session_time, "
1875
                           "part_name, comment, tags, annotations, log_opid,"
1876
                           " sticky)"
1877
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1878
                           -1,
1879
                           stmt.out(),
1880
                           nullptr)
1881
        != SQLITE_OK)
28✔
1882
    {
1883
        log_error("could not prepare bookmark replace statement -- %s",
×
1884
                  sqlite3_errmsg(db));
1885
        return;
×
1886
    }
1887

1888
    {
1889
        for (auto file_iter = lnav_data.ld_log_source.begin();
28✔
1890
             file_iter != lnav_data.ld_log_source.end();
56✔
1891
             ++file_iter)
28✔
1892
        {
1893
            auto lf = (*file_iter)->get_file();
28✔
1894

1895
            if (lf == nullptr) {
28✔
1896
                continue;
×
1897
            }
1898
            if (lf->size() == 0) {
28✔
1899
                continue;
×
1900
            }
1901

1902
            content_line_t base_content_line;
28✔
1903
            base_content_line = lss.get_file_base_content_line(file_iter);
28✔
1904
            base_content_line
1905
                = content_line_t(base_content_line + lf->size() - 1);
28✔
1906

1907
            if (!bind_line(db.in(),
28✔
1908
                           stmt.in(),
1909
                           base_content_line,
1910
                           lnav_data.ld_session_time))
28✔
1911
            {
1912
                continue;
×
1913
            }
1914

1915
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
28✔
1916
                log_error("could not bind log hash -- %s",
×
1917
                          sqlite3_errmsg(db.in()));
1918
                return;
×
1919
            }
1920

1921
            sqlite3_bind_int(stmt.in(), 10, 0);
28✔
1922

1923
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
1924
                log_error("could not execute bookmark insert statement -- %s",
×
1925
                          sqlite3_errmsg(db));
1926
                return;
×
1927
            }
1928

1929
            sqlite3_reset(stmt.in());
28✔
1930
        }
28✔
1931
    }
1932

1933
    save_user_bookmarks(db.in(),
28✔
1934
                        stmt.in(),
1935
                        bm[&textview_curses::BM_USER],
1936
                        bm[&textview_curses::BM_STICKY]);
1937
    for (const auto& ldd : lss) {
56✔
1938
        auto* lf = ldd->get_file_ptr();
28✔
1939
        if (lf == nullptr) {
28✔
1940
            continue;
×
1941
        }
1942

1943
        save_meta_bookmarks(db.in(), stmt.in(), lf);
28✔
1944
    }
1945

1946
    if (sqlite3_prepare_v2(db.in(),
28✔
1947
                           "DELETE FROM time_offset WHERE "
1948
                           " log_time = ? and log_format = ? and log_hash = ? "
1949
                           " and session_time = ?",
1950
                           -1,
1951
                           stmt.out(),
1952
                           NULL)
1953
        != SQLITE_OK)
28✔
1954
    {
1955
        log_error("could not prepare time_offset delete statement -- %s",
×
1956
                  sqlite3_errmsg(db));
1957
        return;
×
1958
    }
1959

1960
    for (auto& offset_session_line : offset_session_lines) {
29✔
1961
        sqlite3_clear_bindings(stmt.in());
1✔
1962

1963
        if (bind_values(stmt,
2✔
1964
                        offset_session_line.sl_time,
1965
                        offset_session_line.sl_format_name,
1966
                        offset_session_line.sl_line_hash,
1✔
1967
                        lnav_data.ld_session_time)
1968
            != SQLITE_OK)
1✔
1969
        {
1970
            continue;
×
1971
        }
1972

1973
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1974
            log_error("could not execute bookmark insert statement -- %s",
×
1975
                      sqlite3_errmsg(db));
1976
            return;
×
1977
        }
1978

1979
        sqlite3_reset(stmt.in());
1✔
1980
    }
1981

1982
    offset_session_lines.clear();
28✔
1983

1984
    if (sqlite3_prepare_v2(db.in(),
28✔
1985
                           "REPLACE INTO time_offset"
1986
                           " (log_time, log_format, log_hash, session_time, "
1987
                           "offset_sec, offset_usec)"
1988
                           " VALUES (?, ?, ?, ?, ?, ?)",
1989
                           -1,
1990
                           stmt.out(),
1991
                           nullptr)
1992
        != SQLITE_OK)
28✔
1993
    {
1994
        log_error("could not prepare time_offset replace statement -- %s",
×
1995
                  sqlite3_errmsg(db));
1996
        return;
×
1997
    }
1998

1999
    {
2000
        for (auto file_iter = lnav_data.ld_log_source.begin();
28✔
2001
             file_iter != lnav_data.ld_log_source.end();
56✔
2002
             ++file_iter)
28✔
2003
        {
2004
            auto lf = (*file_iter)->get_file();
28✔
2005
            if (lf == nullptr) {
28✔
2006
                continue;
×
2007
            }
2008
            if (lf->size() == 0) {
28✔
2009
                continue;
×
2010
            }
2011

2012
            if (bind_values(stmt,
84✔
2013
                            lf->original_line_time(lf->begin()),
2014
                            lf->get_format()->get_name(),
56✔
2015
                            lf->get_content_id(),
28✔
2016
                            lnav_data.ld_session_time)
2017
                != SQLITE_OK)
28✔
2018
            {
2019
                continue;
×
2020
            }
2021

2022
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
28✔
2023
                log_error("could not bind log hash -- %s",
×
2024
                          sqlite3_errmsg(db.in()));
2025
                return;
×
2026
            }
2027

2028
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
28✔
2029
                log_error("could not bind log hash -- %s",
×
2030
                          sqlite3_errmsg(db.in()));
2031
                return;
×
2032
            }
2033

2034
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
2035
                log_error("could not execute bookmark insert statement -- %s",
×
2036
                          sqlite3_errmsg(db));
2037
                return;
×
2038
            }
2039

2040
            sqlite3_reset(stmt.in());
28✔
2041
        }
28✔
2042
    }
2043

2044
    for (const auto& ls : lss) {
56✔
2045
        if (ls->get_file() == nullptr) {
28✔
2046
            continue;
25✔
2047
        }
2048

2049
        const auto lf = ls->get_file();
28✔
2050
        if (!lf->is_time_adjusted()) {
28✔
2051
            continue;
25✔
2052
        }
2053

2054
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
2055
        auto offset = lf->get_time_offset();
3✔
2056

2057
        bind_values(stmt.in(),
6✔
2058
                    lf->original_line_time(line_iter),
2059
                    lf->get_format()->get_name(),
6✔
2060
                    lf->get_content_id(),
3✔
2061
                    lnav_data.ld_session_time,
2062
                    offset.tv_sec,
2063
                    offset.tv_usec);
2064

2065
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
2066
            log_error("could not execute bookmark insert statement -- %s",
×
2067
                      sqlite3_errmsg(db));
2068
            return;
×
2069
        }
2070

2071
        sqlite3_reset(stmt.in());
3✔
2072
    }
28✔
2073

2074
    log_info("saved %d log bookmarks", sqlite3_changes(db.in()));
28✔
2075
    save_text_bookmarks(db.in());
28✔
2076
    log_info("saved %d text bookmarks", sqlite3_changes(db.in()));
28✔
2077
    save_timeline_bookmarks(db.in());
28✔
2078
    log_info("saved %d timeline bookmarks", sqlite3_changes(db.in()));
28✔
2079

2080
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
28✔
2081
        != SQLITE_OK)
28✔
2082
    {
2083
        log_error("unable to begin transaction -- %s", errmsg.in());
×
2084
        return;
×
2085
    }
2086

2087
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
28✔
2088
        != SQLITE_OK)
28✔
2089
    {
2090
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
2091
        return;
×
2092
    }
2093
    auto bookmark_changes = sqlite3_changes(db.in());
28✔
2094
    if (bookmark_changes > 0) {
28✔
2095
        log_info("deleted %d old bookmarks", bookmark_changes);
×
2096
    }
2097

2098
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
28✔
2099
        != SQLITE_OK)
28✔
2100
    {
2101
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
2102
        return;
×
2103
    }
2104
    auto netloc_changes = sqlite3_changes(db.in());
28✔
2105
    if (netloc_changes > 0) {
28✔
2106
        log_info("deleted %d old netlocs", netloc_changes);
×
2107
    }
2108

2109
    if (sqlite3_exec(
28✔
2110
            db.in(), TEXT_BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
2111
        != SQLITE_OK)
28✔
2112
    {
2113
        log_error("unable to delete old text bookmarks -- %s", errmsg.in());
×
2114
        return;
×
2115
    }
2116
    auto text_bm_changes = sqlite3_changes(db.in());
28✔
2117
    if (text_bm_changes > 0) {
28✔
2118
        log_info("deleted %d old text bookmarks", text_bm_changes);
×
2119
    }
2120

2121
    if (sqlite3_exec(db.in(),
28✔
2122
                     TIMELINE_BOOKMARK_LRU_STMT,
2123
                     nullptr,
2124
                     nullptr,
2125
                     errmsg.out())
2126
        != SQLITE_OK)
28✔
2127
    {
2128
        log_error("unable to delete old timeline bookmarks -- %s",
×
2129
                  errmsg.in());
2130
        return;
×
2131
    }
2132
    auto timeline_bm_changes = sqlite3_changes(db.in());
28✔
2133
    if (timeline_bm_changes > 0) {
28✔
2134
        log_info("deleted %d old timeline bookmarks", timeline_bm_changes);
×
2135
    }
2136
}
28✔
2137

2138
static void
2139
save_session_with_id(const std::string& session_id)
27✔
2140
{
2141
    auto_mem<FILE> file(fclose);
27✔
2142
    yajl_gen handle = nullptr;
27✔
2143

2144
    /* TODO: save the last search query */
2145

2146
    log_info("saving session with id: %s", session_id.c_str());
27✔
2147

2148
    auto view_base_name
2149
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
54✔
2150
                      session_id,
2151
                      lnav_data.ld_session_time,
2152
                      getppid());
27✔
2153
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
27✔
2154
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
27✔
2155

2156
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
27✔
2157
        perror("Unable to open session file");
×
2158
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
27✔
2159
        perror("Unable to create yajl_gen object");
×
2160
    } else {
2161
        yajl_gen_config(
27✔
2162
            handle, yajl_gen_print_callback, yajl_writer, file.in());
2163

2164
        {
2165
            yajlpp_map root_map(handle);
27✔
2166

2167
            root_map.gen("save-time");
27✔
2168
            root_map.gen((long long) time(nullptr));
27✔
2169

2170
            root_map.gen("time-offset");
27✔
2171
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
27✔
2172

2173
            root_map.gen("files");
27✔
2174

2175
            {
2176
                yajlpp_array file_list(handle);
27✔
2177

2178
                for (auto& ld_file_name :
27✔
2179
                     lnav_data.ld_active_files.fc_file_names)
83✔
2180
                {
2181
                    file_list.gen(ld_file_name.first);
29✔
2182
                }
2183
            }
27✔
2184

2185
            root_map.gen("file-states");
27✔
2186

2187
            {
2188
                yajlpp_map file_states(handle);
27✔
2189

2190
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
56✔
2191
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
29✔
2192

2193
                    file_states.gen(lf->get_filename().native());
29✔
2194

2195
                    {
2196
                        yajlpp_map file_state(handle);
29✔
2197

2198
                        file_state.gen("visible");
29✔
2199
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
29✔
2200
                    }
29✔
2201
                }
2202
            }
27✔
2203

2204
            root_map.gen("views");
27✔
2205

2206
            {
2207
                yajlpp_map top_view_map(handle);
27✔
2208

2209
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
270✔
2210
                    auto& tc = lnav_data.ld_views[lpc];
243✔
2211
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
243✔
2212

2213
                    top_view_map.gen(lnav_view_strings[lpc]);
243✔
2214

2215
                    yajlpp_map view_map(handle);
243✔
2216

2217
                    view_map.gen("top_line");
243✔
2218

2219
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
243✔
2220
                        view_map.gen(-1LL);
228✔
2221
                    } else {
2222
                        view_map.gen((long long) tc.get_top());
15✔
2223
                    }
2224

2225
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
302✔
2226
                        && tc.get_inner_height() > 0_vl
4✔
2227
                        && tc.get_selection() != tc.get_inner_height() - 1)
302✔
2228
                    {
2229
                        auto sel = tc.get_selection();
3✔
2230
                        if (sel) {
3✔
2231
                            view_map.gen("focused_line");
3✔
2232
                            view_map.gen((long long) sel.value());
3✔
2233

2234
                            if (ta != nullptr) {
3✔
2235
                                auto anchor_opt
2236
                                    = ta->anchor_for_row(sel.value());
1✔
2237
                                if (anchor_opt) {
1✔
2238
                                    view_map.gen("anchor");
1✔
2239
                                    view_map.gen(anchor_opt.value());
1✔
2240
                                }
2241
                            }
1✔
2242
                        }
2243
                    }
2244

2245
                    view_map.gen("search");
243✔
2246
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
243✔
2247

2248
                    view_map.gen("word_wrap");
243✔
2249
                    view_map.gen(tc.get_word_wrap());
243✔
2250

2251
                    auto* tss = tc.get_sub_source();
243✔
2252
                    if (tss == nullptr) {
243✔
2253
                        continue;
54✔
2254
                    }
2255

2256
                    view_map.gen("filtering");
189✔
2257
                    view_map.gen(tss->tss_apply_filters);
189✔
2258

2259
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
189✔
2260
                        view_map.gen("min_level");
×
2261
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
2262
                    }
2263

2264
                    view_map.gen("commands");
189✔
2265
                    yajlpp_array cmd_array(handle);
189✔
2266

2267
                    tss->add_commands_for_session(
189✔
2268
                        [&](auto& cmd) { cmd_array.gen(cmd); });
202✔
2269
                }
243✔
2270
            }
27✔
2271
        }
27✔
2272

2273
        yajl_gen_clear(handle);
27✔
2274
        yajl_gen_free(handle);
27✔
2275

2276
        fclose(file.release());
27✔
2277

2278
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
27✔
2279

2280
        log_info("Saved session: %s", view_file_name.c_str());
27✔
2281
    }
2282
}
27✔
2283

2284
void
2285
save_session()
28✔
2286
{
2287
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
28✔
2288
        log_info("secure mode is enabled, not saving session");
×
2289
        return;
×
2290
    }
2291

2292
    static auto op = lnav_operation{"save_session"};
28✔
2293

2294
    auto op_guard = lnav_opid_guard::internal(op);
28✔
2295

2296
    log_debug("BEGIN save_session");
28✔
2297
    save_time_bookmarks();
28✔
2298

2299
    const auto opt_session_id = compute_session_id();
28✔
2300
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
54✔
2301
    for (const auto& pair : lnav_data.ld_session_id) {
32✔
2302
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
2303
            continue;
3✔
2304
        }
2305
        save_session_with_id(pair.first);
1✔
2306
    }
2307
    log_debug("END save_session");
28✔
2308
}
28✔
2309

2310
void
2311
reset_session()
6✔
2312
{
2313
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
2314

2315
    save_session();
6✔
2316

2317
    lnav_data.ld_session_time = time(nullptr);
6✔
2318
    session_data.sd_file_states.clear();
6✔
2319

2320
    for (auto& tc : lnav_data.ld_views) {
60✔
2321
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
2322
        auto& hmap = tc.get_highlights();
54✔
2323
        auto hl_iter = hmap.begin();
54✔
2324

2325
        while (hl_iter != hmap.end()) {
552✔
2326
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
2327
                ++hl_iter;
498✔
2328
            } else {
2329
                hmap.erase(hl_iter++);
×
2330
            }
2331
        }
2332

2333
        if (ttt != nullptr) {
54✔
2334
            ttt->clear_min_max_row_times();
36✔
2335
        }
2336
    }
2337

2338
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
2339
        lf->reset_state();
8✔
2340
    }
2341

2342
    lnav_data.ld_log_source.get_breakpoints().clear();
6✔
2343
    lnav_data.ld_log_source.lss_highlighters.clear();
6✔
2344
    lnav_data.ld_log_source.set_force_rebuild();
6✔
2345
    lnav_data.ld_log_source.set_marked_only(false);
6✔
2346
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
2347
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
2348
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
2349
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
2350
    rebuild_indexes(std::nullopt);
6✔
2351

2352
    lnav_data.ld_db_row_source.reset_user_state();
6✔
2353

2354
    auto* tss = static_cast<timeline_source*>(
2355
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
6✔
2356
    if (tss != nullptr) {
6✔
2357
        tss->ts_hidden_row_types.clear();
6✔
2358
        tss->ts_pending_bookmarks.clear();
6✔
2359
    }
2360

2361
    for (auto& tc : lnav_data.ld_views) {
60✔
2362
        text_sub_source* tss = tc.get_sub_source();
54✔
2363

2364
        if (tss == nullptr) {
54✔
2365
            continue;
12✔
2366
        }
2367
        tss->get_filters().clear_filters();
42✔
2368
        tss->tss_apply_filters = true;
42✔
2369
        tss->text_filters_changed();
42✔
2370
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
2371
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
2372
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
2373
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
2374
        tc.get_bookmarks()[&textview_curses::BM_STICKY].clear();
42✔
2375
        tc.reload_data();
42✔
2376
    }
2377

2378
    for (auto& fvs : lnav_data.ld_text_source.get_file_states()) {
6✔
2379
        fvs->fvs_bookmarks.clear();
×
2380
    }
2381

2382
    lnav_data.ld_filter_view.reload_data();
6✔
2383
    lnav_data.ld_files_view.reload_data();
6✔
2384
    for (const auto& format : log_format::get_root_formats()) {
468✔
2385
        auto* elf = dynamic_cast<external_log_format*>(format.get());
462✔
2386

2387
        if (elf == nullptr) {
462✔
2388
            continue;
30✔
2389
        }
2390

2391
        bool changed = false;
432✔
2392
        for (const auto& vd : elf->elf_value_defs) {
6,306✔
2393
            if (vd.second->vd_meta.lvm_user_hidden) {
5,874✔
2394
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
2395
                changed = true;
×
2396
            }
2397
        }
2398
        if (changed) {
432✔
2399
            elf->elf_value_defs_state->vds_generation += 1;
×
2400
        }
2401
    }
2402
}
6✔
2403

2404
void
2405
lnav::session::apply_view_commands()
30✔
2406
{
2407
    static auto op = lnav_operation{__FUNCTION__};
30✔
2408

2409
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2410

2411
    log_debug("applying view commands");
30✔
2412
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
300✔
2413
        const auto& vs = session_data.sd_view_states[view_index];
270✔
2414
        auto& tview = lnav_data.ld_views[view_index];
270✔
2415

2416
        lnav::set::small<std::string> curr_cmds;
270✔
2417
        auto* tss = tview.get_sub_source();
270✔
2418
        if (tview.get_sub_source() != nullptr) {
270✔
2419
            tss->tss_apply_filters = vs.vs_filtering;
210✔
2420
            if (vs.vs_min_log_level) {
210✔
2421
                tss->set_min_log_level(vs.vs_min_log_level.value());
×
2422
            }
2423
            tss->add_commands_for_session([&](auto& cmd) {
210✔
2424
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
2425
                                  .split_when(string_fragment::tag1{' '})
5✔
2426
                                  .first;
2427
                curr_cmds.insert(cmd_sf.to_string());
5✔
2428
            });
5✔
2429
        }
2430
        for (const auto& cmdline : vs.vs_commands) {
279✔
2431
            auto cmdline_sf = string_fragment::from_str(cmdline);
9✔
2432
            auto active = ensure_view(&tview);
9✔
2433
            auto [cmd_sf, _cmdline_rem]
9✔
2434
                = cmdline_sf.split_when(string_fragment::tag1{' '});
9✔
2435
            if (curr_cmds.contains(cmd_sf.to_string())) {
9✔
2436
                log_debug("view %s command '%.*s' already active",
3✔
2437
                          tview.get_title().c_str(),
2438
                          cmd_sf.length(),
2439
                          cmd_sf.data());
2440
                continue;
3✔
2441
            }
2442
            auto exec_cmd_res
2443
                = execute_command(lnav_data.ld_exec_context, cmdline);
6✔
2444
            if (exec_cmd_res.isOk()) {
6✔
2445
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
6✔
2446
            } else {
2447
                log_error("Result: %s",
×
2448
                          exec_cmd_res.unwrapErr()
2449
                              .to_attr_line()
2450
                              .get_string()
2451
                              .c_str());
2452
            }
2453
            if (!active) {
6✔
2454
                lnav_data.ld_view_stack.pop_back();
×
2455
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
2456
                    // XXX
2457
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
2458
                        auto tss = tc->get_sub_source();
×
2459
                        tss->text_filters_changed();
×
2460
                        tc->reload_data();
×
2461
                    }
2462
                };
2463
            }
2464
        }
6✔
2465
    }
270✔
2466
}
30✔
2467

2468
void
2469
lnav::session::restore_view_states()
30✔
2470
{
2471
    static auto op = lnav_operation{__FUNCTION__};
30✔
2472

2473
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2474

2475
    log_debug("restoring view states");
30✔
2476
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
300✔
2477
        const auto& vs = session_data.sd_view_states[view_index];
270✔
2478
        auto& tview = lnav_data.ld_views[view_index];
270✔
2479
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
270✔
2480

2481
        if (!vs.vs_search.empty()) {
270✔
2482
            tview.execute_search(vs.vs_search);
×
2483
            tview.set_follow_search_for(-1, {});
×
2484
        }
2485
        tview.set_word_wrap(vs.vs_word_wrap);
270✔
2486
        auto has_loc = tview.get_selection().has_value();
270✔
2487
        if (!has_loc && vs.vs_top >= 0
69✔
2488
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
339✔
2489
                || tview.get_top() == tview.get_top_for_last_row()))
270✔
2490
        {
2491
            log_info("restoring %s view top: %d",
18✔
2492
                     lnav_view_strings[view_index].data(),
2493
                     (int) vs.vs_top);
2494
            tview.set_top(vis_line_t(vs.vs_top), true);
18✔
2495
        }
2496
        if (!has_loc && vs.vs_selection) {
270✔
2497
            log_info("restoring %s view selection: %d",
2✔
2498
                     lnav_view_strings[view_index].data(),
2499
                     (int) vs.vs_selection.value_or(-1_vl));
2500
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
2✔
2501
        }
2502
        auto sel = tview.get_selection();
270✔
2503
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
69✔
2504
            && !vs.vs_anchor->empty())
339✔
2505
        {
2506
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
2507

2508
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
2509
                log_info("%s view anchor mismatch %s != %s",
×
2510
                         lnav_view_strings[view_index].data(),
2511
                         curr_anchor.value_or("").c_str(),
2512
                         vs.vs_anchor.value().c_str());
2513

2514
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
2515
                if (row_opt) {
×
2516
                    tview.set_selection(row_opt.value());
×
2517
                }
2518
            }
2519
        }
2520
        sel = tview.get_selection();
270✔
2521
        if (!sel) {
270✔
2522
            auto height = tview.get_inner_height();
69✔
2523
            if (height == 0) {
69✔
2524
            } else if (view_index == LNV_TEXT) {
1✔
2525
                auto lf = lnav_data.ld_text_source.current_file();
×
2526
                if (lf != nullptr) {
×
2527
                    switch (lf->get_text_format().value_or(
×
2528
                        text_format_t::TF_BINARY))
×
2529
                    {
2530
                        case text_format_t::TF_PLAINTEXT:
×
2531
                        case text_format_t::TF_LOG: {
2532
                            if (height > 0_vl) {
×
2533
                                tview.set_selection(height - 1_vl);
×
2534
                            }
2535
                            break;
×
2536
                        }
2537
                        default:
×
2538
                            tview.set_selection(0_vl);
×
2539
                            break;
×
2540
                    }
2541
                }
2542
            } else if (view_index == LNV_LOG) {
1✔
2543
                tview.set_selection(height - 1_vl);
×
2544
            } else {
2545
                tview.set_selection(0_vl);
1✔
2546
            }
2547
        }
2548
        log_info("%s view actual top/selection: %d/%d",
270✔
2549
                 lnav_view_strings[view_index].data(),
2550
                 (int) tview.get_top(),
2551
                 (int) tview.get_selection().value_or(-1_vl));
2552
    }
2553
}
30✔
2554

2555
void
2556
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2557
{
2558
    constexpr const char* STMT = R"(
×
2559
       INSERT INTO regex101_entries
2560
          (format_name, regex_name, permalink, delete_code)
2561
          VALUES (?, ?, ?, ?);
2562
)";
2563

2564
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2565
    auto_sqlite3 db;
×
2566

2567
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2568
        return;
×
2569
    }
2570

2571
    auto_mem<char, sqlite3_free> errmsg;
×
2572
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2573
        != SQLITE_OK)
×
2574
    {
2575
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2576
        return;
×
2577
    }
2578

2579
    auto prep_res = prepare_stmt(db.in(),
2580
                                 STMT,
2581
                                 ei.re_format_name,
×
2582
                                 ei.re_regex_name,
×
2583
                                 ei.re_permalink,
×
2584
                                 ei.re_delete_code);
×
2585

2586
    if (prep_res.isErr()) {
×
2587
        return;
×
2588
    }
2589

2590
    auto ps = prep_res.unwrap();
×
2591

2592
    ps.execute();
×
2593
}
2594

2595
template<>
2596
struct from_sqlite<lnav::session::regex101::entry> {
2597
    inline lnav::session::regex101::entry operator()(int argc,
2598
                                                     sqlite3_value** argv,
2599
                                                     int argi)
2600
    {
2601
        return {
2602
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
2603
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2604
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2605
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2606
        };
2607
    }
2608
};
2609

2610
Result<std::vector<lnav::session::regex101::entry>, std::string>
2611
lnav::session::regex101::get_entries()
×
2612
{
2613
    constexpr const char* STMT = R"(
×
2614
       SELECT * FROM regex101_entries;
2615
)";
2616

2617
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2618
    auto_sqlite3 db;
×
2619

2620
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2621
        return Err(std::string());
×
2622
    }
2623

2624
    auto_mem<char, sqlite3_free> errmsg;
×
2625
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2626
        != SQLITE_OK)
×
2627
    {
2628
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2629
        return Err(std::string(errmsg));
×
2630
    }
2631

2632
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2633
    bool done = false;
×
2634
    std::vector<entry> retval;
×
2635

2636
    while (!done) {
×
2637
        auto fetch_res = ps.fetch_row<entry>();
×
2638

2639
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2640
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2641
        }
2642

2643
        fetch_res.match(
×
2644
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2645
            [](const prepared_stmt::fetch_error&) {},
×
2646
            [&retval](entry en) { retval.emplace_back(en); });
×
2647
    }
2648
    return Ok(retval);
×
2649
}
2650

2651
void
2652
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2653
                                      const std::string& regex_name)
2654
{
2655
    constexpr const char* STMT = R"(
×
2656
       DELETE FROM regex101_entries WHERE
2657
          format_name = ? AND regex_name = ?;
2658
)";
2659

2660
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2661
    auto_sqlite3 db;
×
2662

2663
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2664
        return;
×
2665
    }
2666

2667
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2668

2669
    if (prep_res.isErr()) {
×
2670
        return;
×
2671
    }
2672

2673
    auto ps = prep_res.unwrap();
×
2674

2675
    ps.execute();
×
2676
}
2677

2678
lnav::session::regex101::get_result_t
2679
lnav::session::regex101::get_entry(const std::string& format_name,
×
2680
                                   const std::string& regex_name)
2681
{
2682
    constexpr const char* STMT = R"(
×
2683
       SELECT * FROM regex101_entries WHERE
2684
          format_name = ? AND regex_name = ?;
2685
    )";
2686

2687
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2688
    auto_sqlite3 db;
×
2689

2690
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2691
        return error{std::string()};
×
2692
    }
2693

2694
    auto_mem<char, sqlite3_free> errmsg;
×
2695
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2696
        != SQLITE_OK)
×
2697
    {
2698
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2699
        return error{std::string(errmsg)};
×
2700
    }
2701

2702
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2703
    if (prep_res.isErr()) {
×
2704
        return error{prep_res.unwrapErr()};
×
2705
    }
2706

2707
    auto ps = prep_res.unwrap();
×
2708
    return ps.fetch_row<entry>().match(
×
2709
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2710
            return error{fe.fe_msg};
×
2711
        },
2712
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2713
            return no_entry{};
×
2714
        },
2715
        [](const entry& en) -> get_result_t { return en; });
×
2716
}
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