c65b4b93a905d2dd897ca3ac9e69c88ebc32231c
[lilass.git] / database.py
1 import sqlite3
2 import os.path
3 from binascii import hexlify, unhexlify
4 from screen import ScreenSetup, Resolution, RelativeScreenPosition
5
6 class InvalidDBFile(Exception):
7     pass
8
9 class Database:
10     def __init__(self, dbfilename):
11         self._create = False
12         assert(os.path.isdir(os.path.dirname(dbfilename)))
13         if not os.path.isfile(dbfilename):
14             if os.path.lexists(dbfilename):
15                 raise Exception("Database must be a file: '%s'" % dbfilename)
16             # database will be created on __enter__ because we need a dbconnection for it
17             self._create = True
18         self._dbfilename = dbfilename
19         self._connection = None
20     def __enter__(self):
21         self._connection = sqlite3.connect(self._dbfilename)
22         c = self._c()
23         if self._create:
24             c.execute("""CREATE TABLE meta (key text, value text, PRIMARY KEY(key))""")
25             c.execute("""INSERT INTO meta VALUES ('version', '1')""")
26             c.execute("""CREATE TABLE known_configs (edid blob, resinternal text, resexternal text, mode text, ext_is_primary integer, PRIMARY KEY(edid))""")
27             # edid in binary format
28             # resindernal, resexternal = "1024x768" or NULL if display is off
29             # mode: the enum text of screen.RelativeScreenPosition or NULL if one display is off
30         else: # check compatibility
31             dbversion = int(self._getMeta("version"))
32             if dbversion > 1:
33                 raise InvalidDBFile("Database is too new: Version %d. Please update lilass." % dbversion)
34         return self
35     def _getMeta(self, key):
36         c = self._c()
37         c.execute("""SELECT value FROM meta WHERE key=?""", (key,))
38         got = c.fetchone()
39         if got is None: # to differentiate between the value being NULL and the row being not there
40             raise KeyError("""Key "%s" is not in the meta table.""" % key)
41         assert c.fetchone() is None # uniqueness
42         assert len(got) == 1
43         return got[0]
44     def putConfig(self, extconn_edid, conf):
45         c = self._c()
46         b_edid = unhexlify(extconn_edid)
47         intres = conf.intResolution.forDatabase() if conf.intResolution else None
48         extres = conf.extResolution.forDatabase() if conf.extResolution else None
49         mode = conf.relPosition.text if conf.relPosition else None
50         extprim = int(conf.extIsPrimary) # False => 0, True => 1
51         c.execute("""INSERT OR REPLACE INTO known_configs VALUES (?,?,?,?,?)""", (b_edid, intres, extres, mode, extprim))
52     def getConfig(self, extconn_edid):
53         c = self._c()
54         b_edid = unhexlify(extconn_edid)
55         c.execute("""SELECT * FROM known_configs WHERE edid=?""", (b_edid,))
56         result = c.fetchone()
57         if result is None:
58             return None
59         assert c.fetchone() is None # uniqueness
60         _, intres, extres, mode, extprim = result
61         intres = Resolution.fromDatabase(intres) # this method is safe for NULLs
62         extres = Resolution.fromDatabase(extres)
63         mode = RelativeScreenPosition(mode)
64         extprim = bool(extprim) # 0 => False, 1 => True
65         return ScreenSetup(intres, extres, mode, extprim)
66     def __exit__(self, type, value, tb):
67         if self._connection:
68             self._connection.commit()
69             self._connection.close()
70     def _c(self):
71         assert(self._connection)
72         return self._connection.cursor()