]>
Commit | Line | Data |
---|---|---|
91aead36 | 1 | #!/usr/bin/python3 |
37e24fbf MT |
2 | ############################################################################### |
3 | # # | |
4 | # ddns - A dynamic DNS client for IPFire # | |
5 | # Copyright (C) 2014 IPFire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
21 | ||
22 | import datetime | |
63e16fee | 23 | import os |
37e24fbf MT |
24 | import sqlite3 |
25 | ||
26 | # Initialize the logger. | |
27 | import logging | |
28 | logger = logging.getLogger("ddns.database") | |
29 | logger.propagate = 1 | |
30 | ||
31 | class DDNSDatabase(object): | |
32 | def __init__(self, core, path): | |
33 | self.core = core | |
63e16fee | 34 | self.path = path |
37e24fbf | 35 | |
63e16fee MT |
36 | # We won't open the connection to the database directly |
37 | # so that we do not do it unnecessarily. | |
38 | self._db = None | |
37e24fbf | 39 | |
37e24fbf MT |
40 | def _open_database(self, path): |
41 | logger.debug("Opening database %s" % path) | |
42 | ||
43 | exists = os.path.exists(path) | |
44 | ||
45 | conn = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) | |
46 | conn.isolation_level = None | |
47 | ||
63e16fee | 48 | if not exists and self.is_writable(): |
37e24fbf MT |
49 | logger.debug("Initialising database layout") |
50 | c = conn.cursor() | |
51 | c.executescript(""" | |
52 | CREATE TABLE updates ( | |
53 | hostname TEXT NOT NULL, | |
54 | status TEXT NOT NULL, | |
55 | message TEXT, | |
56 | timestamp timestamp NOT NULL | |
57 | ); | |
58 | ||
59 | CREATE TABLE settings ( | |
60 | k TEXT NOT NULL, | |
61 | v TEXT NOT NULL | |
62 | ); | |
a2857a13 MT |
63 | |
64 | CREATE INDEX idx_updates_hostname ON updates(hostname); | |
37e24fbf MT |
65 | """) |
66 | c.execute("INSERT INTO settings(k, v) VALUES(?, ?)", ("version", "1")) | |
67 | ||
68 | return conn | |
69 | ||
63e16fee MT |
70 | def is_writable(self): |
71 | # Check if the database file exists and is writable. | |
72 | ret = os.access(self.path, os.W_OK) | |
73 | if ret: | |
74 | return True | |
75 | ||
76 | # If not, we check if we are able to write to the directory. | |
77 | # In that case the database file will be created in _open_database(). | |
78 | return os.access(os.path.dirname(self.path), os.W_OK) | |
79 | ||
37e24fbf | 80 | def _execute(self, query, *parameters): |
63e16fee MT |
81 | if self._db is None: |
82 | self._db = self._open_database(self.path) | |
83 | ||
37e24fbf MT |
84 | c = self._db.cursor() |
85 | try: | |
86 | c.execute(query, parameters) | |
87 | finally: | |
88 | c.close() | |
89 | ||
90 | def add_update(self, hostname, status, message=None): | |
63e16fee MT |
91 | if not self.is_writable(): |
92 | logger.warning("Could not log any updates because the database is not writable") | |
93 | return | |
94 | ||
37e24fbf MT |
95 | self._execute("INSERT INTO updates(hostname, status, message, timestamp) \ |
96 | VALUES(?, ?, ?, ?)", hostname, status, message, datetime.datetime.utcnow()) | |
97 | ||
98 | def log_success(self, hostname): | |
99 | logger.debug("Logging successful update for %s" % hostname) | |
100 | ||
101 | return self.add_update(hostname, "success") | |
102 | ||
103 | def log_failure(self, hostname, exception): | |
104 | if exception: | |
105 | message = "%s: %s" % (exception.__class__.__name__, exception.reason) | |
106 | else: | |
107 | message = None | |
108 | ||
109 | logger.debug("Logging failed update for %s: %s" % (hostname, message or "")) | |
110 | ||
111 | return self.add_update(hostname, "failure", message=message) | |
112 | ||
112d3fb8 MT |
113 | def last_update(self, hostname, status=None): |
114 | """ | |
115 | Returns the timestamp of the last update (with the given status code). | |
116 | """ | |
f62fa5ba MT |
117 | if self._db is None: |
118 | self._db = self._open_database(self.path) | |
119 | ||
37e24fbf MT |
120 | c = self._db.cursor() |
121 | ||
122 | try: | |
112d3fb8 MT |
123 | if status: |
124 | c.execute("SELECT timestamp FROM updates WHERE hostname = ? AND status = ? \ | |
125 | ORDER BY timestamp DESC LIMIT 1", (hostname, status)) | |
126 | else: | |
127 | c.execute("SELECT timestamp FROM updates WHERE hostname = ? \ | |
128 | ORDER BY timestamp DESC LIMIT 1", (hostname,)) | |
129 | ||
130 | for row in c: | |
131 | return row[0] | |
132 | finally: | |
133 | c.close() | |
134 | ||
135 | def last_update_status(self, hostname): | |
136 | """ | |
137 | Returns the update status of the last update. | |
138 | """ | |
f62fa5ba MT |
139 | if self._db is None: |
140 | self._db = self._open_database(self.path) | |
141 | ||
112d3fb8 MT |
142 | c = self._db.cursor() |
143 | ||
144 | try: | |
145 | c.execute("SELECT status FROM updates WHERE hostname = ? \ | |
146 | ORDER BY timestamp DESC LIMIT 1", (hostname,)) | |
147 | ||
148 | for row in c: | |
149 | return row[0] | |
150 | finally: | |
151 | c.close() | |
152 | ||
153 | def last_update_failure_message(self, hostname): | |
154 | """ | |
155 | Returns the reason string for the last failed update (if any). | |
156 | """ | |
f62fa5ba MT |
157 | if self._db is None: |
158 | self._db = self._open_database(self.path) | |
159 | ||
112d3fb8 MT |
160 | c = self._db.cursor() |
161 | ||
162 | try: | |
163 | c.execute("SELECT message FROM updates WHERE hostname = ? AND status = ? \ | |
164 | ORDER BY timestamp DESC LIMIT 1", (hostname, "failure")) | |
37e24fbf MT |
165 | |
166 | for row in c: | |
167 | return row[0] | |
168 | finally: | |
169 | c.close() |