-#!/usr/bin/python
+#!/usr/bin/python3
###############################################################################
# #
# ddns - A dynamic DNS client for IPFire #
###############################################################################
import datetime
-import os.path
+import os
import sqlite3
# Initialize the logger.
class DDNSDatabase(object):
def __init__(self, core, path):
self.core = core
+ self.path = path
- # Open the database file
- self._db = self._open_database(path)
-
- def __del__(self):
- self._close_database()
+ # We won't open the connection to the database directly
+ # so that we do not do it unnecessarily.
+ self._db = None
def _open_database(self, path):
logger.debug("Opening database %s" % path)
conn = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
conn.isolation_level = None
- if not exists:
+ if not exists and self.is_writable():
logger.debug("Initialising database layout")
c = conn.cursor()
c.executescript("""
k TEXT NOT NULL,
v TEXT NOT NULL
);
+
+ CREATE INDEX idx_updates_hostname ON updates(hostname);
""")
c.execute("INSERT INTO settings(k, v) VALUES(?, ?)", ("version", "1"))
return conn
- def _close_database(self):
- if self._db:
- self._db_close()
- self._db = None
+ def is_writable(self):
+ # Check if the database file exists and is writable.
+ ret = os.access(self.path, os.W_OK)
+ if ret:
+ return True
+
+ # If not, we check if we are able to write to the directory.
+ # In that case the database file will be created in _open_database().
+ return os.access(os.path.dirname(self.path), os.W_OK)
def _execute(self, query, *parameters):
+ if self._db is None:
+ self._db = self._open_database(self.path)
+
c = self._db.cursor()
try:
c.execute(query, parameters)
c.close()
def add_update(self, hostname, status, message=None):
+ if not self.is_writable():
+ logger.warning("Could not log any updates because the database is not writable")
+ return
+
self._execute("INSERT INTO updates(hostname, status, message, timestamp) \
VALUES(?, ?, ?, ?)", hostname, status, message, datetime.datetime.utcnow())
return self.add_update(hostname, "failure", message=message)
- def last_update(self, hostname, status="success"):
+ def last_update(self, hostname, status=None):
+ """
+ Returns the timestamp of the last update (with the given status code).
+ """
+ if self._db is None:
+ self._db = self._open_database(self.path)
+
+ c = self._db.cursor()
+
+ try:
+ if status:
+ c.execute("SELECT timestamp FROM updates WHERE hostname = ? AND status = ? \
+ ORDER BY timestamp DESC LIMIT 1", (hostname, status))
+ else:
+ c.execute("SELECT timestamp FROM updates WHERE hostname = ? \
+ ORDER BY timestamp DESC LIMIT 1", (hostname,))
+
+ for row in c:
+ return row[0]
+ finally:
+ c.close()
+
+ def last_update_status(self, hostname):
+ """
+ Returns the update status of the last update.
+ """
+ if self._db is None:
+ self._db = self._open_database(self.path)
+
+ c = self._db.cursor()
+
+ try:
+ c.execute("SELECT status FROM updates WHERE hostname = ? \
+ ORDER BY timestamp DESC LIMIT 1", (hostname,))
+
+ for row in c:
+ return row[0]
+ finally:
+ c.close()
+
+ def last_update_failure_message(self, hostname):
+ """
+ Returns the reason string for the last failed update (if any).
+ """
+ if self._db is None:
+ self._db = self._open_database(self.path)
+
c = self._db.cursor()
try:
- c.execute("SELECT timestamp FROM updates WHERE hostname = ? AND status = ? \
- ORDER BY timestamp DESC LIMIT 1", (hostname, status))
+ c.execute("SELECT message FROM updates WHERE hostname = ? AND status = ? \
+ ORDER BY timestamp DESC LIMIT 1", (hostname, "failure"))
for row in c:
return row[0]