summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/timer/timer_persist.lua
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/timer/timer_persist.lua')
-rw-r--r--plugins/timer/timer_persist.lua249
1 files changed, 249 insertions, 0 deletions
diff --git a/plugins/timer/timer_persist.lua b/plugins/timer/timer_persist.lua
new file mode 100644
index 00000000000..cb24b485dcb
--- /dev/null
+++ b/plugins/timer/timer_persist.lua
@@ -0,0 +1,249 @@
+-- license:BSD-3-Clause
+-- copyright-holders:Vas Crabb
+
+local sqlite3 = require('lsqlite3')
+
+
+local function check_schema(db)
+ local create_statement =
+ [[CREATE TABLE timer (
+ driver VARCHAR(32) NOT NULL,
+ softlist VARCHAR(24) NOT NULL DEFAULT '',
+ software VARCHAR(16) NOT NULL DEFAULT '',
+ total_time INTEGER NOT NULL DEFAULT 0,
+ play_count INTEGER NOT NULL DEFAULT 0,
+ emu_sec INTEGER NOT NULL DEFAULT 0,
+ emu_nsec INTEGER NOT NULL DEFAULT 0,
+ PRIMARY KEY (driver, softlist, software));]]
+
+ -- create table if it doesn't exist yet
+ local table_found = false
+ db:exec(
+ [[SELECT * FROM sqlite_master WHERE type = 'table' AND name='timer';]],
+ function()
+ table_found = true
+ end)
+ if not table_found then
+ emu.print_verbose('Creating timer database table')
+ db:exec(create_statement)
+ return
+ end
+
+ -- check recently added columns
+ local have_softlist = false
+ local have_emu_sec = false
+ local have_emu_nsec = false
+ db:exec(
+ [[PRAGMA table_info(timer);]],
+ function(udata, n, vals, cols)
+ for i, name in ipairs(cols) do
+ if name == 'name' then
+ if vals[i] == 'softlist' then
+ have_softlist = true
+ elseif vals[i] == 'emu_sec' then
+ have_emu_sec = true
+ elseif vals[i] == 'emu_nsec' then
+ have_emu_nsec = true
+ end
+ return 0
+ end
+ end
+ return 0
+ end)
+ if not have_softlist then
+ emu.print_verbose('Adding softlist column to timer database')
+ db:exec([[ALTER TABLE timer ADD COLUMN softlist VARCHAR(24) NOT NULL DEFAULT '';]])
+ local to_split = { }
+ db:exec(
+ [[SELECT DISTINCT software FROM timer WHERE software LIKE '%:%';]],
+ function(udata, n, vals)
+ table.insert(to_split, vals[1])
+ return 0
+ end)
+ if #to_split > 0 then
+ local update = db:prepare([[UPDATE timer SET softlist = ?, software = ? WHERE software = ?;]])
+ for i, softname in ipairs(to_split) do
+ local x, y = softname:find(':')
+ local softlist = softname:sub(1, x - 1)
+ local software = softname:sub(y + 1)
+ update:bind_values(softlist, software, softname)
+ update:step()
+ update:reset()
+ end
+ end
+ end
+ if not have_emu_sec then
+ emu.print_verbose('Adding emu_sec column to timer database')
+ db:exec([[ALTER TABLE timer ADD COLUMN emu_sec INTEGER NOT NULL DEFAULT 0;]])
+ end
+ if not have_emu_nsec then
+ emu.print_verbose('Adding emu_nsec column to timer database')
+ db:exec([[ALTER TABLE timer ADD COLUMN emu_nsec INTEGER NOT NULL DEFAULT 0;]])
+ end
+
+ -- check the required columns are in the primary key
+ local index_name
+ db:exec(
+ [[SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'timer';]],
+ function(udata, n, vals)
+ index_name = vals[1]
+ end)
+ local index_good
+ if index_name then
+ local driver_indexed = false
+ local softlist_indexed = false
+ local software_indexed = false
+ db:exec(
+ string.format([[PRAGMA index_info('%s');]], index_name), -- can't use prepared statement for PRAGMA
+ function(udata, n, vals, cols)
+ for i, name in ipairs(cols) do
+ if name == 'name' then
+ if vals[i] == 'driver' then
+ driver_indexed = true
+ elseif vals[i] == 'softlist' then
+ softlist_indexed = true
+ elseif vals[i] == 'software' then
+ software_indexed = true
+ end
+ return 0
+ end
+ end
+ return 0
+ end)
+ index_good = driver_indexed and softlist_indexed and software_indexed
+ end
+
+ -- if the required columns are not indexed, migrate to a new table with desired primary key
+ if not index_good then
+ emu.print_verbose('Re-indexing timer database table')
+ db:exec([[DROP TABLE IF EXISTS timer_old;]])
+ db:exec([[ALTER TABLE timer RENAME TO timer_old;]])
+ db:exec(create_statement)
+ db:exec(
+ [[INSERT
+ INTO timer (driver, softlist, software, total_time, play_count, emu_sec, emu_nsec)
+ SELECT driver, softlist, software, total_time, play_count, emu_sec, emu_nsec FROM timer_old;]])
+ db:exec([[DROP TABLE timer_old;]])
+ end
+end
+
+
+local function open_database()
+ -- make sure settings directory exists
+ local dir = manager.machine.options.entries.homepath:value():match('([^;]+)') .. '/timer'
+ local attr = lfs.attributes(dir)
+ if not attr then
+ lfs.mkdir(dir)
+ elseif attr.mode ~= 'directory' then
+ emu.print_error(string.format('Error opening timer database: "%s" is not a directory', dir))
+ return nil
+ end
+
+ -- open database
+ local filename = dir .. '/timer.db'
+ local db = sqlite3.open(filename)
+ if not db then
+ emu.print_error(string.format('Error opening timer database file "%s"', filename))
+ return nil
+ end
+
+ -- make sure schema is up-to-date
+ check_schema(db)
+ return db
+end
+
+
+local function get_software()
+ local softname = emu.softname()
+ local i, j = softname:find(':')
+ if i then
+ return softname:sub(1, i - 1), softname:sub(j + 1)
+ else
+ -- FIXME: need a way to get the implicit software list when no colon in the option value
+ return '', softname
+ end
+end
+
+
+local function get_current(db)
+ local statement = db:prepare(
+ [[SELECT
+ total_time, play_count, emu_sec, emu_nsec
+ FROM timer
+ WHERE driver = ? AND softlist = ? AND software = ?;]])
+ statement:bind_values(emu.romname(), get_software())
+ local result
+ if statement:step() == sqlite3.ROW then
+ result = statement:get_named_values()
+ end
+ statement:reset()
+ return result
+end
+
+
+local lib = { }
+
+function lib:start_session()
+ -- open database
+ local db = open_database()
+ if not db then
+ return 0, 0, emu.attotime()
+ end
+
+ -- get existing values
+ local row = get_current(db)
+ local update
+ if row then
+ update = db:prepare(
+ [[UPDATE timer
+ SET play_count = play_count + 1
+ WHERE driver = ? AND softlist = ? AND software = ?;]])
+ else
+ row = { total_time = 0, play_count = 0, emu_sec = 0, emu_nsec = 0 }
+ update = db:prepare(
+ [[INSERT
+ INTO timer (driver, softlist, software, total_time, play_count, emu_sec, emu_nsec)
+ VALUES (?, ?, ?, 0, 1, 0, 0);]])
+ end
+ update:bind_values(emu.romname(), get_software())
+ update:step()
+ update:reset()
+ return row.total_time, row.play_count + 1, emu.attotime.from_seconds(row.emu_sec) + emu.attotime.from_nsec(row.emu_nsec)
+end
+
+function lib:update_totals(start)
+ -- open database
+ local db = open_database()
+ if not db then
+ return
+ end
+
+ -- get existing values
+ local row = get_current(db)
+ if not row then
+ row = { total_time = 0, play_count = 1, emu_sec = 0, emu_nsec = 0 }
+ end
+
+ -- calculate new totals
+ local emu_total = emu.attotime.from_seconds(row.emu_sec) + emu.attotime.from_nsec(row.emu_nsec) + manager.machine.time
+ row.total_time = os.time() - start + row.total_time
+ row.emu_sec = emu_total.seconds
+ row.emu_nsec = emu_total.nsec
+
+ -- update database
+ local update = db:prepare(
+ [[INSERT OR REPLACE
+ INTO timer (driver, softlist, software, total_time, play_count, emu_sec, emu_nsec)
+ VALUES (?, ?, ?, ?, ?, ?, ?);]])
+ local softlist, software = get_software()
+ update:bind_values(emu.romname(), softlist, software, row.total_time, row.play_count, row.emu_sec, row.emu_nsec)
+ update:step()
+ update:reset()
+
+ -- close database
+ if db:close() ~= sqlite3.OK then
+ emu.print_error('Error closing timer database')
+ end
+end
+
+return lib