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

tstack / lnav / 23836075406-2909

01 Apr 2026 06:51AM UTC coverage: 69.084% (+0.007%) from 69.077%
23836075406-2909

push

github

tstack
[tags] only save user-provided tags in the session, not format-provided

60 of 76 new or added lines in 14 files covered. (78.95%)

2 existing lines in 2 files now uncovered.

53287 of 77134 relevant lines covered (69.08%)

535481.86 hits per line

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

74.66
/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,
13✔
180
                 intern_string_t format_name,
181
                 std::string line_hash)
182
        : sl_time(tv), sl_format_name(format_name),
13✔
183
          sl_line_hash(std::move(line_hash))
13✔
184
    {
185
    }
13✔
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);
×
785
                            if (!meta) {
×
786
                                log_debug("  loaded user opid");
×
787
                            }
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::session)) {
8✔
1266
            continue;
1✔
1267
        }
1268

1269
        if (line_meta.bm_name_source
7✔
1270
            == bookmark_metadata::meta_source::user)
1271
        {
1272
            if (sqlite3_bind_text(stmt,
7✔
1273
                                  5,
1274
                                  line_meta.bm_name.c_str(),
1275
                                  line_meta.bm_name.length(),
7✔
1276
                                  SQLITE_TRANSIENT)
1277
                != SQLITE_OK)
7✔
1278
            {
NEW
1279
                log_error("could not bind part name -- %s",
×
1280
                          sqlite3_errmsg(db));
NEW
1281
                return;
×
1282
            }
1283
        }
1284

1285
        if (sqlite3_bind_text(stmt,
7✔
1286
                              6,
1287
                              line_meta.bm_comment.c_str(),
1288
                              line_meta.bm_comment.length(),
7✔
1289
                              SQLITE_TRANSIENT)
1290
            != SQLITE_OK)
7✔
1291
        {
1292
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1293
            return;
×
1294
        }
1295

1296
        std::string tags;
7✔
1297

1298
        {
1299
            yajlpp_gen gen;
7✔
1300

1301
            yajl_gen_config(gen, yajl_gen_beautify, false);
7✔
1302

1303
            {
1304
                yajlpp_array arr(gen);
7✔
1305

1306
                for (const auto& entry : line_meta.bm_tags) {
11✔
1307
                    if (entry.te_source
4✔
1308
                        == bookmark_metadata::meta_source::user)
1309
                    {
1310
                        arr.gen(entry.te_tag);
4✔
1311
                    }
1312
                }
1313
            }
7✔
1314

1315
            tags = gen.to_string_fragment().to_string();
7✔
1316

1317
            if (tags == "[]") {
7✔
1318
                tags.clear();
3✔
1319
            }
1320
        }
7✔
1321

1322
        if (sqlite3_bind_text(
7✔
1323
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
7✔
1324
            != SQLITE_OK)
7✔
1325
        {
1326
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1327
            return;
×
1328
        }
1329

1330
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1331
            auto anno_str = logmsg_annotations_handlers.to_string(
1332
                line_meta.bm_annotations);
1✔
1333

1334
            if (sqlite3_bind_text(stmt,
1✔
1335
                                  8,
1336
                                  anno_str.c_str(),
1337
                                  anno_str.length(),
1✔
1338
                                  SQLITE_TRANSIENT)
1339
                != SQLITE_OK)
1✔
1340
            {
1341
                log_error("could not bind annotations -- %s",
×
1342
                          sqlite3_errmsg(db));
1343
                return;
×
1344
            }
1345
        } else {
1✔
1346
            sqlite3_bind_null(stmt, 8);
6✔
1347
        }
1348

1349
        if (line_meta.bm_opid.empty()) {
7✔
1350
            sqlite3_bind_null(stmt, 9);
7✔
1351
        } else {
1352
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1353
        }
1354

1355
        sqlite3_bind_int(stmt, 10, 0);
7✔
1356

1357
        if (sqlite3_step(stmt) != SQLITE_DONE) {
7✔
1358
            log_error("could not execute bookmark insert statement -- %s",
×
1359
                      sqlite3_errmsg(db));
1360
            return;
×
1361
        }
1362

1363
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1364
                                          lf->get_format_ptr()->get_name(),
7✔
1365
                                          line_hash);
1366

1367
        sqlite3_reset(stmt);
7✔
1368
    }
9✔
1369
}
1370

1371
static void
1372
save_text_bookmarks(sqlite3* db)
28✔
1373
{
1374
    auto& tss = lnav_data.ld_text_source;
28✔
1375

1376
    if (tss.empty()) {
28✔
1377
        return;
27✔
1378
    }
1379

1380
    tss.copy_bookmarks_to_current_file();
1✔
1381

1382
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1383

1384
    if (sqlite3_prepare_v2(db,
1✔
1385
                           "DELETE FROM text_bookmarks2 WHERE session_time = ?",
1386
                           -1,
1387
                           stmt.out(),
1388
                           nullptr)
1389
        != SQLITE_OK)
1✔
1390
    {
1391
        log_error("could not prepare text_bookmarks delete -- %s",
×
1392
                  sqlite3_errmsg(db));
1393
        return;
×
1394
    }
1395
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
1✔
1396
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1397
        log_error("could not execute text_bookmarks delete -- %s",
×
1398
                  sqlite3_errmsg(db));
1399
        return;
×
1400
    }
1401

1402
    if (sqlite3_prepare_v2(
1✔
1403
            db,
1404
            "REPLACE INTO text_bookmarks2"
1405
            " (file_path, line_number, line_hash, mark_type, session_time)"
1406
            " VALUES (?, ?, ?, ?, ?)",
1407
            -1,
1408
            stmt.out(),
1409
            nullptr)
1410
        != SQLITE_OK)
1✔
1411
    {
1412
        log_error("could not prepare text_bookmarks replace statement -- %s",
×
1413
                  sqlite3_errmsg(db));
1414
        return;
×
1415
    }
1416

1417
    for (const auto& fvs : tss.get_file_states()) {
2✔
1418
        auto& lf = fvs->fvs_file;
1✔
1419
        if (lf == nullptr || lf->size() == 0) {
1✔
1420
            continue;
×
1421
        }
1422

1423
        auto file_path = lf->get_path_for_key().string();
1✔
1424
        auto line_count = static_cast<int>(lf->size());
1✔
1425

1426
        static const bookmark_type_t* SAVE_TYPES[] = {
1427
            &textview_curses::BM_USER,
1428
            &textview_curses::BM_STICKY,
1429
        };
1430

1431
        for (const auto* bm_type : SAVE_TYPES) {
3✔
1432
            auto& bv = fvs->fvs_content_marks[bm_type];
2✔
1433
            if (bv.empty()) {
2✔
1434
                continue;
1✔
1435
            }
1436

1437
            for (const auto& vl : bv.bv_tree) {
2✔
1438
                if (static_cast<int>(vl) >= line_count) {
1✔
1439
                    continue;
×
1440
                }
1441

1442
                auto line_iter = lf->begin() + static_cast<int>(vl);
1✔
1443
                auto fr = lf->get_file_range(line_iter, false);
1✔
1444
                auto read_result = lf->read_range(fr);
1✔
1445

1446
                if (read_result.isErr()) {
1✔
1447
                    continue;
×
1448
                }
1449

1450
                auto line_hash
1451
                    = read_result
1452
                          .map([](auto sbr) {
2✔
1453
                              return hasher()
2✔
1454
                                  .update(sbr.get_data(), sbr.length())
1✔
1455
                                  .to_string();
2✔
1456
                          })
1457
                          .unwrap();
1✔
1458

1459
                sqlite3_clear_bindings(stmt.in());
1✔
1460
                bind_to_sqlite(stmt.in(), 1, file_path);
1✔
1461
                sqlite3_bind_int(stmt.in(), 2, static_cast<int>(vl));
1✔
1462
                bind_to_sqlite(stmt.in(), 3, line_hash);
1✔
1463
                bind_to_sqlite(stmt.in(), 4, bm_type->get_name());
1✔
1464
                sqlite3_bind_int64(stmt.in(), 5, lnav_data.ld_session_time);
1✔
1465

1466
                if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1467
                    log_error("could not execute text_bookmarks insert -- %s",
×
1468
                              sqlite3_errmsg(db));
1469
                    return;
×
1470
                }
1471

1472
                sqlite3_reset(stmt.in());
1✔
1473
            }
1✔
1474
        }
1475
    }
1✔
1476
}
1✔
1477

1478
static void
1479
load_text_bookmarks(sqlite3* db)
29✔
1480
{
1481
    static const char* const TEXT_BOOKMARK_STMT = R"(
1482
        SELECT line_number, line_hash, mark_type, session_time,
1483
               session_time=? AS same_session
1484
        FROM text_bookmarks2
1485
        WHERE file_path = ?
1486
        ORDER BY same_session DESC, session_time DESC
1487
    )";
1488

1489
    auto& tss = lnav_data.ld_text_source;
29✔
1490
    auto& tc = lnav_data.ld_views[LNV_TEXT];
29✔
1491

1492
    if (tss.empty()) {
29✔
1493
        return;
28✔
1494
    }
1495

1496
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1497
    if (sqlite3_prepare_v2(db, TEXT_BOOKMARK_STMT, -1, stmt.out(), nullptr)
1✔
1498
        != SQLITE_OK)
1✔
1499
    {
1500
        log_error("could not prepare text_bookmarks select -- %s",
×
1501
                  sqlite3_errmsg(db));
1502
        return;
×
1503
    }
1504

1505
    for (const auto& fvs : tss.get_file_states()) {
2✔
1506
        auto& lf = fvs->fvs_file;
1✔
1507
        if (lf == nullptr || lf->size() == 0) {
1✔
1508
            continue;
×
1509
        }
1510

1511
        auto file_path = lf->get_path_for_key().string();
1✔
1512
        auto line_count = static_cast<int>(lf->size());
1✔
1513
        sqlite3_reset(stmt.in());
1✔
1514
        sqlite3_clear_bindings(stmt.in());
1✔
1515
        sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
1✔
1516
        bind_to_sqlite(stmt.in(), 2, file_path);
1✔
1517

1518
        size_t mark_count = 0;
1✔
1519
        int64_t last_session_time = -1;
1✔
1520
        bool done = false;
1✔
1521
        while (!done) {
3✔
1522
            auto rc = sqlite3_step(stmt.in());
2✔
1523

1524
            switch (rc) {
2✔
1525
                case SQLITE_OK:
1✔
1526
                case SQLITE_DONE:
1527
                    done = true;
1✔
1528
                    break;
1✔
1529

1530
                case SQLITE_ROW: {
1✔
1531
                    auto line_number = sqlite3_column_int(stmt.in(), 0);
1✔
1532
                    const auto* stored_hash
1533
                        = (const char*) sqlite3_column_text(stmt.in(), 1);
1✔
1534
                    const auto* mark_type
1535
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
1✔
1536
                    auto session_time = sqlite3_column_int64(stmt.in(), 3);
1✔
1537

1538
                    if (last_session_time == -1) {
1✔
1539
                        last_session_time = session_time;
1✔
1540
                    } else if (last_session_time != session_time) {
×
1541
                        done = true;
×
1542
                        continue;
×
1543
                    }
1544

1545
                    if (line_number >= line_count) {
1✔
1546
                        continue;
×
1547
                    }
1548

1549
                    auto bm_type_opt = bookmark_type_t::find_type(mark_type);
1✔
1550
                    if (!bm_type_opt) {
1✔
1551
                        continue;
×
1552
                    }
1553

1554
                    auto line_iter = lf->begin() + line_number;
1✔
1555
                    auto fr = lf->get_file_range(line_iter, false);
1✔
1556
                    auto read_result = lf->read_range(fr);
1✔
1557

1558
                    if (read_result.isErr()) {
1✔
1559
                        continue;
×
1560
                    }
1561

1562
                    auto line_hash
1563
                        = read_result
1564
                              .map([](auto sbr) {
2✔
1565
                                  return hasher()
2✔
1566
                                      .update(sbr.get_data(), sbr.length())
1✔
1567
                                      .to_string();
2✔
1568
                              })
1569
                              .unwrap();
1✔
1570

1571
                    if (line_hash != stored_hash) {
1✔
1572
                        log_warning("text bookmark hash mismatch at %s:%d",
×
1573
                                    file_path.c_str(),
1574
                                    line_number);
1575
                        continue;
×
1576
                    }
1577

1578
                    auto vl = vis_line_t(line_number);
1✔
1579
                    fvs->fvs_bookmarks[bm_type_opt.value()].insert_once(vl);
1✔
1580
                    fvs->fvs_content_marks[bm_type_opt.value()].insert_once(
1✔
1581
                        static_cast<uint32_t>(line_number));
1582
                    mark_count += 1;
1✔
1583
                    break;
1✔
1584
                }
2✔
1585

1586
                default:
×
1587
                    log_error("text bookmark select error: %d -- %s",
×
1588
                              rc,
1589
                              sqlite3_errmsg(db));
1590
                    done = true;
×
1591
                    break;
×
1592
            }
1593
        }
1594
        log_debug(
1✔
1595
            "loaded %zu text bookmarks from %s", mark_count, file_path.c_str());
1596
    }
1✔
1597

1598
    // Copy the front file's bookmarks into the textview
1599
    if (!tss.get_file_states().empty()) {
1✔
1600
        auto& front = tss.get_file_states().front();
1✔
1601
        tc.get_bookmarks()[&textview_curses::BM_USER]
1✔
1602
            = front->fvs_bookmarks[&textview_curses::BM_USER];
2✔
1603
        tc.get_bookmarks()[&textview_curses::BM_STICKY]
1✔
1604
            = front->fvs_bookmarks[&textview_curses::BM_STICKY];
2✔
1605
    }
1606

1607
    tc.reload_data();
1✔
1608
}
1✔
1609

1610
static void
1611
save_timeline_bookmarks(sqlite3* db)
28✔
1612
{
1613
    auto& tc = lnav_data.ld_views[LNV_TIMELINE];
28✔
1614
    auto* tss = static_cast<timeline_source*>(tc.get_sub_source());
28✔
1615

1616
    if (tss == nullptr) {
28✔
1617
        return;
×
1618
    }
1619

1620
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
1621

1622
    if (sqlite3_prepare_v2(db,
28✔
1623
                           "DELETE FROM timeline_bookmarks"
1624
                           " WHERE session_time = ?",
1625
                           -1,
1626
                           stmt.out(),
1627
                           nullptr)
1628
        != SQLITE_OK)
28✔
1629
    {
1630
        log_error("could not prepare timeline_bookmarks delete -- %s",
×
1631
                  sqlite3_errmsg(db));
1632
        return;
×
1633
    }
1634
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
28✔
1635
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
1636
        log_error("could not execute timeline_bookmarks delete -- %s",
×
1637
                  sqlite3_errmsg(db));
1638
        return;
×
1639
    }
1640

1641
    if (sqlite3_prepare_v2(db,
28✔
1642
                           "REPLACE INTO timeline_bookmarks"
1643
                           " (row_type, row_name, mark_type, session_time)"
1644
                           " VALUES (?, ?, ?, ?)",
1645
                           -1,
1646
                           stmt.out(),
1647
                           nullptr)
1648
        != SQLITE_OK)
28✔
1649
    {
1650
        log_error(
×
1651
            "could not prepare timeline_bookmarks replace statement -- %s",
1652
            sqlite3_errmsg(db));
1653
        return;
×
1654
    }
1655

1656
    static const bookmark_type_t* SAVE_TYPES[] = {
1657
        &textview_curses::BM_USER,
1658
        &textview_curses::BM_STICKY,
1659
    };
1660

1661
    for (const auto* bm_type : SAVE_TYPES) {
84✔
1662
        const auto& bv = tc.get_bookmarks()[bm_type];
56✔
1663
        if (bv.empty()) {
56✔
1664
            continue;
54✔
1665
        }
1666

1667
        for (const auto& vl : bv.bv_tree) {
4✔
1668
            auto line = static_cast<size_t>(vl);
2✔
1669
            if (line >= tss->ts_time_order.size()) {
2✔
1670
                continue;
×
1671
            }
1672

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

1675
            sqlite3_clear_bindings(stmt.in());
2✔
1676
            bind_to_sqlite(
2✔
1677
                stmt.in(), 1, timeline_source::row_type_to_string(row.or_type));
2✔
1678
            bind_to_sqlite(stmt.in(), 2, row.or_name.to_string());
2✔
1679
            bind_to_sqlite(stmt.in(), 3, bm_type->get_name());
2✔
1680
            sqlite3_bind_int64(stmt.in(), 4, lnav_data.ld_session_time);
2✔
1681

1682
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1683
                log_error("could not execute timeline_bookmarks insert -- %s",
×
1684
                          sqlite3_errmsg(db));
1685
                return;
×
1686
            }
1687

1688
            sqlite3_reset(stmt.in());
2✔
1689
        }
1690
    }
1691
}
28✔
1692

1693
static void
1694
load_timeline_bookmarks(sqlite3* db)
29✔
1695
{
1696
    static const char* const TIMELINE_BOOKMARK_STMT = R"(
1697
        SELECT row_type, row_name, mark_type, session_time,
1698
               session_time=? AS same_session
1699
        FROM timeline_bookmarks
1700
        ORDER BY same_session DESC, session_time DESC
1701
    )";
1702

1703
    auto* tss = static_cast<timeline_source*>(
1704
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
29✔
1705

1706
    if (tss == nullptr) {
29✔
1707
        return;
×
1708
    }
1709

1710
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
29✔
1711
    if (sqlite3_prepare_v2(db, TIMELINE_BOOKMARK_STMT, -1, stmt.out(), nullptr)
29✔
1712
        != SQLITE_OK)
29✔
1713
    {
1714
        log_debug("could not prepare timeline_bookmarks select -- %s",
×
1715
                  sqlite3_errmsg(db));
1716
        return;
×
1717
    }
1718

1719
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
29✔
1720

1721
    int64_t last_session_time = -1;
29✔
1722
    bool done = false;
29✔
1723
    while (!done) {
60✔
1724
        auto rc = sqlite3_step(stmt.in());
31✔
1725

1726
        switch (rc) {
31✔
1727
            case SQLITE_OK:
29✔
1728
            case SQLITE_DONE:
1729
                done = true;
29✔
1730
                break;
29✔
1731

1732
            case SQLITE_ROW: {
2✔
1733
                auto* row_type_str
1734
                    = (const char*) sqlite3_column_text(stmt.in(), 0);
2✔
1735
                auto* row_name
1736
                    = (const char*) sqlite3_column_text(stmt.in(), 1);
2✔
1737
                auto* mark_type
1738
                    = (const char*) sqlite3_column_text(stmt.in(), 2);
2✔
1739
                auto session_time = sqlite3_column_int64(stmt.in(), 3);
2✔
1740

1741
                if (last_session_time == -1) {
2✔
1742
                    last_session_time = session_time;
2✔
1743
                } else if (last_session_time != session_time) {
×
1744
                    done = true;
×
1745
                    continue;
×
1746
                }
1747

1748
                if (row_type_str == nullptr || row_name == nullptr
2✔
1749
                    || mark_type == nullptr)
2✔
1750
                {
1751
                    continue;
×
1752
                }
1753

1754
                auto bm_type_opt = bookmark_type_t::find_type(mark_type);
2✔
1755
                if (!bm_type_opt) {
2✔
1756
                    continue;
×
1757
                }
1758

1759
                auto rt_opt = timeline_source::row_type_from_string(
2✔
1760
                    row_type_str);
1761
                if (!rt_opt) {
2✔
1762
                    continue;
×
1763
                }
1764

1765
                tss->ts_pending_bookmarks.emplace_back(
4✔
1766
                    timeline_source::pending_bookmark{
4✔
1767
                        rt_opt.value(),
2✔
1768
                        row_name,
1769
                        bm_type_opt.value(),
2✔
1770
                    });
1771
                break;
2✔
1772
            }
1773

1774
            default:
×
1775
                log_error("timeline bookmark select error: %d -- %s",
×
1776
                          rc,
1777
                          sqlite3_errmsg(db));
1778
                done = true;
×
1779
                break;
×
1780
        }
1781
    }
1782
}
29✔
1783

1784
static void
1785
save_time_bookmarks()
28✔
1786
{
1787
    auto_sqlite3 db;
28✔
1788
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
28✔
1789
    auto_mem<char, sqlite3_free> errmsg;
28✔
1790
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
1791

1792
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
28✔
1793
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1794
        return;
×
1795
    }
1796

1797
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
28✔
1798
        != SQLITE_OK)
28✔
1799
    {
1800
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1801
        return;
×
1802
    }
1803

1804
    if (sqlite3_exec(
28✔
1805
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1806
        != SQLITE_OK)
28✔
1807
    {
1808
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1809
        return;
×
1810
    }
1811

1812
    {
1813
        static const char* UPDATE_NETLOCS_STMT
1814
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1815

1816
        std::set<std::string> netlocs;
28✔
1817

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

1821
        if (sqlite3_prepare_v2(
28✔
1822
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1823
            != SQLITE_OK)
28✔
1824
        {
1825
            log_error("could not prepare recent_netlocs statement -- %s",
×
1826
                      sqlite3_errmsg(db));
1827
            return;
×
1828
        }
1829

1830
        for (const auto& netloc : netlocs) {
28✔
1831
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1832

1833
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1834
                log_error("could not execute bookmark insert statement -- %s",
×
1835
                          sqlite3_errmsg(db));
1836
                return;
×
1837
            }
1838

1839
            sqlite3_reset(stmt.in());
×
1840
        }
1841
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
28✔
1842
    }
28✔
1843

1844
    auto& lss = lnav_data.ld_log_source;
28✔
1845
    auto& bm = lss.get_user_bookmarks();
28✔
1846

1847
    if (sqlite3_prepare_v2(db.in(),
28✔
1848
                           "DELETE FROM bookmarks WHERE "
1849
                           " log_time = ? and log_format = ? and log_hash = ? "
1850
                           " and session_time = ?",
1851
                           -1,
1852
                           stmt.out(),
1853
                           nullptr)
1854
        != SQLITE_OK)
28✔
1855
    {
1856
        log_error("could not prepare bookmark delete statement -- %s",
×
1857
                  sqlite3_errmsg(db));
1858
        return;
×
1859
    }
1860

1861
    for (auto& marked_session_line : marked_session_lines) {
30✔
1862
        sqlite3_clear_bindings(stmt.in());
2✔
1863

1864
        if (bind_values(stmt,
4✔
1865
                        marked_session_line.sl_time,
1866
                        marked_session_line.sl_format_name,
1867
                        marked_session_line.sl_line_hash,
2✔
1868
                        lnav_data.ld_session_time)
1869
            != SQLITE_OK)
2✔
1870
        {
1871
            continue;
×
1872
        }
1873

1874
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1875
            log_error("could not execute bookmark insert statement -- %s",
×
1876
                      sqlite3_errmsg(db));
1877
            return;
×
1878
        }
1879

1880
        sqlite3_reset(stmt.in());
2✔
1881
    }
1882

1883
    marked_session_lines.clear();
28✔
1884

1885
    if (sqlite3_prepare_v2(db.in(),
28✔
1886
                           "REPLACE INTO bookmarks"
1887
                           " (log_time, log_format, log_hash, session_time, "
1888
                           "part_name, comment, tags, annotations, log_opid,"
1889
                           " sticky)"
1890
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1891
                           -1,
1892
                           stmt.out(),
1893
                           nullptr)
1894
        != SQLITE_OK)
28✔
1895
    {
1896
        log_error("could not prepare bookmark replace statement -- %s",
×
1897
                  sqlite3_errmsg(db));
1898
        return;
×
1899
    }
1900

1901
    {
1902
        for (auto file_iter = lnav_data.ld_log_source.begin();
28✔
1903
             file_iter != lnav_data.ld_log_source.end();
56✔
1904
             ++file_iter)
28✔
1905
        {
1906
            auto lf = (*file_iter)->get_file();
28✔
1907

1908
            if (lf == nullptr) {
28✔
1909
                continue;
×
1910
            }
1911
            if (lf->size() == 0) {
28✔
1912
                continue;
×
1913
            }
1914

1915
            content_line_t base_content_line;
28✔
1916
            base_content_line = lss.get_file_base_content_line(file_iter);
28✔
1917
            base_content_line
1918
                = content_line_t(base_content_line + lf->size() - 1);
28✔
1919

1920
            if (!bind_line(db.in(),
28✔
1921
                           stmt.in(),
1922
                           base_content_line,
1923
                           lnav_data.ld_session_time))
28✔
1924
            {
1925
                continue;
×
1926
            }
1927

1928
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
28✔
1929
                log_error("could not bind log hash -- %s",
×
1930
                          sqlite3_errmsg(db.in()));
1931
                return;
×
1932
            }
1933

1934
            sqlite3_bind_int(stmt.in(), 10, 0);
28✔
1935

1936
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
1937
                log_error("could not execute bookmark insert statement -- %s",
×
1938
                          sqlite3_errmsg(db));
1939
                return;
×
1940
            }
1941

1942
            sqlite3_reset(stmt.in());
28✔
1943
        }
28✔
1944
    }
1945

1946
    save_user_bookmarks(db.in(),
28✔
1947
                        stmt.in(),
1948
                        bm[&textview_curses::BM_USER],
1949
                        bm[&textview_curses::BM_STICKY]);
1950
    for (const auto& ldd : lss) {
56✔
1951
        auto* lf = ldd->get_file_ptr();
28✔
1952
        if (lf == nullptr) {
28✔
1953
            continue;
×
1954
        }
1955

1956
        save_meta_bookmarks(db.in(), stmt.in(), lf);
28✔
1957
    }
1958

1959
    if (sqlite3_prepare_v2(db.in(),
28✔
1960
                           "DELETE FROM time_offset WHERE "
1961
                           " log_time = ? and log_format = ? and log_hash = ? "
1962
                           " and session_time = ?",
1963
                           -1,
1964
                           stmt.out(),
1965
                           NULL)
1966
        != SQLITE_OK)
28✔
1967
    {
1968
        log_error("could not prepare time_offset delete statement -- %s",
×
1969
                  sqlite3_errmsg(db));
1970
        return;
×
1971
    }
1972

1973
    for (auto& offset_session_line : offset_session_lines) {
29✔
1974
        sqlite3_clear_bindings(stmt.in());
1✔
1975

1976
        if (bind_values(stmt,
2✔
1977
                        offset_session_line.sl_time,
1978
                        offset_session_line.sl_format_name,
1979
                        offset_session_line.sl_line_hash,
1✔
1980
                        lnav_data.ld_session_time)
1981
            != SQLITE_OK)
1✔
1982
        {
1983
            continue;
×
1984
        }
1985

1986
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1987
            log_error("could not execute bookmark insert statement -- %s",
×
1988
                      sqlite3_errmsg(db));
1989
            return;
×
1990
        }
1991

1992
        sqlite3_reset(stmt.in());
1✔
1993
    }
1994

1995
    offset_session_lines.clear();
28✔
1996

1997
    if (sqlite3_prepare_v2(db.in(),
28✔
1998
                           "REPLACE INTO time_offset"
1999
                           " (log_time, log_format, log_hash, session_time, "
2000
                           "offset_sec, offset_usec)"
2001
                           " VALUES (?, ?, ?, ?, ?, ?)",
2002
                           -1,
2003
                           stmt.out(),
2004
                           nullptr)
2005
        != SQLITE_OK)
28✔
2006
    {
2007
        log_error("could not prepare time_offset replace statement -- %s",
×
2008
                  sqlite3_errmsg(db));
2009
        return;
×
2010
    }
2011

2012
    {
2013
        for (auto file_iter = lnav_data.ld_log_source.begin();
28✔
2014
             file_iter != lnav_data.ld_log_source.end();
56✔
2015
             ++file_iter)
28✔
2016
        {
2017
            auto lf = (*file_iter)->get_file();
28✔
2018
            if (lf == nullptr) {
28✔
2019
                continue;
×
2020
            }
2021
            if (lf->size() == 0) {
28✔
2022
                continue;
×
2023
            }
2024

2025
            if (bind_values(stmt,
84✔
2026
                            lf->original_line_time(lf->begin()),
2027
                            lf->get_format()->get_name(),
56✔
2028
                            lf->get_content_id(),
28✔
2029
                            lnav_data.ld_session_time)
2030
                != SQLITE_OK)
28✔
2031
            {
2032
                continue;
×
2033
            }
2034

2035
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
28✔
2036
                log_error("could not bind log hash -- %s",
×
2037
                          sqlite3_errmsg(db.in()));
2038
                return;
×
2039
            }
2040

2041
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
28✔
2042
                log_error("could not bind log hash -- %s",
×
2043
                          sqlite3_errmsg(db.in()));
2044
                return;
×
2045
            }
2046

2047
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
2048
                log_error("could not execute bookmark insert statement -- %s",
×
2049
                          sqlite3_errmsg(db));
2050
                return;
×
2051
            }
2052

2053
            sqlite3_reset(stmt.in());
28✔
2054
        }
28✔
2055
    }
2056

2057
    for (const auto& ls : lss) {
56✔
2058
        if (ls->get_file() == nullptr) {
28✔
2059
            continue;
25✔
2060
        }
2061

2062
        const auto lf = ls->get_file();
28✔
2063
        if (!lf->is_time_adjusted()) {
28✔
2064
            continue;
25✔
2065
        }
2066

2067
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
2068
        auto offset = lf->get_time_offset();
3✔
2069

2070
        bind_values(stmt.in(),
6✔
2071
                    lf->original_line_time(line_iter),
2072
                    lf->get_format()->get_name(),
6✔
2073
                    lf->get_content_id(),
3✔
2074
                    lnav_data.ld_session_time,
2075
                    offset.tv_sec,
2076
                    offset.tv_usec);
2077

2078
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
3✔
2079
            log_error("could not execute bookmark insert statement -- %s",
×
2080
                      sqlite3_errmsg(db));
2081
            return;
×
2082
        }
2083

2084
        sqlite3_reset(stmt.in());
3✔
2085
    }
28✔
2086

2087
    log_info("saved %d log bookmarks", sqlite3_changes(db.in()));
28✔
2088
    save_text_bookmarks(db.in());
28✔
2089
    log_info("saved %d text bookmarks", sqlite3_changes(db.in()));
28✔
2090
    save_timeline_bookmarks(db.in());
28✔
2091
    log_info("saved %d timeline bookmarks", sqlite3_changes(db.in()));
28✔
2092

2093
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
28✔
2094
        != SQLITE_OK)
28✔
2095
    {
2096
        log_error("unable to begin transaction -- %s", errmsg.in());
×
2097
        return;
×
2098
    }
2099

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

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

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

2134
    if (sqlite3_exec(db.in(),
28✔
2135
                     TIMELINE_BOOKMARK_LRU_STMT,
2136
                     nullptr,
2137
                     nullptr,
2138
                     errmsg.out())
2139
        != SQLITE_OK)
28✔
2140
    {
2141
        log_error("unable to delete old timeline bookmarks -- %s",
×
2142
                  errmsg.in());
2143
        return;
×
2144
    }
2145
    auto timeline_bm_changes = sqlite3_changes(db.in());
28✔
2146
    if (timeline_bm_changes > 0) {
28✔
2147
        log_info("deleted %d old timeline bookmarks", timeline_bm_changes);
×
2148
    }
2149
}
28✔
2150

2151
static void
2152
save_session_with_id(const std::string& session_id)
27✔
2153
{
2154
    auto_mem<FILE> file(fclose);
27✔
2155
    yajl_gen handle = nullptr;
27✔
2156

2157
    /* TODO: save the last search query */
2158

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

2161
    auto view_base_name
2162
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
54✔
2163
                      session_id,
2164
                      lnav_data.ld_session_time,
2165
                      getppid());
27✔
2166
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
27✔
2167
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
27✔
2168

2169
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
27✔
2170
        perror("Unable to open session file");
×
2171
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
27✔
2172
        perror("Unable to create yajl_gen object");
×
2173
    } else {
2174
        yajl_gen_config(
27✔
2175
            handle, yajl_gen_print_callback, yajl_writer, file.in());
2176

2177
        {
2178
            yajlpp_map root_map(handle);
27✔
2179

2180
            root_map.gen("save-time");
27✔
2181
            root_map.gen((long long) time(nullptr));
27✔
2182

2183
            root_map.gen("time-offset");
27✔
2184
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
27✔
2185

2186
            root_map.gen("files");
27✔
2187

2188
            {
2189
                yajlpp_array file_list(handle);
27✔
2190

2191
                for (auto& ld_file_name :
27✔
2192
                     lnav_data.ld_active_files.fc_file_names)
83✔
2193
                {
2194
                    file_list.gen(ld_file_name.first);
29✔
2195
                }
2196
            }
27✔
2197

2198
            root_map.gen("file-states");
27✔
2199

2200
            {
2201
                yajlpp_map file_states(handle);
27✔
2202

2203
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
56✔
2204
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
29✔
2205

2206
                    file_states.gen(lf->get_filename().native());
29✔
2207

2208
                    {
2209
                        yajlpp_map file_state(handle);
29✔
2210

2211
                        file_state.gen("visible");
29✔
2212
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
29✔
2213
                    }
29✔
2214
                }
2215
            }
27✔
2216

2217
            root_map.gen("views");
27✔
2218

2219
            {
2220
                yajlpp_map top_view_map(handle);
27✔
2221

2222
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
270✔
2223
                    auto& tc = lnav_data.ld_views[lpc];
243✔
2224
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
243✔
2225

2226
                    top_view_map.gen(lnav_view_strings[lpc]);
243✔
2227

2228
                    yajlpp_map view_map(handle);
243✔
2229

2230
                    view_map.gen("top_line");
243✔
2231

2232
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
243✔
2233
                        view_map.gen(-1LL);
228✔
2234
                    } else {
2235
                        view_map.gen((long long) tc.get_top());
15✔
2236
                    }
2237

2238
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
302✔
2239
                        && tc.get_inner_height() > 0_vl
4✔
2240
                        && tc.get_selection() != tc.get_inner_height() - 1)
302✔
2241
                    {
2242
                        auto sel = tc.get_selection();
3✔
2243
                        if (sel) {
3✔
2244
                            view_map.gen("focused_line");
3✔
2245
                            view_map.gen((long long) sel.value());
3✔
2246

2247
                            if (ta != nullptr) {
3✔
2248
                                auto anchor_opt
2249
                                    = ta->anchor_for_row(sel.value());
1✔
2250
                                if (anchor_opt) {
1✔
2251
                                    view_map.gen("anchor");
1✔
2252
                                    view_map.gen(anchor_opt.value());
1✔
2253
                                }
2254
                            }
1✔
2255
                        }
2256
                    }
2257

2258
                    view_map.gen("search");
243✔
2259
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
243✔
2260

2261
                    view_map.gen("word_wrap");
243✔
2262
                    view_map.gen(tc.get_word_wrap());
243✔
2263

2264
                    auto* tss = tc.get_sub_source();
243✔
2265
                    if (tss == nullptr) {
243✔
2266
                        continue;
54✔
2267
                    }
2268

2269
                    view_map.gen("filtering");
189✔
2270
                    view_map.gen(tss->tss_apply_filters);
189✔
2271

2272
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
189✔
2273
                        view_map.gen("min_level");
×
2274
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
2275
                    }
2276

2277
                    view_map.gen("commands");
189✔
2278
                    yajlpp_array cmd_array(handle);
189✔
2279

2280
                    tss->add_commands_for_session(
189✔
2281
                        [&](auto& cmd) { cmd_array.gen(cmd); });
202✔
2282
                }
243✔
2283
            }
27✔
2284
        }
27✔
2285

2286
        yajl_gen_clear(handle);
27✔
2287
        yajl_gen_free(handle);
27✔
2288

2289
        fclose(file.release());
27✔
2290

2291
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
27✔
2292

2293
        log_info("Saved session: %s", view_file_name.c_str());
27✔
2294
    }
2295
}
27✔
2296

2297
void
2298
save_session()
28✔
2299
{
2300
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
28✔
2301
        log_info("secure mode is enabled, not saving session");
×
2302
        return;
×
2303
    }
2304

2305
    static auto op = lnav_operation{"save_session"};
28✔
2306

2307
    auto op_guard = lnav_opid_guard::internal(op);
28✔
2308

2309
    log_debug("BEGIN save_session");
28✔
2310
    save_time_bookmarks();
28✔
2311

2312
    const auto opt_session_id = compute_session_id();
28✔
2313
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
54✔
2314
    for (const auto& pair : lnav_data.ld_session_id) {
32✔
2315
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
2316
            continue;
3✔
2317
        }
2318
        save_session_with_id(pair.first);
1✔
2319
    }
2320
    log_debug("END save_session");
28✔
2321
}
28✔
2322

2323
void
2324
reset_session()
6✔
2325
{
2326
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
2327

2328
    save_session();
6✔
2329

2330
    lnav_data.ld_session_time = time(nullptr);
6✔
2331
    session_data.sd_file_states.clear();
6✔
2332

2333
    for (auto& tc : lnav_data.ld_views) {
60✔
2334
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
2335
        auto& hmap = tc.get_highlights();
54✔
2336
        auto hl_iter = hmap.begin();
54✔
2337

2338
        while (hl_iter != hmap.end()) {
552✔
2339
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
2340
                ++hl_iter;
498✔
2341
            } else {
2342
                hmap.erase(hl_iter++);
×
2343
            }
2344
        }
2345

2346
        if (ttt != nullptr) {
54✔
2347
            ttt->clear_min_max_row_times();
36✔
2348
        }
2349
    }
2350

2351
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
2352
        lf->reset_state();
8✔
2353
    }
2354

2355
    lnav_data.ld_log_source.get_breakpoints().clear();
6✔
2356
    lnav_data.ld_log_source.lss_highlighters.clear();
6✔
2357
    lnav_data.ld_log_source.set_force_rebuild();
6✔
2358
    lnav_data.ld_log_source.set_marked_only(false);
6✔
2359
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
2360
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
2361
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
2362
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
2363
    rebuild_indexes(std::nullopt);
6✔
2364

2365
    lnav_data.ld_db_row_source.reset_user_state();
6✔
2366

2367
    auto* tss = static_cast<timeline_source*>(
2368
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
6✔
2369
    if (tss != nullptr) {
6✔
2370
        tss->ts_hidden_row_types.clear();
6✔
2371
        tss->ts_pending_bookmarks.clear();
6✔
2372
    }
2373

2374
    for (auto& tc : lnav_data.ld_views) {
60✔
2375
        text_sub_source* tss = tc.get_sub_source();
54✔
2376

2377
        if (tss == nullptr) {
54✔
2378
            continue;
12✔
2379
        }
2380
        tss->get_filters().clear_filters();
42✔
2381
        tss->tss_apply_filters = true;
42✔
2382
        tss->text_filters_changed();
42✔
2383
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
2384
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
2385
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
2386
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
2387
        tc.get_bookmarks()[&textview_curses::BM_STICKY].clear();
42✔
2388
        tc.reload_data();
42✔
2389
    }
2390

2391
    for (auto& fvs : lnav_data.ld_text_source.get_file_states()) {
6✔
2392
        fvs->fvs_bookmarks.clear();
×
2393
    }
2394

2395
    lnav_data.ld_filter_view.reload_data();
6✔
2396
    lnav_data.ld_files_view.reload_data();
6✔
2397
    for (const auto& format : log_format::get_root_formats()) {
468✔
2398
        auto* elf = dynamic_cast<external_log_format*>(format.get());
462✔
2399

2400
        if (elf == nullptr) {
462✔
2401
            continue;
30✔
2402
        }
2403

2404
        bool changed = false;
432✔
2405
        for (const auto& vd : elf->elf_value_defs) {
6,306✔
2406
            if (vd.second->vd_meta.lvm_user_hidden) {
5,874✔
2407
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
2408
                changed = true;
×
2409
            }
2410
        }
2411
        if (changed) {
432✔
2412
            elf->elf_value_defs_state->vds_generation += 1;
×
2413
        }
2414
    }
2415
}
6✔
2416

2417
void
2418
lnav::session::apply_view_commands()
30✔
2419
{
2420
    static auto op = lnav_operation{__FUNCTION__};
30✔
2421

2422
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2423

2424
    log_debug("applying view commands");
30✔
2425
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
300✔
2426
        const auto& vs = session_data.sd_view_states[view_index];
270✔
2427
        auto& tview = lnav_data.ld_views[view_index];
270✔
2428

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

2481
void
2482
lnav::session::restore_view_states()
30✔
2483
{
2484
    static auto op = lnav_operation{__FUNCTION__};
30✔
2485

2486
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2487

2488
    log_debug("restoring view states");
30✔
2489
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
300✔
2490
        const auto& vs = session_data.sd_view_states[view_index];
270✔
2491
        auto& tview = lnav_data.ld_views[view_index];
270✔
2492
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
270✔
2493

2494
        if (!vs.vs_search.empty()) {
270✔
2495
            tview.execute_search(vs.vs_search);
×
2496
            tview.set_follow_search_for(-1, {});
×
2497
        }
2498
        tview.set_word_wrap(vs.vs_word_wrap);
270✔
2499
        auto has_loc = tview.get_selection().has_value();
270✔
2500
        if (!has_loc && vs.vs_top >= 0
69✔
2501
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
339✔
2502
                || tview.get_top() == tview.get_top_for_last_row()))
270✔
2503
        {
2504
            log_info("restoring %s view top: %d",
18✔
2505
                     lnav_view_strings[view_index].data(),
2506
                     (int) vs.vs_top);
2507
            tview.set_top(vis_line_t(vs.vs_top), true);
18✔
2508
        }
2509
        if (!has_loc && vs.vs_selection) {
270✔
2510
            log_info("restoring %s view selection: %d",
2✔
2511
                     lnav_view_strings[view_index].data(),
2512
                     (int) vs.vs_selection.value_or(-1_vl));
2513
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
2✔
2514
        }
2515
        auto sel = tview.get_selection();
270✔
2516
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
69✔
2517
            && !vs.vs_anchor->empty())
339✔
2518
        {
2519
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
2520

2521
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
2522
                log_info("%s view anchor mismatch %s != %s",
×
2523
                         lnav_view_strings[view_index].data(),
2524
                         curr_anchor.value_or("").c_str(),
2525
                         vs.vs_anchor.value().c_str());
2526

2527
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
2528
                if (row_opt) {
×
2529
                    tview.set_selection(row_opt.value());
×
2530
                }
2531
            }
2532
        }
2533
        sel = tview.get_selection();
270✔
2534
        if (!sel) {
270✔
2535
            auto height = tview.get_inner_height();
69✔
2536
            if (height == 0) {
69✔
2537
            } else if (view_index == LNV_TEXT) {
1✔
2538
                auto lf = lnav_data.ld_text_source.current_file();
×
2539
                if (lf != nullptr) {
×
2540
                    switch (lf->get_text_format().value_or(
×
2541
                        text_format_t::TF_BINARY))
×
2542
                    {
2543
                        case text_format_t::TF_PLAINTEXT:
×
2544
                        case text_format_t::TF_LOG: {
2545
                            if (height > 0_vl) {
×
2546
                                tview.set_selection(height - 1_vl);
×
2547
                            }
2548
                            break;
×
2549
                        }
2550
                        default:
×
2551
                            tview.set_selection(0_vl);
×
2552
                            break;
×
2553
                    }
2554
                }
2555
            } else if (view_index == LNV_LOG) {
1✔
2556
                tview.set_selection(height - 1_vl);
×
2557
            } else {
2558
                tview.set_selection(0_vl);
1✔
2559
            }
2560
        }
2561
        log_info("%s view actual top/selection: %d/%d",
270✔
2562
                 lnav_view_strings[view_index].data(),
2563
                 (int) tview.get_top(),
2564
                 (int) tview.get_selection().value_or(-1_vl));
2565
    }
2566
}
30✔
2567

2568
void
2569
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2570
{
2571
    constexpr const char* STMT = R"(
×
2572
       INSERT INTO regex101_entries
2573
          (format_name, regex_name, permalink, delete_code)
2574
          VALUES (?, ?, ?, ?);
2575
)";
2576

2577
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2578
    auto_sqlite3 db;
×
2579

2580
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2581
        return;
×
2582
    }
2583

2584
    auto_mem<char, sqlite3_free> errmsg;
×
2585
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2586
        != SQLITE_OK)
×
2587
    {
2588
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2589
        return;
×
2590
    }
2591

2592
    auto prep_res = prepare_stmt(db.in(),
2593
                                 STMT,
2594
                                 ei.re_format_name,
×
2595
                                 ei.re_regex_name,
×
2596
                                 ei.re_permalink,
×
2597
                                 ei.re_delete_code);
×
2598

2599
    if (prep_res.isErr()) {
×
2600
        return;
×
2601
    }
2602

2603
    auto ps = prep_res.unwrap();
×
2604

2605
    ps.execute();
×
2606
}
2607

2608
template<>
2609
struct from_sqlite<lnav::session::regex101::entry> {
2610
    inline lnav::session::regex101::entry operator()(int argc,
2611
                                                     sqlite3_value** argv,
2612
                                                     int argi)
2613
    {
2614
        return {
2615
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
2616
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2617
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2618
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2619
        };
2620
    }
2621
};
2622

2623
Result<std::vector<lnav::session::regex101::entry>, std::string>
2624
lnav::session::regex101::get_entries()
×
2625
{
2626
    constexpr const char* STMT = R"(
×
2627
       SELECT * FROM regex101_entries;
2628
)";
2629

2630
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2631
    auto_sqlite3 db;
×
2632

2633
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2634
        return Err(std::string());
×
2635
    }
2636

2637
    auto_mem<char, sqlite3_free> errmsg;
×
2638
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2639
        != SQLITE_OK)
×
2640
    {
2641
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2642
        return Err(std::string(errmsg));
×
2643
    }
2644

2645
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2646
    bool done = false;
×
2647
    std::vector<entry> retval;
×
2648

2649
    while (!done) {
×
2650
        auto fetch_res = ps.fetch_row<entry>();
×
2651

2652
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2653
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2654
        }
2655

2656
        fetch_res.match(
×
2657
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2658
            [](const prepared_stmt::fetch_error&) {},
×
2659
            [&retval](entry en) { retval.emplace_back(en); });
×
2660
    }
2661
    return Ok(retval);
×
2662
}
2663

2664
void
2665
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2666
                                      const std::string& regex_name)
2667
{
2668
    constexpr const char* STMT = R"(
×
2669
       DELETE FROM regex101_entries WHERE
2670
          format_name = ? AND regex_name = ?;
2671
)";
2672

2673
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2674
    auto_sqlite3 db;
×
2675

2676
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2677
        return;
×
2678
    }
2679

2680
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2681

2682
    if (prep_res.isErr()) {
×
2683
        return;
×
2684
    }
2685

2686
    auto ps = prep_res.unwrap();
×
2687

2688
    ps.execute();
×
2689
}
2690

2691
lnav::session::regex101::get_result_t
2692
lnav::session::regex101::get_entry(const std::string& format_name,
×
2693
                                   const std::string& regex_name)
2694
{
2695
    constexpr const char* STMT = R"(
×
2696
       SELECT * FROM regex101_entries WHERE
2697
          format_name = ? AND regex_name = ?;
2698
    )";
2699

2700
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2701
    auto_sqlite3 db;
×
2702

2703
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2704
        return error{std::string()};
×
2705
    }
2706

2707
    auto_mem<char, sqlite3_free> errmsg;
×
2708
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2709
        != SQLITE_OK)
×
2710
    {
2711
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2712
        return error{std::string(errmsg)};
×
2713
    }
2714

2715
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2716
    if (prep_res.isErr()) {
×
2717
        return error{prep_res.unwrapErr()};
×
2718
    }
2719

2720
    auto ps = prep_res.unwrap();
×
2721
    return ps.fetch_row<entry>().match(
×
2722
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2723
            return error{fe.fe_msg};
×
2724
        },
2725
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2726
            return no_entry{};
×
2727
        },
2728
        [](const entry& en) -> get_result_t { return en; });
×
2729
}
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