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

tstack / lnav / 23759254759-2902

30 Mar 2026 05:34PM UTC coverage: 69.091% (+0.03%) from 69.059%
23759254759-2902

push

github

tstack
[timeline] support sticky headers

191 of 232 new or added lines in 6 files covered. (82.33%)

5 existing lines in 1 file now uncovered.

53208 of 77012 relevant lines covered (69.09%)

536256.47 hits per line

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

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

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

35
#include "session_data.hh"
36

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

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

65
session_data_t session_data;
66
recent_refs_t recent_refs;
67

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

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

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

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

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

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

102
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
103

104
    PRIMARY KEY (netloc)
105
);
106

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

113
    PRIMARY KEY (format_name, regex_name),
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

208
    sqlite3_clear_bindings(stmt);
28✔
209

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

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

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

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

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

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

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

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

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

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

299
    session_info_list.sort();
31✔
300

301
    size_t session_loops = 0;
31✔
302

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

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

324
    session_info_list.sort();
31✔
325

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

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

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

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

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

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

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

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

383
    cleanup_session_data();
31✔
384

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

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

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

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

423
    session_file_names.sort();
30✔
424

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567
        --high_line_iter;
29✔
568

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

729
                                    if (!YAJL_IS_STRING(elem)) {
10✔
730
                                        continue;
×
731
                                    }
732
                                    bookmark_metadata::KNOWN_TAGS.insert(
10✔
733
                                        elem->u.string);
10✔
734
                                    bm_meta[line_number].add_tag(
30✔
735
                                        elem->u.string);
10✔
736
                                }
737
                            }
738
                            meta = true;
10✔
739
                        }
10✔
740
                        if (annotations != nullptr && annotations[0] != '\0') {
20✔
741
                            static const intern_string_t SRC
742
                                = intern_string::lookup("annotations");
3✔
743

744
                            const auto anno_sf
745
                                = string_fragment::from_c_str(annotations);
1✔
746
                            auto parse_res
747
                                = logmsg_annotations_handlers.parser_for(SRC)
2✔
748
                                      .of(anno_sf);
1✔
749
                            if (parse_res.isErr()) {
1✔
750
                                log_error(
×
751
                                    "unable to parse annotations JSON -- "
752
                                    "%s",
753
                                    parse_res.unwrapErr()[0]
754
                                        .to_attr_line()
755
                                        .get_string()
756
                                        .c_str());
757
                            } else if (bm_meta.find(line_number)
1✔
758
                                       == bm_meta.end())
2✔
759
                            {
760
                                lss.set_user_mark(&textview_curses::BM_META,
1✔
761
                                                  line_cl);
762
                                bm_meta[line_number].bm_annotations
1✔
763
                                    = parse_res.unwrap();
2✔
764
                                meta = true;
1✔
765
                            } else {
766
                                meta = true;
×
767
                            }
768
                        }
1✔
769
                        if (log_opid != nullptr && log_opid[0] != '\0') {
20✔
770
                            auto opid_sf
771
                                = string_fragment::from_c_str(log_opid);
×
772
                            lf->set_logline_opid(line_number, opid_sf);
×
773
                            meta = true;
×
774
                        }
775
                        if (meta) {
20✔
776
                            meta_loaded = true;
18✔
777
                        } else if (part_name != nullptr) {
2✔
778
                            marked_session_lines.emplace_back(
2✔
779
                                lf->original_line_time(line_iter),
2✔
780
                                format->get_name(),
2✔
781
                                line_hash);
782
                            lss.set_user_mark(&textview_curses::BM_USER,
2✔
783
                                              line_cl);
784
                        }
785
                        if (sticky) {
20✔
786
                            lss.set_user_mark(&textview_curses::BM_STICKY,
×
787
                                              line_cl);
788
                        }
789
                        reload_needed = true;
20✔
790
                        break;
20✔
791
                    }
66✔
792
                    break;
20✔
793
                }
794

795
                default: {
×
796
                    const char* errmsg;
797

798
                    errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
799
                    log_error(
×
800
                        "bookmark select error: code %d -- %s", rc, errmsg);
801
                    done = true;
×
802
                } break;
×
803
            }
804
        }
805

806
        sqlite3_reset(stmt.in());
29✔
807
    }
29✔
808
    log_info("END select bookmarks");
29✔
809

810
    log_info("BEGIN select time_offset");
29✔
811
    if (sqlite3_prepare_v2(db.in(), TIME_OFFSET_STMT, -1, stmt.out(), nullptr)
29✔
812
        != SQLITE_OK)
29✔
813
    {
814
        log_error("could not prepare time_offset select statement -- %s",
×
815
                  sqlite3_errmsg(db));
816
        return;
×
817
    }
818

819
    for (auto file_iter = lnav_data.ld_log_source.begin();
29✔
820
         file_iter != lnav_data.ld_log_source.end();
58✔
821
         ++file_iter)
29✔
822
    {
823
        auto lf = (*file_iter)->get_file();
29✔
824
        content_line_t base_content_line;
29✔
825

826
        if (lf == nullptr) {
29✔
827
            continue;
×
828
        }
829
        if (lf->size() == 0) {
29✔
830
            continue;
×
831
        }
832

833
        lss.find(lf->get_filename_as_string().c_str(), base_content_line);
29✔
834

835
        auto low_line_iter = lf->begin();
29✔
836
        auto high_line_iter = lf->end();
29✔
837

838
        --high_line_iter;
29✔
839

840
        if (bind_values(stmt.in(),
58✔
841
                        lnav_data.ld_session_load_time,
842
                        lf->get_content_id(),
29✔
843
                        lf->original_line_time(low_line_iter),
844
                        lf->original_line_time(high_line_iter),
845
                        lf->get_format()->get_name())
58✔
846
            != SQLITE_OK)
29✔
847
        {
848
            return;
×
849
        }
850

851
        date_time_scanner dts;
29✔
852
        auto done = false;
29✔
853
        int64_t last_mark_time = -1;
29✔
854

855
        while (!done) {
86✔
856
            const auto rc = sqlite3_step(stmt.in());
57✔
857

858
            switch (rc) {
57✔
859
                case SQLITE_OK:
29✔
860
                case SQLITE_DONE:
861
                    done = true;
29✔
862
                    break;
29✔
863

864
                case SQLITE_ROW: {
28✔
865
                    const auto* log_time
866
                        = (const char*) sqlite3_column_text(stmt.in(), 0);
28✔
867
                    int64_t mark_time = sqlite3_column_int64(stmt.in(), 3);
28✔
868
                    timeval log_tv;
869
                    exttm log_tm;
28✔
870

871
                    if (last_mark_time == -1) {
28✔
872
                        last_mark_time = mark_time;
28✔
873
                    } else if (last_mark_time != mark_time) {
×
874
                        done = true;
×
875
                        continue;
25✔
876
                    }
877

878
                    if (sqlite3_column_type(stmt.in(), 4) == SQLITE_NULL) {
28✔
879
                        continue;
25✔
880
                    }
881

882
                    if (!dts.scan(log_time,
3✔
883
                                  strlen(log_time),
884
                                  nullptr,
885
                                  &log_tm,
886
                                  log_tv))
887
                    {
888
                        continue;
×
889
                    }
890

891
                    auto line_iter
892
                        = lower_bound(lf->begin(), lf->end(), log_tv);
3✔
893
                    while (line_iter != lf->end()) {
6✔
894
                        auto line_tv = line_iter->get_timeval();
6✔
895

896
                        if ((line_tv.tv_sec != log_tv.tv_sec)
6✔
897
                            || (line_tv.tv_usec / 1000
3✔
898
                                != log_tv.tv_usec / 1000))
3✔
899
                        {
900
                            break;
901
                        }
902

903
                        int file_line = std::distance(lf->begin(), line_iter);
3✔
904
                        timeval offset;
905

906
                        offset_session_lines.emplace_back(
3✔
907
                            lf->original_line_time(line_iter),
3✔
908
                            lf->get_format_ptr()->get_name(),
3✔
909
                            lf->get_content_id());
3✔
910
                        offset.tv_sec = sqlite3_column_int64(stmt.in(), 4);
3✔
911
                        offset.tv_usec = sqlite3_column_int64(stmt.in(), 5);
3✔
912
                        lf->adjust_content_time(file_line, offset);
3✔
913

914
                        reload_needed = true;
3✔
915

916
                        ++line_iter;
3✔
917
                    }
918
                    break;
3✔
919
                }
920

921
                default: {
×
922
                    const auto* errmsg = sqlite3_errmsg(lnav_data.ld_db);
×
923
                    log_error(
×
924
                        "bookmark select error: code %d -- %s", rc, errmsg);
925
                    done = true;
×
926
                    break;
×
927
                }
928
            }
929
        }
930

931
        sqlite3_reset(stmt.in());
29✔
932
    }
29✔
933
    log_info("END select time_offset");
29✔
934

935
    if (reload_needed) {
29✔
936
        if (meta_loaded) {
17✔
937
            lss.set_line_meta_changed();
17✔
938
            lss.text_filters_changed();
17✔
939
        }
940
        lnav_data.ld_views[LNV_LOG].reload_data();
17✔
941
    }
942

943
    load_text_bookmarks(db.in());
29✔
944
    load_timeline_bookmarks(db.in());
29✔
945
}
39✔
946

947
static int
948
read_files(yajlpp_parse_context* ypc,
27✔
949
           const unsigned char* str,
950
           size_t len,
951
           yajl_string_props_t*)
952
{
953
    return 1;
27✔
954
}
955

956
const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
957
    {level_names[LEVEL_TRACE], LEVEL_TRACE},
958
    {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5},
959
    {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4},
960
    {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3},
961
    {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2},
962
    {level_names[LEVEL_DEBUG], LEVEL_DEBUG},
963
    {level_names[LEVEL_INFO], LEVEL_INFO},
964
    {level_names[LEVEL_STATS], LEVEL_STATS},
965
    {level_names[LEVEL_NOTICE], LEVEL_NOTICE},
966
    {level_names[LEVEL_WARNING], LEVEL_WARNING},
967
    {level_names[LEVEL_ERROR], LEVEL_ERROR},
968
    {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL},
969
    {level_names[LEVEL_FATAL], LEVEL_FATAL},
970

971
    json_path_handler_base::ENUM_TERMINATOR,
972
};
973

974
static const json_path_container view_def_handlers = {
975
    json_path_handler("top_line").for_field(&view_state::vs_top),
976
    json_path_handler("focused_line").for_field(&view_state::vs_selection),
977
    json_path_handler("anchor").for_field(&view_state::vs_anchor),
978
    json_path_handler("search").for_field(&view_state::vs_search),
979
    json_path_handler("word_wrap").for_field(&view_state::vs_word_wrap),
980
    json_path_handler("filtering").for_field(&view_state::vs_filtering),
981
    json_path_handler("min_level")
982
        .with_enum_values(LEVEL_ENUM)
983
        .for_field(&view_state::vs_min_log_level),
984
    json_path_handler("commands#").for_field(&view_state::vs_commands),
985
};
986

987
static const json_path_container view_handlers = {
988
    yajlpp::pattern_property_handler("(?<view_name>[\\w\\-]+)")
989
        .with_obj_provider<view_state, session_data_t>(
990
            +[](const yajlpp_provider_context& ypc, session_data_t* root) {
2,046✔
991
                auto view_index_opt
992
                    = view_from_string(ypc.get_substr("view_name").c_str());
2,046✔
993
                if (view_index_opt) {
2,046✔
994
                    return &root->sd_view_states[view_index_opt.value()];
2,046✔
995
                }
996

997
                log_error("unknown view name: %s",
×
998
                          ypc.get_substr("view_name").c_str());
999
                static view_state dummy;
1000
                return &dummy;
×
1001
            })
1002
        .with_children(view_def_handlers),
1003
};
1004

1005
static const json_path_container file_state_handlers = {
1006
    yajlpp::property_handler("visible")
1007
        .with_description("Indicates whether the file is visible or not")
1008
        .for_field(&file_state::fs_is_visible),
1009
};
1010

1011
static const json_path_container file_states_handlers = {
1012
    yajlpp::pattern_property_handler(R"((?<filename>[^/]+))")
1013
        .with_description("Map of file names to file state objects")
1014
        .with_obj_provider<file_state, session_data_t>(
1015
            [](const auto& ypc, session_data_t* root) {
81✔
1016
                auto fn = ypc.get_substr("filename");
81✔
1017
                return &root->sd_file_states[fn];
162✔
1018
            })
81✔
1019
        .with_children(file_state_handlers),
1020
};
1021

1022
static const typed_json_path_container<session_data_t> view_info_handlers = {
1023
    yajlpp::property_handler("save-time")
1024
        .for_field(&session_data_t::sd_save_time),
1025
    yajlpp::property_handler("time-offset")
1026
        .for_field(&session_data_t::sd_time_offset),
1027
    json_path_handler("files#", read_files),
1028
    yajlpp::property_handler("file-states").with_children(file_states_handlers),
1029
    yajlpp::property_handler("views").with_children(view_handlers),
1030
};
1031

1032
void
1033
load_session()
31✔
1034
{
1035
    static auto op = lnav_operation{"load_session"};
31✔
1036

1037
    auto op_guard = lnav_opid_guard::internal(op);
31✔
1038
    log_info("BEGIN load_session");
31✔
1039
    scan_sessions() | [](const auto pair) {
31✔
1040
        lnav_data.ld_session_load_time = pair.first.second;
28✔
1041
        const auto& view_info_path = pair.second;
28✔
1042
        auto view_info_src = intern_string::lookup(view_info_path.string());
28✔
1043

1044
        auto open_res = lnav::filesystem::open_file(view_info_path, O_RDONLY);
28✔
1045
        if (open_res.isErr()) {
28✔
1046
            log_error("cannot open session file: %s -- %s",
×
1047
                      view_info_path.c_str(),
1048
                      open_res.unwrapErr().c_str());
1049
            return;
×
1050
        }
1051

1052
        auto fd = open_res.unwrap();
28✔
1053
        unsigned char buffer[1024];
1054
        ssize_t rc;
1055

1056
        log_info("loading session file: %s", view_info_path.c_str());
28✔
1057
        auto parser = view_info_handlers.parser_for(view_info_src);
28✔
1058
        while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
86✔
1059
            auto buf_frag = string_fragment::from_bytes(buffer, rc);
29✔
1060
            auto parse_res = parser.consume(buf_frag);
29✔
1061
            if (parse_res.isErr()) {
29✔
1062
                log_error("failed to load session: %s -- %s",
×
1063
                          view_info_path.c_str(),
1064
                          parse_res.unwrapErr()[0]
1065
                              .to_attr_line()
1066
                              .get_string()
1067
                              .c_str());
1068
                return;
×
1069
            }
1070
        }
1071

1072
        auto complete_res = parser.complete();
28✔
1073
        if (complete_res.isErr()) {
28✔
1074
            log_error("failed to load session: %s -- %s",
×
1075
                      view_info_path.c_str(),
1076
                      complete_res.unwrapErr()[0]
1077
                          .to_attr_line()
1078
                          .get_string()
1079
                          .c_str());
1080
            return;
×
1081
        }
1082
        session_data = complete_res.unwrap();
28✔
1083

1084
        bool log_changes = false;
28✔
1085

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

1089
            if (iter == session_data.sd_file_states.end()) {
29✔
1090
                continue;
2✔
1091
            }
1092

1093
            log_debug("found state for file: %s %d (%s)",
27✔
1094
                      lf->get_content_id().c_str(),
1095
                      iter->second.fs_is_visible,
1096
                      lf->get_filename_as_string().c_str());
1097
            lnav_data.ld_log_source.find_data(lf) |
27✔
1098
                [iter, &log_changes](auto ld) {
52✔
1099
                    if (ld->ld_visible != iter->second.fs_is_visible) {
26✔
1100
                        ld->get_file_ptr()->set_indexing(
1✔
1101
                            iter->second.fs_is_visible);
1✔
1102
                        ld->set_visibility(iter->second.fs_is_visible);
1✔
1103
                        log_changes = true;
1✔
1104
                    }
1105
                };
1106
        }
1107

1108
        if (log_changes) {
28✔
1109
            lnav_data.ld_log_source.text_filters_changed();
1✔
1110
        }
1111
    };
28✔
1112

1113
    lnav::events::publish(lnav_data.ld_db.in(),
31✔
1114
                          lnav::events::session::loaded{});
1115

1116
    log_info("END load_session");
31✔
1117
}
31✔
1118

1119
static void
1120
yajl_writer(void* context, const char* str, size_t len)
10,149✔
1121
{
1122
    FILE* file = (FILE*) context;
10,149✔
1123

1124
    fwrite(str, len, 1, file);
10,149✔
1125
}
10,149✔
1126

1127
static void
1128
save_user_bookmarks(sqlite3* db,
28✔
1129
                    sqlite3_stmt* stmt,
1130
                    bookmark_vector<content_line_t>& user_marks,
1131
                    bookmark_vector<content_line_t>& sticky_marks)
1132
{
1133
    auto& lss = lnav_data.ld_log_source;
28✔
1134

1135
    // Collect all lines that are either user-marked or sticky
1136
    tlx::btree_set<content_line_t> all_lines;
28✔
1137
    for (const auto& cl : user_marks.bv_tree) {
29✔
1138
        all_lines.insert(cl);
1✔
1139
    }
1140
    for (const auto& cl : sticky_marks.bv_tree) {
28✔
1141
        all_lines.insert(cl);
×
1142
    }
1143

1144
    for (const auto& cl_const : all_lines) {
29✔
1145
        auto cl = cl_const;
1✔
1146
        auto lf = lss.find(cl);
1✔
1147
        if (lf == nullptr) {
1✔
1148
            continue;
×
1149
        }
1150

1151
        sqlite3_clear_bindings(stmt);
1✔
1152

1153
        const auto line_iter = lf->begin() + cl;
1✔
1154
        auto fr = lf->get_file_range(line_iter, false);
1✔
1155
        auto read_result = lf->read_range(fr);
1✔
1156

1157
        if (read_result.isErr()) {
1✔
1158
            continue;
×
1159
        }
1160

1161
        auto line_hash = read_result
1162
                             .map([cl](auto sbr) {
2✔
1163
                                 return hasher()
2✔
1164
                                     .update(sbr.get_data(), sbr.length())
1✔
1165
                                     .update(cl)
1✔
1166
                                     .to_string();
2✔
1167
                             })
1168
                             .unwrap();
1✔
1169

1170
        if (bind_values(stmt,
3✔
1171
                        lf->original_line_time(line_iter),
1172
                        lf->get_format()->get_name(),
2✔
1173
                        line_hash,
1174
                        lnav_data.ld_session_time)
1175
            != SQLITE_OK)
1✔
1176
        {
1177
            continue;
×
1178
        }
1179

1180
        auto is_user = user_marks.bv_tree.find(cl) != user_marks.bv_tree.end();
1✔
1181
        auto is_sticky
1182
            = sticky_marks.bv_tree.find(cl) != sticky_marks.bv_tree.end();
1✔
1183

1184
        // Use part_name "" for user marks, null for sticky-only
1185
        if (is_user) {
1✔
1186
            if (sqlite3_bind_text(stmt, 5, "", 0, SQLITE_TRANSIENT)
1✔
1187
                != SQLITE_OK)
1✔
1188
            {
1189
                log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1190
                return;
×
1191
            }
1192
        } else {
1193
            sqlite3_bind_null(stmt, 5);
×
1194
        }
1195

1196
        if (sqlite3_bind_int(stmt, 10, is_sticky ? 1 : 0) != SQLITE_OK) {
1✔
1197
            log_error("could not bind sticky -- %s", sqlite3_errmsg(db));
×
1198
            return;
×
1199
        }
1200

1201
        if (sqlite3_step(stmt) != SQLITE_DONE) {
1✔
1202
            log_error("could not execute bookmark insert statement -- %s",
×
1203
                      sqlite3_errmsg(db));
1204
            return;
×
1205
        }
1206

1207
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
1✔
1208
                                          lf->get_format_ptr()->get_name(),
1✔
1209
                                          line_hash);
1210

1211
        sqlite3_reset(stmt);
1✔
1212
    }
1✔
1213
}
28✔
1214

1215
static void
1216
save_meta_bookmarks(sqlite3* db, sqlite3_stmt* stmt, logfile* lf)
28✔
1217
{
1218
    for (const auto& bm_pair : lf->get_bookmark_metadata()) {
36✔
1219
        auto cl = content_line_t(bm_pair.first);
8✔
1220
        sqlite3_clear_bindings(stmt);
8✔
1221

1222
        auto line_iter = lf->begin() + cl;
8✔
1223
        auto fr = lf->get_file_range(line_iter, false);
8✔
1224
        auto read_result = lf->read_range(fr);
8✔
1225

1226
        if (read_result.isErr()) {
8✔
1227
            continue;
×
1228
        }
1229

1230
        auto line_hash = read_result
1231
                             .map([cl](auto sbr) {
16✔
1232
                                 return hasher()
16✔
1233
                                     .update(sbr.get_data(), sbr.length())
8✔
1234
                                     .update(cl)
8✔
1235
                                     .to_string();
16✔
1236
                             })
1237
                             .unwrap();
8✔
1238

1239
        if (bind_values(stmt,
24✔
1240
                        lf->original_line_time(line_iter),
1241
                        lf->get_format()->get_name(),
16✔
1242
                        line_hash,
1243
                        lnav_data.ld_session_time)
1244
            != SQLITE_OK)
8✔
1245
        {
1246
            continue;
×
1247
        }
1248

1249
        const auto& line_meta = bm_pair.second;
8✔
1250
        if (line_meta.empty(bookmark_metadata::categories::any)) {
8✔
1251
            continue;
×
1252
        }
1253

1254
        if (sqlite3_bind_text(stmt,
8✔
1255
                              5,
1256
                              line_meta.bm_name.c_str(),
1257
                              line_meta.bm_name.length(),
8✔
1258
                              SQLITE_TRANSIENT)
1259
            != SQLITE_OK)
8✔
1260
        {
1261
            log_error("could not bind part name -- %s", sqlite3_errmsg(db));
×
1262
            return;
×
1263
        }
1264

1265
        if (sqlite3_bind_text(stmt,
8✔
1266
                              6,
1267
                              line_meta.bm_comment.c_str(),
1268
                              line_meta.bm_comment.length(),
8✔
1269
                              SQLITE_TRANSIENT)
1270
            != SQLITE_OK)
8✔
1271
        {
1272
            log_error("could not bind comment -- %s", sqlite3_errmsg(db));
×
1273
            return;
×
1274
        }
1275

1276
        std::string tags;
8✔
1277

1278
        if (!line_meta.bm_tags.empty()) {
8✔
1279
            yajlpp_gen gen;
5✔
1280

1281
            yajl_gen_config(gen, yajl_gen_beautify, false);
5✔
1282

1283
            {
1284
                yajlpp_array arr(gen);
5✔
1285

1286
                for (const auto& str : line_meta.bm_tags) {
10✔
1287
                    arr.gen(str);
5✔
1288
                }
1289
            }
5✔
1290

1291
            tags = gen.to_string_fragment().to_string();
5✔
1292
        }
5✔
1293

1294
        if (sqlite3_bind_text(
8✔
1295
                stmt, 7, tags.c_str(), tags.length(), SQLITE_TRANSIENT)
8✔
1296
            != SQLITE_OK)
8✔
1297
        {
1298
            log_error("could not bind tags -- %s", sqlite3_errmsg(db));
×
1299
            return;
×
1300
        }
1301

1302
        if (!line_meta.bm_annotations.la_pairs.empty()) {
8✔
1303
            auto anno_str = logmsg_annotations_handlers.to_string(
1304
                line_meta.bm_annotations);
1✔
1305

1306
            if (sqlite3_bind_text(stmt,
1✔
1307
                                  8,
1308
                                  anno_str.c_str(),
1309
                                  anno_str.length(),
1✔
1310
                                  SQLITE_TRANSIENT)
1311
                != SQLITE_OK)
1✔
1312
            {
1313
                log_error("could not bind annotations -- %s",
×
1314
                          sqlite3_errmsg(db));
1315
                return;
×
1316
            }
1317
        } else {
1✔
1318
            sqlite3_bind_null(stmt, 8);
7✔
1319
        }
1320

1321
        if (line_meta.bm_opid.empty()) {
8✔
1322
            sqlite3_bind_null(stmt, 9);
8✔
1323
        } else {
1324
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1325
        }
1326

1327
        sqlite3_bind_int(stmt, 10, 0);
8✔
1328

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

1335
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
8✔
1336
                                          lf->get_format_ptr()->get_name(),
8✔
1337
                                          line_hash);
1338

1339
        sqlite3_reset(stmt);
8✔
1340
    }
8✔
1341
}
1342

1343
static void
1344
save_text_bookmarks(sqlite3* db)
28✔
1345
{
1346
    auto& tss = lnav_data.ld_text_source;
28✔
1347

1348
    if (tss.empty()) {
28✔
1349
        return;
27✔
1350
    }
1351

1352
    tss.copy_bookmarks_to_current_file();
1✔
1353

1354
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1355

1356
    if (sqlite3_prepare_v2(db,
1✔
1357
                           "DELETE FROM text_bookmarks2 WHERE session_time = ?",
1358
                           -1,
1359
                           stmt.out(),
1360
                           nullptr)
1361
        != SQLITE_OK)
1✔
1362
    {
1363
        log_error("could not prepare text_bookmarks delete -- %s",
×
1364
                  sqlite3_errmsg(db));
1365
        return;
×
1366
    }
1367
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
1✔
1368
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1369
        log_error("could not execute text_bookmarks delete -- %s",
×
1370
                  sqlite3_errmsg(db));
1371
        return;
×
1372
    }
1373

1374
    if (sqlite3_prepare_v2(
1✔
1375
            db,
1376
            "REPLACE INTO text_bookmarks2"
1377
            " (file_path, line_number, line_hash, mark_type, session_time)"
1378
            " VALUES (?, ?, ?, ?, ?)",
1379
            -1,
1380
            stmt.out(),
1381
            nullptr)
1382
        != SQLITE_OK)
1✔
1383
    {
1384
        log_error("could not prepare text_bookmarks replace statement -- %s",
×
1385
                  sqlite3_errmsg(db));
1386
        return;
×
1387
    }
1388

1389
    for (const auto& fvs : tss.get_file_states()) {
2✔
1390
        auto& lf = fvs->fvs_file;
1✔
1391
        if (lf == nullptr || lf->size() == 0) {
1✔
1392
            continue;
×
1393
        }
1394

1395
        auto file_path = lf->get_path_for_key().string();
1✔
1396
        auto line_count = static_cast<int>(lf->size());
1✔
1397

1398
        static const bookmark_type_t* SAVE_TYPES[] = {
1399
            &textview_curses::BM_USER,
1400
            &textview_curses::BM_STICKY,
1401
        };
1402

1403
        for (const auto* bm_type : SAVE_TYPES) {
3✔
1404
            auto& bv = fvs->fvs_content_marks[bm_type];
2✔
1405
            if (bv.empty()) {
2✔
1406
                continue;
1✔
1407
            }
1408

1409
            for (const auto& vl : bv.bv_tree) {
2✔
1410
                if (static_cast<int>(vl) >= line_count) {
1✔
1411
                    continue;
×
1412
                }
1413

1414
                auto line_iter = lf->begin() + static_cast<int>(vl);
1✔
1415
                auto fr = lf->get_file_range(line_iter, false);
1✔
1416
                auto read_result = lf->read_range(fr);
1✔
1417

1418
                if (read_result.isErr()) {
1✔
1419
                    continue;
×
1420
                }
1421

1422
                auto line_hash
1423
                    = read_result
1424
                          .map([](auto sbr) {
2✔
1425
                              return hasher()
2✔
1426
                                  .update(sbr.get_data(), sbr.length())
1✔
1427
                                  .to_string();
2✔
1428
                          })
1429
                          .unwrap();
1✔
1430

1431
                sqlite3_clear_bindings(stmt.in());
1✔
1432
                bind_to_sqlite(stmt.in(), 1, file_path);
1✔
1433
                sqlite3_bind_int(stmt.in(), 2, static_cast<int>(vl));
1✔
1434
                bind_to_sqlite(stmt.in(), 3, line_hash);
1✔
1435
                bind_to_sqlite(stmt.in(), 4, bm_type->get_name());
1✔
1436
                sqlite3_bind_int64(stmt.in(), 5, lnav_data.ld_session_time);
1✔
1437

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

1444
                sqlite3_reset(stmt.in());
1✔
1445
            }
1✔
1446
        }
1447
    }
1✔
1448
}
1✔
1449

1450
static void
1451
load_text_bookmarks(sqlite3* db)
29✔
1452
{
1453
    static const char* const TEXT_BOOKMARK_STMT = R"(
1454
        SELECT line_number, line_hash, mark_type, session_time,
1455
               session_time=? AS same_session
1456
        FROM text_bookmarks2
1457
        WHERE file_path = ?
1458
        ORDER BY same_session DESC, session_time DESC
1459
    )";
1460

1461
    auto& tss = lnav_data.ld_text_source;
29✔
1462
    auto& tc = lnav_data.ld_views[LNV_TEXT];
29✔
1463

1464
    if (tss.empty()) {
29✔
1465
        return;
28✔
1466
    }
1467

1468
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1469
    if (sqlite3_prepare_v2(db, TEXT_BOOKMARK_STMT, -1, stmt.out(), nullptr)
1✔
1470
        != SQLITE_OK)
1✔
1471
    {
1472
        log_error("could not prepare text_bookmarks select -- %s",
×
1473
                  sqlite3_errmsg(db));
1474
        return;
×
1475
    }
1476

1477
    for (const auto& fvs : tss.get_file_states()) {
2✔
1478
        auto& lf = fvs->fvs_file;
1✔
1479
        if (lf == nullptr || lf->size() == 0) {
1✔
1480
            continue;
×
1481
        }
1482

1483
        auto file_path = lf->get_path_for_key().string();
1✔
1484
        auto line_count = static_cast<int>(lf->size());
1✔
1485
        sqlite3_reset(stmt.in());
1✔
1486
        sqlite3_clear_bindings(stmt.in());
1✔
1487
        sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
1✔
1488
        bind_to_sqlite(stmt.in(), 2, file_path);
1✔
1489

1490
        size_t mark_count = 0;
1✔
1491
        int64_t last_session_time = -1;
1✔
1492
        bool done = false;
1✔
1493
        while (!done) {
3✔
1494
            auto rc = sqlite3_step(stmt.in());
2✔
1495

1496
            switch (rc) {
2✔
1497
                case SQLITE_OK:
1✔
1498
                case SQLITE_DONE:
1499
                    done = true;
1✔
1500
                    break;
1✔
1501

1502
                case SQLITE_ROW: {
1✔
1503
                    auto line_number = sqlite3_column_int(stmt.in(), 0);
1✔
1504
                    const auto* stored_hash
1505
                        = (const char*) sqlite3_column_text(stmt.in(), 1);
1✔
1506
                    const auto* mark_type
1507
                        = (const char*) sqlite3_column_text(stmt.in(), 2);
1✔
1508
                    auto session_time = sqlite3_column_int64(stmt.in(), 3);
1✔
1509

1510
                    if (last_session_time == -1) {
1✔
1511
                        last_session_time = session_time;
1✔
NEW
1512
                    } else if (last_session_time != session_time) {
×
NEW
1513
                        done = true;
×
NEW
1514
                        continue;
×
1515
                    }
1516

1517
                    if (line_number >= line_count) {
1✔
1518
                        continue;
×
1519
                    }
1520

1521
                    auto bm_type_opt = bookmark_type_t::find_type(mark_type);
1✔
1522
                    if (!bm_type_opt) {
1✔
1523
                        continue;
×
1524
                    }
1525

1526
                    auto line_iter = lf->begin() + line_number;
1✔
1527
                    auto fr = lf->get_file_range(line_iter, false);
1✔
1528
                    auto read_result = lf->read_range(fr);
1✔
1529

1530
                    if (read_result.isErr()) {
1✔
1531
                        continue;
×
1532
                    }
1533

1534
                    auto line_hash
1535
                        = read_result
1536
                              .map([](auto sbr) {
2✔
1537
                                  return hasher()
2✔
1538
                                      .update(sbr.get_data(), sbr.length())
1✔
1539
                                      .to_string();
2✔
1540
                              })
1541
                              .unwrap();
1✔
1542

1543
                    if (line_hash != stored_hash) {
1✔
1544
                        log_warning("text bookmark hash mismatch at %s:%d",
×
1545
                                    file_path.c_str(),
1546
                                    line_number);
1547
                        continue;
×
1548
                    }
1549

1550
                    auto vl = vis_line_t(line_number);
1✔
1551
                    fvs->fvs_bookmarks[bm_type_opt.value()].insert_once(vl);
1✔
1552
                    fvs->fvs_content_marks[bm_type_opt.value()].insert_once(
1✔
1553
                        static_cast<uint32_t>(line_number));
1554
                    mark_count += 1;
1✔
1555
                    break;
1✔
1556
                }
2✔
1557

1558
                default:
×
1559
                    log_error("text bookmark select error: %d -- %s",
×
1560
                              rc,
1561
                              sqlite3_errmsg(db));
1562
                    done = true;
×
1563
                    break;
×
1564
            }
1565
        }
1566
        log_debug(
1✔
1567
            "loaded %zu text bookmarks from %s", mark_count, file_path.c_str());
1568
    }
1✔
1569

1570
    // Copy the front file's bookmarks into the textview
1571
    if (!tss.get_file_states().empty()) {
1✔
1572
        auto& front = tss.get_file_states().front();
1✔
1573
        tc.get_bookmarks()[&textview_curses::BM_USER]
1✔
1574
            = front->fvs_bookmarks[&textview_curses::BM_USER];
2✔
1575
        tc.get_bookmarks()[&textview_curses::BM_STICKY]
1✔
1576
            = front->fvs_bookmarks[&textview_curses::BM_STICKY];
2✔
1577
    }
1578

1579
    tc.reload_data();
1✔
1580
}
1✔
1581

1582
static void
1583
save_timeline_bookmarks(sqlite3* db)
28✔
1584
{
1585
    auto& tc = lnav_data.ld_views[LNV_TIMELINE];
28✔
1586
    auto* tss = static_cast<timeline_source*>(tc.get_sub_source());
28✔
1587

1588
    if (tss == nullptr) {
28✔
NEW
1589
        return;
×
1590
    }
1591

1592
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
1593

1594
    if (sqlite3_prepare_v2(db,
28✔
1595
                           "DELETE FROM timeline_bookmarks"
1596
                           " WHERE session_time = ?",
1597
                           -1,
1598
                           stmt.out(),
1599
                           nullptr)
1600
        != SQLITE_OK)
28✔
1601
    {
NEW
1602
        log_error("could not prepare timeline_bookmarks delete -- %s",
×
1603
                  sqlite3_errmsg(db));
NEW
1604
        return;
×
1605
    }
1606
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
28✔
1607
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
NEW
1608
        log_error("could not execute timeline_bookmarks delete -- %s",
×
1609
                  sqlite3_errmsg(db));
NEW
1610
        return;
×
1611
    }
1612

1613
    if (sqlite3_prepare_v2(db,
28✔
1614
                           "REPLACE INTO timeline_bookmarks"
1615
                           " (row_type, row_name, mark_type, session_time)"
1616
                           " VALUES (?, ?, ?, ?)",
1617
                           -1,
1618
                           stmt.out(),
1619
                           nullptr)
1620
        != SQLITE_OK)
28✔
1621
    {
NEW
1622
        log_error(
×
1623
            "could not prepare timeline_bookmarks replace statement -- %s",
1624
            sqlite3_errmsg(db));
NEW
1625
        return;
×
1626
    }
1627

1628
    static const bookmark_type_t* SAVE_TYPES[] = {
1629
        &textview_curses::BM_USER,
1630
        &textview_curses::BM_STICKY,
1631
    };
1632

1633
    for (const auto* bm_type : SAVE_TYPES) {
84✔
1634
        const auto& bv = tc.get_bookmarks()[bm_type];
56✔
1635
        if (bv.empty()) {
56✔
1636
            continue;
54✔
1637
        }
1638

1639
        for (const auto& vl : bv.bv_tree) {
4✔
1640
            auto line = static_cast<size_t>(vl);
2✔
1641
            if (line >= tss->ts_time_order.size()) {
2✔
NEW
1642
                continue;
×
1643
            }
1644

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

1647
            sqlite3_clear_bindings(stmt.in());
2✔
1648
            bind_to_sqlite(
2✔
1649
                stmt.in(), 1, timeline_source::row_type_to_string(row.or_type));
2✔
1650
            bind_to_sqlite(stmt.in(), 2, row.or_name.to_string());
2✔
1651
            bind_to_sqlite(stmt.in(), 3, bm_type->get_name());
2✔
1652
            sqlite3_bind_int64(stmt.in(), 4, lnav_data.ld_session_time);
2✔
1653

1654
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
NEW
1655
                log_error("could not execute timeline_bookmarks insert -- %s",
×
1656
                          sqlite3_errmsg(db));
NEW
1657
                return;
×
1658
            }
1659

1660
            sqlite3_reset(stmt.in());
2✔
1661
        }
1662
    }
1663
}
28✔
1664

1665
static void
1666
load_timeline_bookmarks(sqlite3* db)
29✔
1667
{
1668
    static const char* const TIMELINE_BOOKMARK_STMT = R"(
1669
        SELECT row_type, row_name, mark_type, session_time,
1670
               session_time=? AS same_session
1671
        FROM timeline_bookmarks
1672
        ORDER BY same_session DESC, session_time DESC
1673
    )";
1674

1675
    auto* tss = static_cast<timeline_source*>(
1676
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
29✔
1677

1678
    if (tss == nullptr) {
29✔
NEW
1679
        return;
×
1680
    }
1681

1682
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
29✔
1683
    if (sqlite3_prepare_v2(db, TIMELINE_BOOKMARK_STMT, -1, stmt.out(), nullptr)
29✔
1684
        != SQLITE_OK)
29✔
1685
    {
NEW
1686
        log_debug("could not prepare timeline_bookmarks select -- %s",
×
1687
                  sqlite3_errmsg(db));
NEW
1688
        return;
×
1689
    }
1690

1691
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
29✔
1692

1693
    int64_t last_session_time = -1;
29✔
1694
    bool done = false;
29✔
1695
    while (!done) {
60✔
1696
        auto rc = sqlite3_step(stmt.in());
31✔
1697

1698
        switch (rc) {
31✔
1699
            case SQLITE_OK:
29✔
1700
            case SQLITE_DONE:
1701
                done = true;
29✔
1702
                break;
29✔
1703

1704
            case SQLITE_ROW: {
2✔
1705
                auto* row_type_str
1706
                    = (const char*) sqlite3_column_text(stmt.in(), 0);
2✔
1707
                auto* row_name
1708
                    = (const char*) sqlite3_column_text(stmt.in(), 1);
2✔
1709
                auto* mark_type
1710
                    = (const char*) sqlite3_column_text(stmt.in(), 2);
2✔
1711
                auto session_time = sqlite3_column_int64(stmt.in(), 3);
2✔
1712

1713
                if (last_session_time == -1) {
2✔
1714
                    last_session_time = session_time;
2✔
NEW
1715
                } else if (last_session_time != session_time) {
×
NEW
1716
                    done = true;
×
NEW
1717
                    continue;
×
1718
                }
1719

1720
                if (row_type_str == nullptr || row_name == nullptr
2✔
1721
                    || mark_type == nullptr)
2✔
1722
                {
NEW
1723
                    continue;
×
1724
                }
1725

1726
                auto bm_type_opt = bookmark_type_t::find_type(mark_type);
2✔
1727
                if (!bm_type_opt) {
2✔
NEW
1728
                    continue;
×
1729
                }
1730

1731
                auto rt_opt = timeline_source::row_type_from_string(
2✔
1732
                    row_type_str);
1733
                if (!rt_opt) {
2✔
NEW
1734
                    continue;
×
1735
                }
1736

1737
                tss->ts_pending_bookmarks.emplace_back(
4✔
1738
                    timeline_source::pending_bookmark{
4✔
1739
                        rt_opt.value(),
2✔
1740
                        row_name,
1741
                        bm_type_opt.value(),
2✔
1742
                    });
1743
                break;
2✔
1744
            }
1745

NEW
1746
            default:
×
NEW
1747
                log_error("timeline bookmark select error: %d -- %s",
×
1748
                          rc,
1749
                          sqlite3_errmsg(db));
NEW
1750
                done = true;
×
NEW
1751
                break;
×
1752
        }
1753
    }
1754
}
29✔
1755

1756
static void
1757
save_time_bookmarks()
28✔
1758
{
1759
    auto_sqlite3 db;
28✔
1760
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
28✔
1761
    auto_mem<char, sqlite3_free> errmsg;
28✔
1762
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
28✔
1763

1764
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
28✔
1765
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1766
        return;
×
1767
    }
1768

1769
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
28✔
1770
        != SQLITE_OK)
28✔
1771
    {
1772
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1773
        return;
×
1774
    }
1775

1776
    if (sqlite3_exec(
28✔
1777
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1778
        != SQLITE_OK)
28✔
1779
    {
1780
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1781
        return;
×
1782
    }
1783

1784
    {
1785
        static const char* UPDATE_NETLOCS_STMT
1786
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1787

1788
        std::set<std::string> netlocs;
28✔
1789

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

1793
        if (sqlite3_prepare_v2(
28✔
1794
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1795
            != SQLITE_OK)
28✔
1796
        {
1797
            log_error("could not prepare recent_netlocs statement -- %s",
×
1798
                      sqlite3_errmsg(db));
1799
            return;
×
1800
        }
1801

1802
        for (const auto& netloc : netlocs) {
28✔
1803
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1804

1805
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1806
                log_error("could not execute bookmark insert statement -- %s",
×
1807
                          sqlite3_errmsg(db));
1808
                return;
×
1809
            }
1810

1811
            sqlite3_reset(stmt.in());
×
1812
        }
1813
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
28✔
1814
    }
28✔
1815

1816
    auto& lss = lnav_data.ld_log_source;
28✔
1817
    auto& bm = lss.get_user_bookmarks();
28✔
1818

1819
    if (sqlite3_prepare_v2(db.in(),
28✔
1820
                           "DELETE FROM bookmarks WHERE "
1821
                           " log_time = ? and log_format = ? and log_hash = ? "
1822
                           " and session_time = ?",
1823
                           -1,
1824
                           stmt.out(),
1825
                           nullptr)
1826
        != SQLITE_OK)
28✔
1827
    {
1828
        log_error("could not prepare bookmark delete statement -- %s",
×
1829
                  sqlite3_errmsg(db));
1830
        return;
×
1831
    }
1832

1833
    for (auto& marked_session_line : marked_session_lines) {
30✔
1834
        sqlite3_clear_bindings(stmt.in());
2✔
1835

1836
        if (bind_values(stmt,
4✔
1837
                        marked_session_line.sl_time,
1838
                        marked_session_line.sl_format_name,
1839
                        marked_session_line.sl_line_hash,
2✔
1840
                        lnav_data.ld_session_time)
1841
            != SQLITE_OK)
2✔
1842
        {
1843
            continue;
×
1844
        }
1845

1846
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
2✔
1847
            log_error("could not execute bookmark insert statement -- %s",
×
1848
                      sqlite3_errmsg(db));
1849
            return;
×
1850
        }
1851

1852
        sqlite3_reset(stmt.in());
2✔
1853
    }
1854

1855
    marked_session_lines.clear();
28✔
1856

1857
    if (sqlite3_prepare_v2(db.in(),
28✔
1858
                           "REPLACE INTO bookmarks"
1859
                           " (log_time, log_format, log_hash, session_time, "
1860
                           "part_name, comment, tags, annotations, log_opid,"
1861
                           " sticky)"
1862
                           " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1863
                           -1,
1864
                           stmt.out(),
1865
                           nullptr)
1866
        != SQLITE_OK)
28✔
1867
    {
1868
        log_error("could not prepare bookmark replace statement -- %s",
×
1869
                  sqlite3_errmsg(db));
1870
        return;
×
1871
    }
1872

1873
    {
1874
        for (auto file_iter = lnav_data.ld_log_source.begin();
28✔
1875
             file_iter != lnav_data.ld_log_source.end();
56✔
1876
             ++file_iter)
28✔
1877
        {
1878
            auto lf = (*file_iter)->get_file();
28✔
1879

1880
            if (lf == nullptr) {
28✔
1881
                continue;
×
1882
            }
1883
            if (lf->size() == 0) {
28✔
1884
                continue;
×
1885
            }
1886

1887
            content_line_t base_content_line;
28✔
1888
            base_content_line = lss.get_file_base_content_line(file_iter);
28✔
1889
            base_content_line
1890
                = content_line_t(base_content_line + lf->size() - 1);
28✔
1891

1892
            if (!bind_line(db.in(),
28✔
1893
                           stmt.in(),
1894
                           base_content_line,
1895
                           lnav_data.ld_session_time))
28✔
1896
            {
1897
                continue;
×
1898
            }
1899

1900
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
28✔
1901
                log_error("could not bind log hash -- %s",
×
1902
                          sqlite3_errmsg(db.in()));
1903
                return;
×
1904
            }
1905

1906
            sqlite3_bind_int(stmt.in(), 10, 0);
28✔
1907

1908
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
1909
                log_error("could not execute bookmark insert statement -- %s",
×
1910
                          sqlite3_errmsg(db));
1911
                return;
×
1912
            }
1913

1914
            sqlite3_reset(stmt.in());
28✔
1915
        }
28✔
1916
    }
1917

1918
    save_user_bookmarks(db.in(),
28✔
1919
                        stmt.in(),
1920
                        bm[&textview_curses::BM_USER],
1921
                        bm[&textview_curses::BM_STICKY]);
1922
    for (const auto& ldd : lss) {
56✔
1923
        auto* lf = ldd->get_file_ptr();
28✔
1924
        if (lf == nullptr) {
28✔
1925
            continue;
×
1926
        }
1927

1928
        save_meta_bookmarks(db.in(), stmt.in(), lf);
28✔
1929
    }
1930

1931
    if (sqlite3_prepare_v2(db.in(),
28✔
1932
                           "DELETE FROM time_offset WHERE "
1933
                           " log_time = ? and log_format = ? and log_hash = ? "
1934
                           " and session_time = ?",
1935
                           -1,
1936
                           stmt.out(),
1937
                           NULL)
1938
        != SQLITE_OK)
28✔
1939
    {
1940
        log_error("could not prepare time_offset delete statement -- %s",
×
1941
                  sqlite3_errmsg(db));
1942
        return;
×
1943
    }
1944

1945
    for (auto& offset_session_line : offset_session_lines) {
29✔
1946
        sqlite3_clear_bindings(stmt.in());
1✔
1947

1948
        if (bind_values(stmt,
2✔
1949
                        offset_session_line.sl_time,
1950
                        offset_session_line.sl_format_name,
1951
                        offset_session_line.sl_line_hash,
1✔
1952
                        lnav_data.ld_session_time)
1953
            != SQLITE_OK)
1✔
1954
        {
1955
            continue;
×
1956
        }
1957

1958
        if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1959
            log_error("could not execute bookmark insert statement -- %s",
×
1960
                      sqlite3_errmsg(db));
1961
            return;
×
1962
        }
1963

1964
        sqlite3_reset(stmt.in());
1✔
1965
    }
1966

1967
    offset_session_lines.clear();
28✔
1968

1969
    if (sqlite3_prepare_v2(db.in(),
28✔
1970
                           "REPLACE INTO time_offset"
1971
                           " (log_time, log_format, log_hash, session_time, "
1972
                           "offset_sec, offset_usec)"
1973
                           " VALUES (?, ?, ?, ?, ?, ?)",
1974
                           -1,
1975
                           stmt.out(),
1976
                           nullptr)
1977
        != SQLITE_OK)
28✔
1978
    {
1979
        log_error("could not prepare time_offset replace statement -- %s",
×
1980
                  sqlite3_errmsg(db));
1981
        return;
×
1982
    }
1983

1984
    {
1985
        for (auto file_iter = lnav_data.ld_log_source.begin();
28✔
1986
             file_iter != lnav_data.ld_log_source.end();
56✔
1987
             ++file_iter)
28✔
1988
        {
1989
            auto lf = (*file_iter)->get_file();
28✔
1990
            if (lf == nullptr) {
28✔
1991
                continue;
×
1992
            }
1993
            if (lf->size() == 0) {
28✔
1994
                continue;
×
1995
            }
1996

1997
            if (bind_values(stmt,
84✔
1998
                            lf->original_line_time(lf->begin()),
1999
                            lf->get_format()->get_name(),
56✔
2000
                            lf->get_content_id(),
28✔
2001
                            lnav_data.ld_session_time)
2002
                != SQLITE_OK)
28✔
2003
            {
2004
                continue;
×
2005
            }
2006

2007
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
28✔
2008
                log_error("could not bind log hash -- %s",
×
2009
                          sqlite3_errmsg(db.in()));
2010
                return;
×
2011
            }
2012

2013
            if (sqlite3_bind_null(stmt.in(), 6) != SQLITE_OK) {
28✔
2014
                log_error("could not bind log hash -- %s",
×
2015
                          sqlite3_errmsg(db.in()));
2016
                return;
×
2017
            }
2018

2019
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
28✔
2020
                log_error("could not execute bookmark insert statement -- %s",
×
2021
                          sqlite3_errmsg(db));
2022
                return;
×
2023
            }
2024

2025
            sqlite3_reset(stmt.in());
28✔
2026
        }
28✔
2027
    }
2028

2029
    for (const auto& ls : lss) {
56✔
2030
        if (ls->get_file() == nullptr) {
28✔
2031
            continue;
25✔
2032
        }
2033

2034
        const auto lf = ls->get_file();
28✔
2035
        if (!lf->is_time_adjusted()) {
28✔
2036
            continue;
25✔
2037
        }
2038

2039
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
2040
        auto offset = lf->get_time_offset();
3✔
2041

2042
        bind_values(stmt.in(),
6✔
2043
                    lf->original_line_time(line_iter),
2044
                    lf->get_format()->get_name(),
6✔
2045
                    lf->get_content_id(),
3✔
2046
                    lnav_data.ld_session_time,
2047
                    offset.tv_sec,
2048
                    offset.tv_usec);
2049

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

2056
        sqlite3_reset(stmt.in());
3✔
2057
    }
28✔
2058

2059
    log_info("saved %d log bookmarks", sqlite3_changes(db.in()));
28✔
2060
    save_text_bookmarks(db.in());
28✔
2061
    log_info("saved %d text bookmarks", sqlite3_changes(db.in()));
28✔
2062
    save_timeline_bookmarks(db.in());
28✔
2063
    log_info("saved %d timeline bookmarks", sqlite3_changes(db.in()));
28✔
2064

2065
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
28✔
2066
        != SQLITE_OK)
28✔
2067
    {
2068
        log_error("unable to begin transaction -- %s", errmsg.in());
×
2069
        return;
×
2070
    }
2071

2072
    if (sqlite3_exec(db.in(), BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
28✔
2073
        != SQLITE_OK)
28✔
2074
    {
2075
        log_error("unable to delete old bookmarks -- %s", errmsg.in());
×
2076
        return;
×
2077
    }
2078
    auto bookmark_changes = sqlite3_changes(db.in());
28✔
2079
    if (bookmark_changes > 0) {
28✔
2080
        log_info("deleted %d old bookmarks", bookmark_changes);
×
2081
    }
2082

2083
    if (sqlite3_exec(db.in(), NETLOC_LRU_STMT, nullptr, nullptr, errmsg.out())
28✔
2084
        != SQLITE_OK)
28✔
2085
    {
2086
        log_error("unable to delete old netlocs -- %s", errmsg.in());
×
2087
        return;
×
2088
    }
2089
    auto netloc_changes = sqlite3_changes(db.in());
28✔
2090
    if (netloc_changes > 0) {
28✔
2091
        log_info("deleted %d old netlocs", netloc_changes);
×
2092
    }
2093

2094
    if (sqlite3_exec(
28✔
2095
            db.in(), TEXT_BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
2096
        != SQLITE_OK)
28✔
2097
    {
2098
        log_error("unable to delete old text bookmarks -- %s", errmsg.in());
×
2099
        return;
×
2100
    }
2101
    auto text_bm_changes = sqlite3_changes(db.in());
28✔
2102
    if (text_bm_changes > 0) {
28✔
2103
        log_info("deleted %d old text bookmarks", text_bm_changes);
×
2104
    }
2105

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

2123
static void
2124
save_session_with_id(const std::string& session_id)
27✔
2125
{
2126
    auto_mem<FILE> file(fclose);
27✔
2127
    yajl_gen handle = nullptr;
27✔
2128

2129
    /* TODO: save the last search query */
2130

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

2133
    auto view_base_name
2134
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
54✔
2135
                      session_id,
2136
                      lnav_data.ld_session_time,
2137
                      getppid());
27✔
2138
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
27✔
2139
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
27✔
2140

2141
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
27✔
2142
        perror("Unable to open session file");
×
2143
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
27✔
2144
        perror("Unable to create yajl_gen object");
×
2145
    } else {
2146
        yajl_gen_config(
27✔
2147
            handle, yajl_gen_print_callback, yajl_writer, file.in());
2148

2149
        {
2150
            yajlpp_map root_map(handle);
27✔
2151

2152
            root_map.gen("save-time");
27✔
2153
            root_map.gen((long long) time(nullptr));
27✔
2154

2155
            root_map.gen("time-offset");
27✔
2156
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
27✔
2157

2158
            root_map.gen("files");
27✔
2159

2160
            {
2161
                yajlpp_array file_list(handle);
27✔
2162

2163
                for (auto& ld_file_name :
27✔
2164
                     lnav_data.ld_active_files.fc_file_names)
83✔
2165
                {
2166
                    file_list.gen(ld_file_name.first);
29✔
2167
                }
2168
            }
27✔
2169

2170
            root_map.gen("file-states");
27✔
2171

2172
            {
2173
                yajlpp_map file_states(handle);
27✔
2174

2175
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
56✔
2176
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
29✔
2177

2178
                    file_states.gen(lf->get_filename().native());
29✔
2179

2180
                    {
2181
                        yajlpp_map file_state(handle);
29✔
2182

2183
                        file_state.gen("visible");
29✔
2184
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
29✔
2185
                    }
29✔
2186
                }
2187
            }
27✔
2188

2189
            root_map.gen("views");
27✔
2190

2191
            {
2192
                yajlpp_map top_view_map(handle);
27✔
2193

2194
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
270✔
2195
                    auto& tc = lnav_data.ld_views[lpc];
243✔
2196
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
243✔
2197

2198
                    top_view_map.gen(lnav_view_strings[lpc]);
243✔
2199

2200
                    yajlpp_map view_map(handle);
243✔
2201

2202
                    view_map.gen("top_line");
243✔
2203

2204
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
243✔
2205
                        view_map.gen(-1LL);
228✔
2206
                    } else {
2207
                        view_map.gen((long long) tc.get_top());
15✔
2208
                    }
2209

2210
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
302✔
2211
                        && tc.get_inner_height() > 0_vl
4✔
2212
                        && tc.get_selection() != tc.get_inner_height() - 1)
302✔
2213
                    {
2214
                        auto sel = tc.get_selection();
3✔
2215
                        if (sel) {
3✔
2216
                            view_map.gen("focused_line");
3✔
2217
                            view_map.gen((long long) sel.value());
3✔
2218

2219
                            if (ta != nullptr) {
3✔
2220
                                auto anchor_opt
2221
                                    = ta->anchor_for_row(sel.value());
1✔
2222
                                if (anchor_opt) {
1✔
2223
                                    view_map.gen("anchor");
1✔
2224
                                    view_map.gen(anchor_opt.value());
1✔
2225
                                }
2226
                            }
1✔
2227
                        }
2228
                    }
2229

2230
                    view_map.gen("search");
243✔
2231
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
243✔
2232

2233
                    view_map.gen("word_wrap");
243✔
2234
                    view_map.gen(tc.get_word_wrap());
243✔
2235

2236
                    auto* tss = tc.get_sub_source();
243✔
2237
                    if (tss == nullptr) {
243✔
2238
                        continue;
54✔
2239
                    }
2240

2241
                    view_map.gen("filtering");
189✔
2242
                    view_map.gen(tss->tss_apply_filters);
189✔
2243

2244
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
189✔
2245
                        view_map.gen("min_level");
×
2246
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
2247
                    }
2248

2249
                    view_map.gen("commands");
189✔
2250
                    yajlpp_array cmd_array(handle);
189✔
2251

2252
                    tss->add_commands_for_session(
189✔
2253
                        [&](auto& cmd) { cmd_array.gen(cmd); });
203✔
2254
                }
243✔
2255
            }
27✔
2256
        }
27✔
2257

2258
        yajl_gen_clear(handle);
27✔
2259
        yajl_gen_free(handle);
27✔
2260

2261
        fclose(file.release());
27✔
2262

2263
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
27✔
2264

2265
        log_info("Saved session: %s", view_file_name.c_str());
27✔
2266
    }
2267
}
27✔
2268

2269
void
2270
save_session()
28✔
2271
{
2272
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
28✔
2273
        log_info("secure mode is enabled, not saving session");
×
2274
        return;
×
2275
    }
2276

2277
    static auto op = lnav_operation{"save_session"};
28✔
2278

2279
    auto op_guard = lnav_opid_guard::internal(op);
28✔
2280

2281
    log_debug("BEGIN save_session");
28✔
2282
    save_time_bookmarks();
28✔
2283

2284
    const auto opt_session_id = compute_session_id();
28✔
2285
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
54✔
2286
    for (const auto& pair : lnav_data.ld_session_id) {
32✔
2287
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
2288
            continue;
3✔
2289
        }
2290
        save_session_with_id(pair.first);
1✔
2291
    }
2292
    log_debug("END save_session");
28✔
2293
}
28✔
2294

2295
void
2296
reset_session()
6✔
2297
{
2298
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
6✔
2299

2300
    save_session();
6✔
2301

2302
    lnav_data.ld_session_time = time(nullptr);
6✔
2303
    session_data.sd_file_states.clear();
6✔
2304

2305
    for (auto& tc : lnav_data.ld_views) {
60✔
2306
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
54✔
2307
        auto& hmap = tc.get_highlights();
54✔
2308
        auto hl_iter = hmap.begin();
54✔
2309

2310
        while (hl_iter != hmap.end()) {
552✔
2311
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
498✔
2312
                ++hl_iter;
498✔
2313
            } else {
2314
                hmap.erase(hl_iter++);
×
2315
            }
2316
        }
2317

2318
        if (ttt != nullptr) {
54✔
2319
            ttt->clear_min_max_row_times();
36✔
2320
        }
2321
    }
2322

2323
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
14✔
2324
        lf->reset_state();
8✔
2325
    }
2326

2327
    lnav_data.ld_log_source.get_breakpoints().clear();
6✔
2328
    lnav_data.ld_log_source.lss_highlighters.clear();
6✔
2329
    lnav_data.ld_log_source.set_force_rebuild();
6✔
2330
    lnav_data.ld_log_source.set_marked_only(false);
6✔
2331
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
6✔
2332
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
12✔
2333
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
6✔
2334
    lnav_data.ld_log_source.clear_bookmark_metadata();
6✔
2335
    rebuild_indexes(std::nullopt);
6✔
2336

2337
    lnav_data.ld_db_row_source.reset_user_state();
6✔
2338

2339
    auto* tss = static_cast<timeline_source*>(
2340
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
6✔
2341
    if (tss != nullptr) {
6✔
2342
        tss->ts_hidden_row_types.clear();
6✔
2343
        tss->ts_pending_bookmarks.clear();
6✔
2344
    }
2345

2346
    for (auto& tc : lnav_data.ld_views) {
60✔
2347
        text_sub_source* tss = tc.get_sub_source();
54✔
2348

2349
        if (tss == nullptr) {
54✔
2350
            continue;
12✔
2351
        }
2352
        tss->get_filters().clear_filters();
42✔
2353
        tss->tss_apply_filters = true;
42✔
2354
        tss->text_filters_changed();
42✔
2355
        tss->text_clear_marks(&textview_curses::BM_USER);
42✔
2356
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
42✔
2357
        tss->text_clear_marks(&textview_curses::BM_META);
42✔
2358
        tc.get_bookmarks()[&textview_curses::BM_META].clear();
42✔
2359
        tc.get_bookmarks()[&textview_curses::BM_STICKY].clear();
42✔
2360
        tc.reload_data();
42✔
2361
    }
2362

2363
    for (auto& fvs : lnav_data.ld_text_source.get_file_states()) {
6✔
2364
        fvs->fvs_bookmarks.clear();
×
2365
    }
2366

2367
    lnav_data.ld_filter_view.reload_data();
6✔
2368
    lnav_data.ld_files_view.reload_data();
6✔
2369
    for (const auto& format : log_format::get_root_formats()) {
468✔
2370
        auto* elf = dynamic_cast<external_log_format*>(format.get());
462✔
2371

2372
        if (elf == nullptr) {
462✔
2373
            continue;
30✔
2374
        }
2375

2376
        bool changed = false;
432✔
2377
        for (const auto& vd : elf->elf_value_defs) {
6,306✔
2378
            if (vd.second->vd_meta.lvm_user_hidden) {
5,874✔
2379
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
2380
                changed = true;
×
2381
            }
2382
        }
2383
        if (changed) {
432✔
2384
            elf->elf_value_defs_state->vds_generation += 1;
×
2385
        }
2386
    }
2387
}
6✔
2388

2389
void
2390
lnav::session::apply_view_commands()
30✔
2391
{
2392
    static auto op = lnav_operation{__FUNCTION__};
30✔
2393

2394
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2395

2396
    log_debug("applying view commands");
30✔
2397
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
300✔
2398
        const auto& vs = session_data.sd_view_states[view_index];
270✔
2399
        auto& tview = lnav_data.ld_views[view_index];
270✔
2400

2401
        lnav::set::small<std::string> curr_cmds;
270✔
2402
        auto* tss = tview.get_sub_source();
270✔
2403
        if (tview.get_sub_source() != nullptr) {
270✔
2404
            tss->tss_apply_filters = vs.vs_filtering;
210✔
2405
            if (vs.vs_min_log_level) {
210✔
2406
                tss->set_min_log_level(vs.vs_min_log_level.value());
×
2407
            }
2408
            tss->add_commands_for_session([&](auto& cmd) {
210✔
2409
                auto cmd_sf = string_fragment::from_str(cmd)
5✔
2410
                                  .split_when(string_fragment::tag1{' '})
5✔
2411
                                  .first;
2412
                curr_cmds.insert(cmd_sf.to_string());
5✔
2413
            });
5✔
2414
        }
2415
        for (const auto& cmdline : vs.vs_commands) {
280✔
2416
            auto cmdline_sf = string_fragment::from_str(cmdline);
10✔
2417
            auto active = ensure_view(&tview);
10✔
2418
            auto [cmd_sf, _cmdline_rem]
10✔
2419
                = cmdline_sf.split_when(string_fragment::tag1{' '});
10✔
2420
            if (curr_cmds.contains(cmd_sf.to_string())) {
10✔
2421
                log_debug("view %s command '%.*s' already active",
3✔
2422
                          tview.get_title().c_str(),
2423
                          cmd_sf.length(),
2424
                          cmd_sf.data());
2425
                continue;
3✔
2426
            }
2427
            auto exec_cmd_res
2428
                = execute_command(lnav_data.ld_exec_context, cmdline);
7✔
2429
            if (exec_cmd_res.isOk()) {
7✔
2430
                log_info("Result: %s", exec_cmd_res.unwrap().c_str());
7✔
2431
            } else {
2432
                log_error("Result: %s",
×
2433
                          exec_cmd_res.unwrapErr()
2434
                              .to_attr_line()
2435
                              .get_string()
2436
                              .c_str());
2437
            }
2438
            if (!active) {
7✔
2439
                lnav_data.ld_view_stack.pop_back();
×
2440
                lnav_data.ld_view_stack.top() | [](auto* tc) {
×
2441
                    // XXX
2442
                    if (tc == &lnav_data.ld_views[LNV_TIMELINE]) {
×
2443
                        auto tss = tc->get_sub_source();
×
2444
                        tss->text_filters_changed();
×
2445
                        tc->reload_data();
×
2446
                    }
2447
                };
2448
            }
2449
        }
7✔
2450
    }
270✔
2451
}
30✔
2452

2453
void
2454
lnav::session::restore_view_states()
30✔
2455
{
2456
    static auto op = lnav_operation{__FUNCTION__};
30✔
2457

2458
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2459

2460
    log_debug("restoring view states");
30✔
2461
    for (size_t view_index = 0; view_index < LNV__MAX; view_index++) {
300✔
2462
        const auto& vs = session_data.sd_view_states[view_index];
270✔
2463
        auto& tview = lnav_data.ld_views[view_index];
270✔
2464
        auto* ta = dynamic_cast<text_anchors*>(tview.get_sub_source());
270✔
2465

2466
        if (!vs.vs_search.empty()) {
270✔
2467
            tview.execute_search(vs.vs_search);
×
2468
            tview.set_follow_search_for(-1, {});
×
2469
        }
2470
        tview.set_word_wrap(vs.vs_word_wrap);
270✔
2471
        auto has_loc = tview.get_selection().has_value();
270✔
2472
        if (!has_loc && vs.vs_top >= 0
69✔
2473
            && (view_index == LNV_LOG || tview.get_top() == 0_vl
339✔
2474
                || tview.get_top() == tview.get_top_for_last_row()))
270✔
2475
        {
2476
            log_info("restoring %s view top: %d",
18✔
2477
                     lnav_view_strings[view_index].data(),
2478
                     (int) vs.vs_top);
2479
            tview.set_top(vis_line_t(vs.vs_top), true);
18✔
2480
        }
2481
        if (!has_loc && vs.vs_selection) {
270✔
2482
            log_info("restoring %s view selection: %d",
2✔
2483
                     lnav_view_strings[view_index].data(),
2484
                     (int) vs.vs_selection.value_or(-1_vl));
2485
            tview.set_selection(vis_line_t(vs.vs_selection.value()));
2✔
2486
        }
2487
        auto sel = tview.get_selection();
270✔
2488
        if (!has_loc && sel && ta != nullptr && vs.vs_anchor
69✔
2489
            && !vs.vs_anchor->empty())
339✔
2490
        {
2491
            auto curr_anchor = ta->anchor_for_row(sel.value());
×
2492

2493
            if (!curr_anchor || curr_anchor.value() != vs.vs_anchor.value()) {
×
2494
                log_info("%s view anchor mismatch %s != %s",
×
2495
                         lnav_view_strings[view_index].data(),
2496
                         curr_anchor.value_or("").c_str(),
2497
                         vs.vs_anchor.value().c_str());
2498

2499
                auto row_opt = ta->row_for_anchor(vs.vs_anchor.value());
×
2500
                if (row_opt) {
×
2501
                    tview.set_selection(row_opt.value());
×
2502
                }
2503
            }
2504
        }
2505
        sel = tview.get_selection();
270✔
2506
        if (!sel) {
270✔
2507
            auto height = tview.get_inner_height();
69✔
2508
            if (height == 0) {
69✔
2509
            } else if (view_index == LNV_TEXT) {
1✔
2510
                auto lf = lnav_data.ld_text_source.current_file();
×
2511
                if (lf != nullptr) {
×
2512
                    switch (lf->get_text_format().value_or(
×
2513
                        text_format_t::TF_BINARY))
×
2514
                    {
2515
                        case text_format_t::TF_PLAINTEXT:
×
2516
                        case text_format_t::TF_LOG: {
2517
                            if (height > 0_vl) {
×
2518
                                tview.set_selection(height - 1_vl);
×
2519
                            }
2520
                            break;
×
2521
                        }
2522
                        default:
×
2523
                            tview.set_selection(0_vl);
×
2524
                            break;
×
2525
                    }
2526
                }
2527
            } else if (view_index == LNV_LOG) {
1✔
2528
                tview.set_selection(height - 1_vl);
×
2529
            } else {
2530
                tview.set_selection(0_vl);
1✔
2531
            }
2532
        }
2533
        log_info("%s view actual top/selection: %d/%d",
270✔
2534
                 lnav_view_strings[view_index].data(),
2535
                 (int) tview.get_top(),
2536
                 (int) tview.get_selection().value_or(-1_vl));
2537
    }
2538
}
30✔
2539

2540
void
2541
lnav::session::regex101::insert_entry(const lnav::session::regex101::entry& ei)
×
2542
{
2543
    constexpr const char* STMT = R"(
×
2544
       INSERT INTO regex101_entries
2545
          (format_name, regex_name, permalink, delete_code)
2546
          VALUES (?, ?, ?, ?);
2547
)";
2548

2549
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2550
    auto_sqlite3 db;
×
2551

2552
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2553
        return;
×
2554
    }
2555

2556
    auto_mem<char, sqlite3_free> errmsg;
×
2557
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2558
        != SQLITE_OK)
×
2559
    {
2560
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2561
        return;
×
2562
    }
2563

2564
    auto prep_res = prepare_stmt(db.in(),
2565
                                 STMT,
2566
                                 ei.re_format_name,
×
2567
                                 ei.re_regex_name,
×
2568
                                 ei.re_permalink,
×
2569
                                 ei.re_delete_code);
×
2570

2571
    if (prep_res.isErr()) {
×
2572
        return;
×
2573
    }
2574

2575
    auto ps = prep_res.unwrap();
×
2576

2577
    ps.execute();
×
2578
}
2579

2580
template<>
2581
struct from_sqlite<lnav::session::regex101::entry> {
2582
    inline lnav::session::regex101::entry operator()(int argc,
2583
                                                     sqlite3_value** argv,
2584
                                                     int argi)
2585
    {
2586
        return {
2587
            from_sqlite<std::string>()(argc, argv, argi + 0),
×
2588
            from_sqlite<std::string>()(argc, argv, argi + 1),
×
2589
            from_sqlite<std::string>()(argc, argv, argi + 2),
×
2590
            from_sqlite<std::string>()(argc, argv, argi + 3),
×
2591
        };
2592
    }
2593
};
2594

2595
Result<std::vector<lnav::session::regex101::entry>, std::string>
2596
lnav::session::regex101::get_entries()
×
2597
{
2598
    constexpr const char* STMT = R"(
×
2599
       SELECT * FROM regex101_entries;
2600
)";
2601

2602
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2603
    auto_sqlite3 db;
×
2604

2605
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2606
        return Err(std::string());
×
2607
    }
2608

2609
    auto_mem<char, sqlite3_free> errmsg;
×
2610
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2611
        != SQLITE_OK)
×
2612
    {
2613
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2614
        return Err(std::string(errmsg));
×
2615
    }
2616

2617
    auto ps = TRY(prepare_stmt(db.in(), STMT));
×
2618
    bool done = false;
×
2619
    std::vector<entry> retval;
×
2620

2621
    while (!done) {
×
2622
        auto fetch_res = ps.fetch_row<entry>();
×
2623

2624
        if (fetch_res.is<prepared_stmt::fetch_error>()) {
×
2625
            return Err(fetch_res.get<prepared_stmt::fetch_error>().fe_msg);
×
2626
        }
2627

2628
        fetch_res.match(
×
2629
            [&done](const prepared_stmt::end_of_rows&) { done = true; },
×
2630
            [](const prepared_stmt::fetch_error&) {},
×
2631
            [&retval](entry en) { retval.emplace_back(en); });
×
2632
    }
2633
    return Ok(retval);
×
2634
}
2635

2636
void
2637
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2638
                                      const std::string& regex_name)
2639
{
2640
    constexpr const char* STMT = R"(
×
2641
       DELETE FROM regex101_entries WHERE
2642
          format_name = ? AND regex_name = ?;
2643
)";
2644

2645
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
×
2646
    auto_sqlite3 db;
×
2647

2648
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2649
        return;
×
2650
    }
2651

2652
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2653

2654
    if (prep_res.isErr()) {
×
2655
        return;
×
2656
    }
2657

2658
    auto ps = prep_res.unwrap();
×
2659

2660
    ps.execute();
×
2661
}
2662

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

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

2675
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
×
2676
        return error{std::string()};
×
2677
    }
2678

2679
    auto_mem<char, sqlite3_free> errmsg;
×
2680
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
×
2681
        != SQLITE_OK)
×
2682
    {
2683
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
2684
        return error{std::string(errmsg)};
×
2685
    }
2686

2687
    auto prep_res = prepare_stmt(db.in(), STMT, format_name, regex_name);
×
2688
    if (prep_res.isErr()) {
×
2689
        return error{prep_res.unwrapErr()};
×
2690
    }
2691

2692
    auto ps = prep_res.unwrap();
×
2693
    return ps.fetch_row<entry>().match(
×
2694
        [](const prepared_stmt::fetch_error& fe) -> get_result_t {
×
2695
            return error{fe.fe_msg};
×
2696
        },
2697
        [](const prepared_stmt::end_of_rows&) -> get_result_t {
×
2698
            return no_entry{};
×
2699
        },
2700
        [](const entry& en) -> get_result_t { return en; });
×
2701
}
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