#!/usr/bin/python3 # -*- coding: utf-8 -*- # ----------------------------------------------------------------------- # This file is part of TISBackup # # TISBackup 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. # # TISBackup 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 TISBackup. If not, see . # # ----------------------------------------------------------------------- """Database management for backup statistics and history.""" import logging import os import sqlite3 from .utils import ( convert_bytes, datetime2isodate, hours_minutes, html_table, isodate2datetime, pp, splitThousands, time2display, ) class BackupStat: """Manages SQLite database for backup statistics and history.""" dbpath = "" db = None logger = logging.getLogger("tisbackup") def __init__(self, dbpath): self.dbpath = dbpath if not os.path.isfile(self.dbpath): self.db = sqlite3.connect(self.dbpath) self.initdb() else: self.db = sqlite3.connect(self.dbpath, check_same_thread=False) if "'TYPE'" not in str(self.db.execute("select * from stats").description): self.updatedb() def updatedb(self): """Update database schema to add TYPE column if missing.""" self.logger.debug("Update stat database") self.db.execute("alter table stats add column TYPE TEXT;") self.db.execute("update stats set TYPE='BACKUP';") self.db.commit() def initdb(self): """Initialize database schema.""" assert isinstance(self.db, sqlite3.Connection) self.logger.debug("Initialize stat database") self.db.execute(""" create table stats ( backup_name TEXT, server_name TEXT, description TEXT, backup_start TEXT, backup_end TEXT, backup_duration NUMERIC, total_files_count INT, written_files_count INT, total_bytes INT, written_bytes INT, status TEXT, log TEXT, backup_location TEXT, TYPE TEXT)""") self.db.execute(""" create index idx_stats_backup_name on stats(backup_name);""") self.db.execute(""" create index idx_stats_backup_location on stats(backup_location);""") self.db.execute(""" CREATE INDEX idx_stats_backup_name_start on stats(backup_name,backup_start);""") self.db.commit() def start(self, backup_name, server_name, TYPE, description="", backup_location=None): """Add in stat DB a record for the newly running backup""" return self.add( backup_name=backup_name, server_name=server_name, description=description, backup_start=datetime2isodate(), status="Running", TYPE=TYPE, ) def finish( self, rowid, total_files_count=None, written_files_count=None, total_bytes=None, written_bytes=None, log=None, status="OK", backup_end=None, backup_duration=None, backup_location=None, ): """Update record in stat DB for the finished backup""" if not backup_end: backup_end = datetime2isodate() if backup_duration is None: try: # get duration using start of backup datetime backup_duration = ( isodate2datetime(backup_end) - isodate2datetime(self.query("select backup_start from stats where rowid=?", (rowid,))[0]["backup_start"]) ).seconds / 3600.0 except: backup_duration = None # update stat record self.db.execute( """\ update stats set total_files_count=?,written_files_count=?,total_bytes=?,written_bytes=?,log=?,status=?,backup_end=?,backup_duration=?,backup_location=? where rowid = ? """, ( total_files_count, written_files_count, total_bytes, written_bytes, log, status, backup_end, backup_duration, backup_location, rowid, ), ) self.db.commit() def add( self, backup_name="", server_name="", description="", backup_start=None, backup_end=None, backup_duration=None, total_files_count=None, written_files_count=None, total_bytes=None, written_bytes=None, status="draft", log="", TYPE="", backup_location=None, ): """Add a new backup record to the database.""" if not backup_start: backup_start = datetime2isodate() if not backup_end: backup_end = datetime2isodate() cur = self.db.execute( """\ insert into stats ( backup_name, server_name, description, backup_start, backup_end, backup_duration, total_files_count, written_files_count, total_bytes, written_bytes, status, log, backup_location, TYPE) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?) """, ( backup_name, server_name, description, backup_start, backup_end, backup_duration, total_files_count, written_files_count, total_bytes, written_bytes, status, log, backup_location, TYPE, ), ) self.db.commit() return cur.lastrowid def query(self, query, args=(), one=False): """ execute la requete query sur la db et renvoie un tableau de dictionnaires """ cur = self.db.execute(query, args) rv = [dict((cur.description[idx][0], value) for idx, value in enumerate(row)) for row in cur.fetchall()] return (rv[0] if rv else None) if one else rv def last_backups(self, backup_name, count=30): """Display last N backups for a given backup_name.""" if backup_name: cur = self.db.execute("select * from stats where backup_name=? order by backup_end desc limit ?", (backup_name, count)) else: cur = self.db.execute("select * from stats order by backup_end desc limit ?", (count,)) def fcb(fieldname, value): if fieldname in ("backup_start", "backup_end"): return time2display(isodate2datetime(value)) elif "bytes" in fieldname: return convert_bytes(value) elif "count" in fieldname: return splitThousands(value, " ", ".") elif "backup_duration" in fieldname: return hours_minutes(value) else: return value # for r in self.query('select * from stats where backup_name=? order by backup_end desc limit ?',(backup_name,count)): print((pp(cur, None, 1, fcb))) def fcb(self, fields, fieldname, value): """Format callback for HTML table display.""" if fieldname in ("backup_start", "backup_end"): return time2display(isodate2datetime(value)) elif "bytes" in fieldname: return convert_bytes(value) elif "count" in fieldname: return splitThousands(value, " ", ".") elif "backup_duration" in fieldname: return hours_minutes(value) else: return value def as_html(self, cur): """Convert cursor to HTML table.""" if cur: return html_table(cur, self.fcb) else: return html_table(self.db.execute("select * from stats order by backup_start asc"), self.fcb)