;;======================================================================
;; Copyright 2017, Matthew Welland.
;;
;; This file is part of Megatest.
;;
;; Megatest is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; Megatest is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Megatest. If not, see <http://www.gnu.org/licenses/>.
;;
;;======================================================================
;;======================================================================
;;
;; log to sqlite3 db, polling to tail along with indexing to any point in
;; history is then easy
;;
;;======================================================================
(declare (unit ftail))
(module ftail
(
open-tail-db
tail-write
tail-get-fid
file-tail
)
(import scheme chicken data-structures extras)
(use (prefix sqlite3 sqlite3:) posix typed-records)
(define (open-tail-db )
(let* ((basedir (create-directory (conc "/tmp/" (current-user-name))))
(dbpath (conc basedir "/megatest_logs.db"))
(dbexists (file-exists? dbpath))
(db (sqlite3:open-database dbpath))
(handler (sqlite3:make-busy-timeout 136000)))
(sqlite3:set-busy-handler! db handler)
(sqlite3:execute db "PRAGMA synchronous = 0;")
(if (not dbexists)
(begin
(sqlite3:execute db "CREATE TABLE IF NOT EXISTS log_files (id INTEGER PRIMARY KEY,filename TEXT,event_time TIMESTAMP DEFAULT (strftime('%s','now')));")
(sqlite3:execute db "CREATE TABLE IF NOT EXISTS log_data (id INTEGER PRIMARY KEY,fid INTEGER,line TEXT,event_time TIMESTAMP DEFAULT (strftime('%s','now')));")
))
db))
(define (tail-write db fid lines)
(sqlite3:with-transaction
db
(lambda ()
(for-each
(lambda (line)
(sqlite3:execute db "INSERT INTO log_data (fid,line) VALUES (?,?);" fid line))
lines))))
(define (tail-get-fid db fname)
(let ((fid (handle-exceptions
exn
#f
(sqlite3:first-result db "SELECT id FROM log_files WHERE filename=?;" fname))))
(if fid
fid
(begin
(sqlite3:execute db "INSERT INTO log_files (filename) VALUES (?);" fname)
(tail-get-fid db fname)))))
(define (file-tail fname #!key (db-in #f))
(let* ((inp (open-input-file fname))
(db (or db-in (open-tail-db)))
(fid (tail-get-fid db fname)))
(let loop ((inl (read-line inp))
(lines '())
(lastwr (current-seconds)))
(if (eof-object? inl)
(let ((timed-out (> (- (current-seconds) lastwr) 60)))
(if timed-out (tail-write db fid (reverse lines)))
(sleep 1)
(if timed-out
(loop (read-line inp) '() (current-seconds))
(loop (read-line inp) lines lastwr)))
(let* ((savelines (> (length lines) 19)))
;; (print inl)
(if savelines (tail-write db fid (reverse lines)))
(loop (read-line inp)
(if savelines
'()
(cons inl lines))
(if savelines
(current-seconds)
lastwr)))))))
;; offset -20 means get last 20 lines
;;
(define (tail-get-lines db fid offset count)
(if (> offset 0)
(sqlite3:map-row (lambda (id line)
(vector id line))
db
"SELECT id,line FROM log_data WHERE fid=? OFFSET ? LIMIT ?;" fid offset count)
(reverse ;; get N from the end
(sqlite3:map-row (lambda (id line)
(vector id line))
db
"SELECT id,line FROM log_data WHERE fid=? ORDER BY id DESC LIMIT ?;" fid (abs offset)))))
)