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

tstack / lnav / 24959179949-2999

26 Apr 2026 02:37PM UTC coverage: 69.227% (+0.09%) from 69.141%
24959179949-2999

push

github

tstack
[tests] fix paths

53969 of 77959 relevant lines covered (69.23%)

568944.78 hits per line

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

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

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

35
#include "session_data.hh"
36

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

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

65
session_data_t session_data;
66
recent_refs_t recent_refs;
67

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

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

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

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

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

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

102
    access_time datetime DEFAULT CURRENT_TIMESTAMP,
103

104
    PRIMARY KEY (netloc)
105
);
106

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

113
    PRIMARY KEY (format_name, regex_name),
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

208
    sqlite3_clear_bindings(stmt);
30✔
209

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

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

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

227
    return bind_values(stmt,
60✔
228
                       lf->original_line_time(line_iter),
229
                       lf->get_format()->get_name(),
60✔
230
                       line_hash,
231
                       session_time)
232
        == SQLITE_OK;
30✔
233
}
30✔
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()
693✔
341
{
342
    lnav_data.ld_session_time = time(nullptr);
693✔
343
    lnav_data.ld_session_id.clear();
693✔
344
    session_data.sd_view_states[LNV_LOG].vs_top = -1;
693✔
345
}
693✔
346

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

353
    for (auto& ld_file_name : lnav_data.ld_active_files.fc_file_names) {
123✔
354
        if (!ld_file_name.second.loo_include_in_session) {
62✔
355
            continue;
×
356
        }
357
        has_files = true;
62✔
358
        h.update(ld_file_name.first);
62✔
359
    }
360
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
123✔
361
        if (lf->is_valid_filename()) {
62✔
362
            continue;
61✔
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) {
61✔
372
        return std::nullopt;
3✔
373
    }
374

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

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

383
    cleanup_session_data();
31✔
384

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

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

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

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

423
    session_file_names.sort();
30✔
424

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567
        --high_line_iter;
29✔
568

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

853
        --high_line_iter;
29✔
854

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

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

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

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

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

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

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

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

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

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

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

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

929
                        reload_needed = true;
3✔
930

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

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

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

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

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

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

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

986
    json_path_handler_base::ENUM_TERMINATOR,
987
};
988

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

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

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

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

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

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

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

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

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

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

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

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

1099
        bool log_changes = false;
28✔
1100

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

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

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

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

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

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

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

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

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

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

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

1166
        sqlite3_clear_bindings(stmt);
1✔
1167

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1293
        std::string tags;
7✔
1294

1295
        {
1296
            yajlpp_gen gen;
7✔
1297

1298
            yajl_gen_config(gen, yajl_gen_beautify, false);
7✔
1299

1300
            {
1301
                yajlpp_array arr(gen);
7✔
1302

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

1311
            tags = gen.to_string_fragment().to_string();
7✔
1312

1313
            if (tags == "[]") {
7✔
1314
                tags.clear();
3✔
1315
            }
1316
        }
7✔
1317

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

1326
        if (!line_meta.bm_annotations.la_pairs.empty()) {
7✔
1327
            auto anno_str = logmsg_annotations_handlers.to_string(
1328
                line_meta.bm_annotations);
1✔
1329

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

1345
        if (line_meta.bm_opid.empty()) {
7✔
1346
            sqlite3_bind_null(stmt, 9);
7✔
1347
        } else {
1348
            bind_to_sqlite(stmt, 9, line_meta.bm_opid);
×
1349
        }
1350

1351
        sqlite3_bind_int(stmt, 10, 0);
7✔
1352

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

1359
        marked_session_lines.emplace_back(lf->original_line_time(line_iter),
7✔
1360
                                          lf->get_format_ptr()->get_name(),
7✔
1361
                                          line_hash);
1362

1363
        sqlite3_reset(stmt);
7✔
1364
    }
41✔
1365
}
1366

1367
static void
1368
save_text_bookmarks(sqlite3* db)
30✔
1369
{
1370
    auto& tss = lnav_data.ld_text_source;
30✔
1371

1372
    if (tss.empty()) {
30✔
1373
        return;
29✔
1374
    }
1375

1376
    tss.copy_bookmarks_to_current_file();
1✔
1377

1378
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1379

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

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

1413
    for (const auto& fvs : tss.get_file_states()) {
2✔
1414
        auto& lf = fvs->fvs_file;
1✔
1415
        if (lf == nullptr || lf->size() == 0) {
1✔
1416
            continue;
×
1417
        }
1418

1419
        auto file_path = lf->get_path_for_key().string();
1✔
1420
        auto line_count = static_cast<int>(lf->size());
1✔
1421

1422
        static const bookmark_type_t* SAVE_TYPES[] = {
1423
            &textview_curses::BM_USER,
1424
            &textview_curses::BM_STICKY,
1425
        };
1426

1427
        for (const auto* bm_type : SAVE_TYPES) {
3✔
1428
            auto& bv = fvs->fvs_content_marks[bm_type];
2✔
1429
            if (bv.empty()) {
2✔
1430
                continue;
1✔
1431
            }
1432

1433
            for (const auto& vl : bv.bv_tree) {
2✔
1434
                if (static_cast<int>(vl) >= line_count) {
1✔
1435
                    continue;
×
1436
                }
1437

1438
                auto line_iter = lf->begin() + static_cast<int>(vl);
1✔
1439
                auto fr = lf->get_file_range(line_iter, false);
1✔
1440
                auto read_result = lf->read_range(fr);
1✔
1441

1442
                if (read_result.isErr()) {
1✔
1443
                    continue;
×
1444
                }
1445

1446
                auto line_hash
1447
                    = read_result
1448
                          .map([](auto sbr) {
2✔
1449
                              return hasher()
2✔
1450
                                  .update(sbr.get_data(), sbr.length())
1✔
1451
                                  .to_string();
2✔
1452
                          })
1453
                          .unwrap();
1✔
1454

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

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

1468
                sqlite3_reset(stmt.in());
1✔
1469
            }
1✔
1470
        }
1471
    }
1✔
1472
}
1✔
1473

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

1485
    auto& tss = lnav_data.ld_text_source;
29✔
1486
    auto& tc = lnav_data.ld_views[LNV_TEXT];
29✔
1487

1488
    if (tss.empty()) {
29✔
1489
        return;
28✔
1490
    }
1491

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

1501
    for (const auto& fvs : tss.get_file_states()) {
2✔
1502
        auto& lf = fvs->fvs_file;
1✔
1503
        if (lf == nullptr || lf->size() == 0) {
1✔
1504
            continue;
×
1505
        }
1506

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

1514
        size_t mark_count = 0;
1✔
1515
        int64_t last_session_time = -1;
1✔
1516
        bool done = false;
1✔
1517
        while (!done) {
3✔
1518
            auto rc = sqlite3_step(stmt.in());
2✔
1519

1520
            switch (rc) {
2✔
1521
                case SQLITE_OK:
1✔
1522
                case SQLITE_DONE:
1523
                    done = true;
1✔
1524
                    break;
1✔
1525

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

1534
                    if (last_session_time == -1) {
1✔
1535
                        last_session_time = session_time;
1✔
1536
                    } else if (last_session_time != session_time) {
×
1537
                        done = true;
×
1538
                        continue;
×
1539
                    }
1540

1541
                    if (line_number >= line_count) {
1✔
1542
                        continue;
×
1543
                    }
1544

1545
                    auto bm_type_opt = bookmark_type_t::find_type(mark_type);
1✔
1546
                    if (!bm_type_opt) {
1✔
1547
                        continue;
×
1548
                    }
1549

1550
                    auto line_iter = lf->begin() + line_number;
1✔
1551
                    auto fr = lf->get_file_range(line_iter, false);
1✔
1552
                    auto read_result = lf->read_range(fr);
1✔
1553

1554
                    if (read_result.isErr()) {
1✔
1555
                        continue;
×
1556
                    }
1557

1558
                    auto line_hash
1559
                        = read_result
1560
                              .map([](auto sbr) {
2✔
1561
                                  return hasher()
2✔
1562
                                      .update(sbr.get_data(), sbr.length())
1✔
1563
                                      .to_string();
2✔
1564
                              })
1565
                              .unwrap();
1✔
1566

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

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

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

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

1603
    tc.reload_data();
1✔
1604
}
1✔
1605

1606
static void
1607
save_timeline_bookmarks(sqlite3* db)
30✔
1608
{
1609
    auto& tc = lnav_data.ld_views[LNV_TIMELINE];
30✔
1610
    auto* tss = static_cast<timeline_source*>(tc.get_sub_source());
30✔
1611

1612
    if (tss == nullptr) {
30✔
1613
        return;
×
1614
    }
1615

1616
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
30✔
1617

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

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

1652
    static const bookmark_type_t* SAVE_TYPES[] = {
1653
        &textview_curses::BM_USER,
1654
        &textview_curses::BM_STICKY,
1655
    };
1656

1657
    for (const auto* bm_type : SAVE_TYPES) {
90✔
1658
        const auto& bv = tc.get_bookmarks()[bm_type];
60✔
1659
        if (bv.empty()) {
60✔
1660
            continue;
58✔
1661
        }
1662

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

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

1671
            sqlite3_clear_bindings(stmt.in());
2✔
1672
            bind_to_sqlite(
2✔
1673
                stmt.in(), 1, timeline_source::row_type_to_string(row.or_type));
2✔
1674
            bind_to_sqlite(stmt.in(), 2, row.or_name.to_string());
2✔
1675
            bind_to_sqlite(stmt.in(), 3, bm_type->get_name());
2✔
1676
            sqlite3_bind_int64(stmt.in(), 4, lnav_data.ld_session_time);
2✔
1677

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

1684
            sqlite3_reset(stmt.in());
2✔
1685
        }
1686
    }
1687
}
30✔
1688

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

1699
    auto* tss = static_cast<timeline_source*>(
1700
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
29✔
1701

1702
    if (tss == nullptr) {
29✔
1703
        return;
×
1704
    }
1705

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

1715
    sqlite3_bind_int64(stmt.in(), 1, lnav_data.ld_session_load_time);
29✔
1716

1717
    int64_t last_session_time = -1;
29✔
1718
    bool done = false;
29✔
1719
    while (!done) {
60✔
1720
        auto rc = sqlite3_step(stmt.in());
31✔
1721

1722
        switch (rc) {
31✔
1723
            case SQLITE_OK:
29✔
1724
            case SQLITE_DONE:
1725
                done = true;
29✔
1726
                break;
29✔
1727

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

1737
                if (last_session_time == -1) {
2✔
1738
                    last_session_time = session_time;
2✔
1739
                } else if (last_session_time != session_time) {
×
1740
                    done = true;
×
1741
                    continue;
×
1742
                }
1743

1744
                if (row_type_str == nullptr || row_name == nullptr
2✔
1745
                    || mark_type == nullptr)
2✔
1746
                {
1747
                    continue;
×
1748
                }
1749

1750
                auto bm_type_opt = bookmark_type_t::find_type(mark_type);
2✔
1751
                if (!bm_type_opt) {
2✔
1752
                    continue;
×
1753
                }
1754

1755
                auto rt_opt
1756
                    = timeline_source::row_type_from_string(row_type_str);
2✔
1757
                if (!rt_opt) {
2✔
1758
                    continue;
×
1759
                }
1760

1761
                tss->ts_pending_bookmarks.emplace_back(
4✔
1762
                    timeline_source::pending_bookmark{
4✔
1763
                        rt_opt.value(),
2✔
1764
                        row_name,
1765
                        bm_type_opt.value(),
2✔
1766
                    });
1767
                break;
2✔
1768
            }
1769

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

1780
static void
1781
save_time_bookmarks()
30✔
1782
{
1783
    auto_sqlite3 db;
30✔
1784
    auto db_path = lnav::paths::dotlnav() / LOG_METADATA_NAME;
30✔
1785
    auto_mem<char, sqlite3_free> errmsg;
30✔
1786
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
30✔
1787

1788
    if (sqlite3_open(db_path.c_str(), db.out()) != SQLITE_OK) {
30✔
1789
        log_error("unable to open bookmark DB -- %s", db_path.c_str());
×
1790
        return;
×
1791
    }
1792

1793
    if (sqlite3_exec(db.in(), META_TABLE_DEF, nullptr, nullptr, errmsg.out())
30✔
1794
        != SQLITE_OK)
30✔
1795
    {
1796
        log_error("unable to make bookmark table -- %s", errmsg.in());
×
1797
        return;
×
1798
    }
1799

1800
    if (sqlite3_exec(
30✔
1801
            db.in(), "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
1802
        != SQLITE_OK)
30✔
1803
    {
1804
        log_error("unable to begin transaction -- %s", errmsg.in());
×
1805
        return;
×
1806
    }
1807

1808
    {
1809
        static const char* UPDATE_NETLOCS_STMT
1810
            = R"(REPLACE INTO recent_netlocs (netloc) VALUES (?))";
1811

1812
        std::set<std::string> netlocs;
30✔
1813

1814
        isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
30✔
1815
            [&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
60✔
1816

1817
        if (sqlite3_prepare_v2(
30✔
1818
                db.in(), UPDATE_NETLOCS_STMT, -1, stmt.out(), nullptr)
1819
            != SQLITE_OK)
30✔
1820
        {
1821
            log_error("could not prepare recent_netlocs statement -- %s",
×
1822
                      sqlite3_errmsg(db));
1823
            return;
×
1824
        }
1825

1826
        for (const auto& netloc : netlocs) {
30✔
1827
            bind_to_sqlite(stmt.in(), 1, netloc);
×
1828

1829
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
×
1830
                log_error("could not execute bookmark insert statement -- %s",
×
1831
                          sqlite3_errmsg(db));
1832
                return;
×
1833
            }
1834

1835
            sqlite3_reset(stmt.in());
×
1836
        }
1837
        recent_refs.rr_netlocs.insert(netlocs.begin(), netlocs.end());
30✔
1838
    }
30✔
1839

1840
    auto& lss = lnav_data.ld_log_source;
30✔
1841
    auto& bm = lss.get_user_bookmarks();
30✔
1842

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

1857
    for (auto& marked_session_line : marked_session_lines) {
32✔
1858
        sqlite3_clear_bindings(stmt.in());
2✔
1859

1860
        if (bind_values(stmt,
4✔
1861
                        marked_session_line.sl_time,
1862
                        marked_session_line.sl_format_name,
1863
                        marked_session_line.sl_line_hash,
2✔
1864
                        lnav_data.ld_session_time)
1865
            != SQLITE_OK)
2✔
1866
        {
1867
            continue;
×
1868
        }
1869

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

1876
        sqlite3_reset(stmt.in());
2✔
1877
    }
1878

1879
    marked_session_lines.clear();
30✔
1880

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

1897
    {
1898
        for (auto file_iter = lnav_data.ld_log_source.begin();
30✔
1899
             file_iter != lnav_data.ld_log_source.end();
60✔
1900
             ++file_iter)
30✔
1901
        {
1902
            auto lf = (*file_iter)->get_file();
30✔
1903

1904
            if (lf == nullptr) {
30✔
1905
                continue;
×
1906
            }
1907
            if (lf->size() == 0) {
30✔
1908
                continue;
×
1909
            }
1910

1911
            content_line_t base_content_line;
30✔
1912
            base_content_line = lss.get_file_base_content_line(file_iter);
30✔
1913
            base_content_line
1914
                = content_line_t(base_content_line + lf->size() - 1);
30✔
1915

1916
            if (!bind_line(db.in(),
30✔
1917
                           stmt.in(),
1918
                           base_content_line,
1919
                           lnav_data.ld_session_time))
30✔
1920
            {
1921
                continue;
×
1922
            }
1923

1924
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
30✔
1925
                log_error("could not bind log hash -- %s",
×
1926
                          sqlite3_errmsg(db.in()));
1927
                return;
×
1928
            }
1929

1930
            sqlite3_bind_int(stmt.in(), 10, 0);
30✔
1931

1932
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
30✔
1933
                log_error("could not execute bookmark insert statement -- %s",
×
1934
                          sqlite3_errmsg(db));
1935
                return;
×
1936
            }
1937

1938
            sqlite3_reset(stmt.in());
30✔
1939
        }
30✔
1940
    }
1941

1942
    save_user_bookmarks(db.in(),
30✔
1943
                        stmt.in(),
1944
                        bm[&textview_curses::BM_USER],
1945
                        bm[&textview_curses::BM_STICKY]);
1946
    for (const auto& ldd : lss) {
60✔
1947
        auto* lf = ldd->get_file_ptr();
30✔
1948
        if (lf == nullptr) {
30✔
1949
            continue;
×
1950
        }
1951

1952
        save_meta_bookmarks(db.in(), stmt.in(), lf);
30✔
1953
    }
1954

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

1969
    for (auto& offset_session_line : offset_session_lines) {
31✔
1970
        sqlite3_clear_bindings(stmt.in());
1✔
1971

1972
        if (bind_values(stmt,
2✔
1973
                        offset_session_line.sl_time,
1974
                        offset_session_line.sl_format_name,
1975
                        offset_session_line.sl_line_hash,
1✔
1976
                        lnav_data.ld_session_time)
1977
            != SQLITE_OK)
1✔
1978
        {
1979
            continue;
×
1980
        }
1981

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

1988
        sqlite3_reset(stmt.in());
1✔
1989
    }
1990

1991
    offset_session_lines.clear();
30✔
1992

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

2008
    {
2009
        for (auto file_iter = lnav_data.ld_log_source.begin();
30✔
2010
             file_iter != lnav_data.ld_log_source.end();
60✔
2011
             ++file_iter)
30✔
2012
        {
2013
            auto lf = (*file_iter)->get_file();
30✔
2014
            if (lf == nullptr) {
30✔
2015
                continue;
×
2016
            }
2017
            if (lf->size() == 0) {
30✔
2018
                continue;
×
2019
            }
2020

2021
            if (bind_values(stmt,
90✔
2022
                            lf->original_line_time(lf->begin()),
2023
                            lf->get_format()->get_name(),
60✔
2024
                            lf->get_content_id(),
30✔
2025
                            lnav_data.ld_session_time)
2026
                != SQLITE_OK)
30✔
2027
            {
2028
                continue;
×
2029
            }
2030

2031
            if (sqlite3_bind_null(stmt.in(), 5) != SQLITE_OK) {
30✔
2032
                log_error("could not bind log hash -- %s",
×
2033
                          sqlite3_errmsg(db.in()));
2034
                return;
×
2035
            }
2036

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

2043
            if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
30✔
2044
                log_error("could not execute bookmark insert statement -- %s",
×
2045
                          sqlite3_errmsg(db));
2046
                return;
×
2047
            }
2048

2049
            sqlite3_reset(stmt.in());
30✔
2050
        }
30✔
2051
    }
2052

2053
    for (const auto& ls : lss) {
60✔
2054
        if (ls->get_file() == nullptr) {
30✔
2055
            continue;
27✔
2056
        }
2057

2058
        const auto lf = ls->get_file();
30✔
2059
        if (!lf->is_time_adjusted()) {
30✔
2060
            continue;
27✔
2061
        }
2062

2063
        auto line_iter = lf->begin() + lf->get_time_offset_line();
3✔
2064
        auto offset = lf->get_time_offset();
3✔
2065

2066
        bind_values(stmt.in(),
6✔
2067
                    lf->original_line_time(line_iter),
2068
                    lf->get_format()->get_name(),
6✔
2069
                    lf->get_content_id(),
3✔
2070
                    lnav_data.ld_session_time,
2071
                    offset.tv_sec,
2072
                    offset.tv_usec);
2073

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

2080
        sqlite3_reset(stmt.in());
3✔
2081
    }
30✔
2082

2083
    log_info("saved %d log bookmarks", sqlite3_changes(db.in()));
30✔
2084
    save_text_bookmarks(db.in());
30✔
2085
    log_info("saved %d text bookmarks", sqlite3_changes(db.in()));
30✔
2086
    save_timeline_bookmarks(db.in());
30✔
2087
    log_info("saved %d timeline bookmarks", sqlite3_changes(db.in()));
30✔
2088

2089
    if (sqlite3_exec(db.in(), "COMMIT", nullptr, nullptr, errmsg.out())
30✔
2090
        != SQLITE_OK)
30✔
2091
    {
2092
        log_error("unable to begin transaction -- %s", errmsg.in());
×
2093
        return;
×
2094
    }
2095

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

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

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

2130
    if (sqlite3_exec(
30✔
2131
            db.in(), TIMELINE_BOOKMARK_LRU_STMT, nullptr, nullptr, errmsg.out())
2132
        != SQLITE_OK)
30✔
2133
    {
2134
        log_error("unable to delete old timeline bookmarks -- %s", errmsg.in());
×
2135
        return;
×
2136
    }
2137
    auto timeline_bm_changes = sqlite3_changes(db.in());
30✔
2138
    if (timeline_bm_changes > 0) {
30✔
2139
        log_info("deleted %d old timeline bookmarks", timeline_bm_changes);
×
2140
    }
2141
}
30✔
2142

2143
static void
2144
save_session_with_id(const std::string& session_id)
29✔
2145
{
2146
    auto_mem<FILE> file(fclose);
29✔
2147
    yajl_gen handle = nullptr;
29✔
2148

2149
    /* TODO: save the last search query */
2150

2151
    log_info("saving session with id: %s", session_id.c_str());
29✔
2152

2153
    auto view_base_name
2154
        = fmt::format(FMT_STRING("view-info-{}.ts{}.ppid{}.json"),
58✔
2155
                      session_id,
2156
                      lnav_data.ld_session_time,
2157
                      getppid());
29✔
2158
    auto view_file_name = lnav::paths::dotlnav() / view_base_name;
29✔
2159
    auto view_file_tmp_name = view_file_name.string() + ".tmp";
29✔
2160

2161
    if ((file = fopen(view_file_tmp_name.c_str(), "w")) == nullptr) {
29✔
2162
        perror("Unable to open session file");
×
2163
    } else if (nullptr == (handle = yajl_gen_alloc(nullptr))) {
29✔
2164
        perror("Unable to create yajl_gen object");
×
2165
    } else {
2166
        yajl_gen_config(
29✔
2167
            handle, yajl_gen_print_callback, yajl_writer, file.in());
2168

2169
        {
2170
            yajlpp_map root_map(handle);
29✔
2171

2172
            root_map.gen("save-time");
29✔
2173
            root_map.gen((long long) time(nullptr));
29✔
2174

2175
            root_map.gen("time-offset");
29✔
2176
            root_map.gen(lnav_data.ld_log_source.is_time_offset_enabled());
29✔
2177

2178
            root_map.gen("files");
29✔
2179

2180
            {
2181
                yajlpp_array file_list(handle);
29✔
2182

2183
                for (auto& ld_file_name :
29✔
2184
                     lnav_data.ld_active_files.fc_file_names)
89✔
2185
                {
2186
                    file_list.gen(ld_file_name.first);
31✔
2187
                }
2188
            }
29✔
2189

2190
            root_map.gen("file-states");
29✔
2191

2192
            {
2193
                yajlpp_map file_states(handle);
29✔
2194

2195
                for (auto& lf : lnav_data.ld_active_files.fc_files) {
60✔
2196
                    auto ld_opt = lnav_data.ld_log_source.find_data(lf);
31✔
2197

2198
                    file_states.gen(lf->get_filename().native());
31✔
2199

2200
                    {
2201
                        yajlpp_map file_state(handle);
31✔
2202

2203
                        file_state.gen("visible");
31✔
2204
                        file_state.gen(!ld_opt || ld_opt.value()->ld_visible);
31✔
2205
                    }
31✔
2206
                }
2207
            }
29✔
2208

2209
            root_map.gen("views");
29✔
2210

2211
            {
2212
                yajlpp_map top_view_map(handle);
29✔
2213

2214
                for (int lpc = 0; lpc < LNV__MAX; lpc++) {
290✔
2215
                    auto& tc = lnav_data.ld_views[lpc];
261✔
2216
                    auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
261✔
2217

2218
                    top_view_map.gen(lnav_view_strings[lpc]);
261✔
2219

2220
                    yajlpp_map view_map(handle);
261✔
2221

2222
                    view_map.gen("top_line");
261✔
2223

2224
                    if (tc.get_top() >= tc.get_top_for_last_row()) {
261✔
2225
                        view_map.gen(-1LL);
242✔
2226
                    } else {
2227
                        view_map.gen((long long) tc.get_top());
19✔
2228
                    }
2229

2230
                    if (tc.is_selectable() && tc.get_selection() >= 0_vl
324✔
2231
                        && tc.get_inner_height() > 0_vl
4✔
2232
                        && tc.get_selection() != tc.get_inner_height() - 1)
324✔
2233
                    {
2234
                        auto sel = tc.get_selection();
3✔
2235
                        if (sel) {
3✔
2236
                            view_map.gen("focused_line");
3✔
2237
                            view_map.gen((long long) sel.value());
3✔
2238

2239
                            if (ta != nullptr) {
3✔
2240
                                auto anchor_opt
2241
                                    = ta->anchor_for_row(sel.value());
1✔
2242
                                if (anchor_opt) {
1✔
2243
                                    view_map.gen("anchor");
1✔
2244
                                    view_map.gen(anchor_opt.value());
1✔
2245
                                }
2246
                            }
1✔
2247
                        }
2248
                    }
2249

2250
                    view_map.gen("search");
261✔
2251
                    view_map.gen(lnav_data.ld_views[lpc].get_current_search());
261✔
2252

2253
                    view_map.gen("word_wrap");
261✔
2254
                    view_map.gen(tc.get_word_wrap());
261✔
2255

2256
                    auto* tss = tc.get_sub_source();
261✔
2257
                    if (tss == nullptr) {
261✔
2258
                        continue;
58✔
2259
                    }
2260

2261
                    view_map.gen("filtering");
203✔
2262
                    view_map.gen(tss->tss_apply_filters);
203✔
2263

2264
                    if (tss->get_min_log_level() != LEVEL_UNKNOWN) {
203✔
2265
                        view_map.gen("min_level");
×
2266
                        view_map.gen(level_names[tss->get_min_log_level()]);
×
2267
                    }
2268

2269
                    view_map.gen("commands");
203✔
2270
                    yajlpp_array cmd_array(handle);
203✔
2271

2272
                    tss->add_commands_for_session(
203✔
2273
                        [&](auto& cmd) { cmd_array.gen(cmd); });
220✔
2274
                }
261✔
2275
            }
29✔
2276
        }
29✔
2277

2278
        yajl_gen_clear(handle);
29✔
2279
        yajl_gen_free(handle);
29✔
2280

2281
        fclose(file.release());
29✔
2282

2283
        log_perror(rename(view_file_tmp_name.c_str(), view_file_name.c_str()));
29✔
2284

2285
        log_info("Saved session: %s", view_file_name.c_str());
29✔
2286
    }
2287
}
29✔
2288

2289
void
2290
save_session()
30✔
2291
{
2292
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
30✔
2293
        log_info("secure mode is enabled, not saving session");
×
2294
        return;
×
2295
    }
2296

2297
    static auto op = lnav_operation{"save_session"};
30✔
2298

2299
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2300

2301
    log_debug("BEGIN save_session");
30✔
2302
    save_time_bookmarks();
30✔
2303

2304
    const auto opt_session_id = compute_session_id();
30✔
2305
    opt_session_id | [](auto& session_id) { save_session_with_id(session_id); };
58✔
2306
    for (const auto& pair : lnav_data.ld_session_id) {
34✔
2307
        if (opt_session_id && pair.first == opt_session_id.value()) {
4✔
2308
            continue;
3✔
2309
        }
2310
        save_session_with_id(pair.first);
1✔
2311
    }
2312
    log_debug("END save_session");
30✔
2313
}
30✔
2314

2315
void
2316
reset_session()
7✔
2317
{
2318
    log_info("reset session: time=%lld", lnav_data.ld_session_time);
7✔
2319

2320
    save_session();
7✔
2321

2322
    lnav_data.ld_session_time = time(nullptr);
7✔
2323
    session_data.sd_file_states.clear();
7✔
2324

2325
    for (auto& tc : lnav_data.ld_views) {
70✔
2326
        auto* ttt = dynamic_cast<text_time_translator*>(tc.get_sub_source());
63✔
2327
        auto& hmap = tc.get_highlights();
63✔
2328
        auto hl_iter = hmap.begin();
63✔
2329

2330
        while (hl_iter != hmap.end()) {
644✔
2331
            if (hl_iter->first.first != highlight_source_t::INTERACTIVE) {
581✔
2332
                ++hl_iter;
581✔
2333
            } else {
2334
                hmap.erase(hl_iter++);
×
2335
            }
2336
        }
2337

2338
        if (ttt != nullptr) {
63✔
2339
            ttt->clear_min_max_row_times();
42✔
2340
        }
2341
    }
2342

2343
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
16✔
2344
        lf->reset_state();
9✔
2345
    }
2346

2347
    lnav_data.ld_log_source.get_breakpoints().clear();
7✔
2348
    lnav_data.ld_log_source.lss_highlighters.clear();
7✔
2349
    lnav_data.ld_log_source.set_force_rebuild();
7✔
2350
    lnav_data.ld_log_source.set_marked_only(false);
7✔
2351
    lnav_data.ld_log_source.set_min_log_level(LEVEL_UNKNOWN);
7✔
2352
    lnav_data.ld_log_source.set_sql_filter("", nullptr);
14✔
2353
    lnav_data.ld_log_source.set_sql_marker("", nullptr);
7✔
2354
    lnav_data.ld_log_source.clear_bookmark_metadata();
7✔
2355
    rebuild_indexes(std::nullopt);
7✔
2356

2357
    lnav_data.ld_db_row_source.reset_user_state();
7✔
2358

2359
    auto* tss = static_cast<timeline_source*>(
2360
        lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
7✔
2361
    if (tss != nullptr) {
7✔
2362
        tss->ts_hidden_row_types.clear();
7✔
2363
        tss->ts_pending_bookmarks.clear();
7✔
2364
    }
2365

2366
    for (auto& tc : lnav_data.ld_views) {
70✔
2367
        auto* tss = tc.get_sub_source();
63✔
2368

2369
        if (tss == nullptr) {
63✔
2370
            continue;
14✔
2371
        }
2372
        tss->get_filters().clear_filters();
49✔
2373
        tss->tss_apply_filters = true;
49✔
2374
        tss->text_clear_marks(&textview_curses::BM_USER);
49✔
2375
        tss->text_clear_marks(&textview_curses::BM_STICKY);
49✔
2376
        tc.get_bookmarks()[&textview_curses::BM_USER].clear();
49✔
2377
        tc.get_bookmarks()[&textview_curses::BM_STICKY].clear();
49✔
2378
        tss->text_filters_changed();
49✔
2379
        tc.reload_data();
49✔
2380
    }
2381

2382
    for (auto& fvs : lnav_data.ld_text_source.get_file_states()) {
7✔
2383
        fvs->fvs_bookmarks.clear();
×
2384
    }
2385

2386
    lnav_data.ld_filter_view.reload_data();
7✔
2387
    lnav_data.ld_files_view.reload_data();
7✔
2388
    for (const auto& format : log_format::get_root_formats()) {
558✔
2389
        auto* elf = dynamic_cast<external_log_format*>(format.get());
551✔
2390

2391
        if (elf == nullptr) {
551✔
2392
            continue;
35✔
2393
        }
2394

2395
        bool changed = false;
516✔
2396
        for (const auto& vd : elf->elf_value_defs) {
7,545✔
2397
            if (vd.second->vd_meta.lvm_user_hidden) {
7,029✔
2398
                vd.second->vd_meta.lvm_user_hidden = std::nullopt;
×
2399
                changed = true;
×
2400
            }
2401
        }
2402
        if (changed) {
516✔
2403
            elf->elf_value_defs_state->vds_generation += 1;
×
2404
        }
2405
    }
2406
}
7✔
2407

2408
void
2409
lnav::session::apply_view_commands()
30✔
2410
{
2411
    static auto op = lnav_operation{__FUNCTION__};
30✔
2412

2413
    auto op_guard = lnav_opid_guard::internal(op);
30✔
2414

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2604
    ps.execute();
×
2605
}
2606

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

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

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

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

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

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

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

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

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

2663
void
2664
lnav::session::regex101::delete_entry(const std::string& format_name,
×
2665
                                      const std::string& regex_name)
2666
{
2667
    constexpr const char* STMT = R"(
×
2668
       DELETE 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;
×
2677
    }
2678

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

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

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

2687
    ps.execute();
×
2688
}
2689

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

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

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

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

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

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