mame/plugins/data/database.lua
Vas Crabb 923ef2c25d -plugins.data: Reworked the code substantialy.
* Use the plugin data folder for storing the cache.  The history folder
  may be read-only or shared with different configurations.
* Don't create the cache database or surrounding folder if there's
  nothing to store in it.
* Actually use prepared queries multiple times rather than always
  destroying them after a single use.
* Added proper error checking for most database operations.
* Improved query performance by avoiding outer joins and table scans.

-bus/nubus: Made the Macintosh Display Cards map the blue channel to
 white with monochrome monitors.  Also added logging for PLL
 configuration to help debug how CRTC and RAMDAC clocks work in the
 future.
2022-06-25 04:57:24 +10:00

160 lines
3.8 KiB
Lua

local sql = require('lsqlite3')
local db
local function check_db_result(msg)
if db:errcode() > sql.OK then
emu.print_error(string.format('Error: %s (%s - %s)', msg, db:errcode(), db:errmsg()))
return false
end
return true
end
local function settings_path()
return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/data'
end
local function check_version_table()
local found = false
db:exec(
[[SELECT COUNT(*) FROM sqlite_master WHERE name = 'version' AND type = 'table';]],
function (udata, cols, values, names)
found = tonumber(values[1]) > 0
return 0
end)
if check_db_result('checking for "version" table') and (not found) then
db:exec(
[[CREATE TABLE version (
datfile VARCHAR NOT NULL,
version VARCHAR NOT NULL,
PRIMARY KEY (datfile));]])
found = check_db_result('creating "version" table')
end
if not found then
db:close()
db = nil
end
end
local function open_existing()
db = sql.open(settings_path() .. '/history.db', sql.OPEN_READWRITE)
if db then
check_version_table()
end
return db
end
local function open_create()
-- first try to create settings directory
local dir = settings_path()
local attr = lfs.attributes(dir)
if (not attr) and (not lfs.mkdir(dir)) then
emu.print_error(string.format('Error creating data plugin settings directory "%s"', dir))
elseif attr and (attr.mode ~= 'directory') then
emu.print_error(string.format('Error opening data plugin database: "%s" is not a directory', dir))
else
-- now try to open the database
local dbpath = dir .. '/history.db'
db = sql.open(dbpath, sql.OPEN_READWRITE | sql.OPEN_CREATE)
if not db then
emu.print_error(string.format('Error opening data plugin database "%s"', dbpath))
else
check_version_table()
end
end
return db
end
local dbtable = {
ROW = sql.ROW,
BUSY = sql.BUSY,
DONE = sql.DONE,
check = check_db_result }
function dbtable.sanitize_name(name)
return name:gsub('[^%w%."]', '_')
end
function dbtable.get_version(filename)
-- don't try to create the database here, just return nil if it doesn't exist
if (not db) and (not open_existing()) then
return nil
end
local query = db:prepare([[SELECT version FROM version WHERE datfile = ?;]])
query:bind_values(filename)
local result
while result == nil do
local status = query:step()
if status == sql.ROW then
result = query:get_value(0)
elseif status ~= sql.BUSY then
break
end
end
query:finalize()
return result
end
function dbtable.set_version(filename, version)
if (not db) and (not open_create()) then
return nil
end
local query
if version ~= nil then
query = db:prepare(
[[INSERT INTO version (datfile, version) VALUES (?1, ?2)
ON CONFLICT(datfile) DO UPDATE set version = ?2;]])
query:bind_values(filename, version)
else
query = db:prepare([[DELETE FROM version WHERE datfile = ?1;]])
query:bind_values(filename)
end
local result
while result == nil do
local status = query:step()
if status == sqlite3.DONE then
result = true
elseif result ~= sqlite3.ROW then
result = false
end
end
query:finalize()
return result
end
function dbtable.prepare(...)
if (not db) and open_create() then
dbtable.prepare = function (...) return db:prepare(...) end
end
if db then
return db:prepare(...)
else
return nil
end
end
function dbtable.exec(...)
if (not db) and open_create() then
dbtable.exec = function (...) return db:exec(...) end
end
if db then
return db:exec(...)
else
return nil
end
end
function dbtable.open_data_file(file)
local fh, filepath
for path in mame_manager.ui.options.entries.historypath:value():gmatch('([^;]+)') do
filepath = emu.subst_env(path) .. '/' .. file
fh = io.open(filepath, 'r')
if fh then
break
end
end
return fh, filepath, dbtable.sanitize_name(file), dbtable.get_version(file)
end
return dbtable