diff options
author | 2021-11-06 05:20:59 +1100 | |
---|---|---|
committer | 2021-11-06 05:20:59 +1100 | |
commit | 07e55935cf49c94a94dcc75d8240d0733d1ed5d7 (patch) | |
tree | fa60ebf78fee04fd57d9c8b8665f02729ef48a43 /plugins/timer/timer_persist.lua | |
parent | 3bb8e3adc7dbd6a9e57771047011efc3c54378ac (diff) |
plugins: Rewrote timer plugin fixing multiple issues.
Added emulated time recording as well as wall clock time.
Fixed recording time for multiple software items per system. An
incorrect constraint on the database table meant that time was only
being recorded for a single software item per system.
Detect the "empty" driver so the time spent at the selection menu isn't
recorded (you'd get multiple entries for this due to the way options
leak when returning to the system selection menu).
Included schema migration code to update existing timer plugin
databases. Also replaced some unnecessary floating point code with
integer maths, added log messages, and made the plugin unload unload its
database access code during emulation.
Changed other plugins' use of paths with trailing slashes as this causes
stat to fail on Windows.
Diffstat (limited to 'plugins/timer/timer_persist.lua')
-rw-r--r-- | plugins/timer/timer_persist.lua | 249 |
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..5ed33f03eb4 --- /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 = emu.subst_env(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 |