diff --git a/plugins/data/data_command.lua b/plugins/data/data_command.lua index a868b792c22..6923d80c1e8 100644 --- a/plugins/data/data_command.lua +++ b/plugins/data/data_command.lua @@ -1,8 +1,10 @@ local dat = {} local info, ver - local datread = require("data/load_dat") -datread, ver = datread.open("command.dat", nil) +do + local convert = require("data/button_char") + datread, ver = datread.open("command.dat", "# Command List%-Shorthand", convert) +end function dat.check(set, softlist) if softlist or not datread then @@ -13,8 +15,7 @@ function dat.check(set, softlist) if not status or not info then return nil end - local convert = require("data/button_char") - info = "#jf\n" .. convert(info) + info = "#jf\n" .. info return "Command" end diff --git a/plugins/data/data_mameinfo.lua b/plugins/data/data_mameinfo.lua index 5f42fe6a36f..9246c393e3e 100644 --- a/plugins/data/data_mameinfo.lua +++ b/plugins/data/data_mameinfo.lua @@ -1,7 +1,7 @@ local dat = {} local ver, info local datread = require("data/load_dat") -datread, ver = datread.open("mameinfo.dat", "# MAMEINFO.DAT") +datread, ver = datread.open("mameinfo.dat", "# MAMEINFO.DAT", function(str) return str:gsub("\n\n", "\n") end) function dat.check(set, softlist) if softlist or not datread then @@ -17,7 +17,6 @@ function dat.check(set, softlist) if drvinfo then info = info .. "\n\n--- DRIVER INFO ---\nDriver: " .. sourcefile .. "\n\n" .. drvinfo end - info = info:gsub("\n\n", "\n") return "Mameinfo" end diff --git a/plugins/data/data_messinfo.lua b/plugins/data/data_messinfo.lua index 86949e30431..f77463f1617 100644 --- a/plugins/data/data_messinfo.lua +++ b/plugins/data/data_messinfo.lua @@ -2,7 +2,7 @@ local dat = {} local ver, info local datread = require("data/load_dat") -datread, ver = datread.open("messinfo.dat", "# MESSINFO.DAT") +datread, ver = datread.open("messinfo.dat", "# MESSINFO.DAT", function(str) return str:gsub("\n\n", "\n") end) function dat.check(set, softlist) if softlist or not datread then @@ -18,7 +18,6 @@ function dat.check(set, softlist) if drvinfo then info = info .. "\n\n--- DRIVER INFO ---\nDriver: " .. sourcefile .. "\n\n" .. drvinfo end - info = info:gsub("\n\n", "\n") return "Messinfo" end diff --git a/plugins/data/load_dat.lua b/plugins/data/load_dat.lua index 3e9f88dab80..d294d81cf0a 100644 --- a/plugins/data/load_dat.lua +++ b/plugins/data/load_dat.lua @@ -1,95 +1,163 @@ +local sql = require("lsqlite3") local datfile = {} +local db = sql.open(mame_manager:ui():options().entries.historypath:value():match("([^;]+)") .. "/history.db") +if db then + local found = false + db:exec("select * from sqllite_master where name = version", function() found = true return 0 end) + if not found then + db:exec([[ + CREATE TABLE version ( + version VARCHAR NOT NULL, + datfile VARCHAR UNIQUE NOT NULL)]]) + end +end -function datfile.open(file, vertag) - local data = {} - local ver +function datfile.open(file, vertag, fixupcb) + if not db then + return nil + end + local function read(tag1, tag2, set) + local data + local stmt = db:prepare("SELECT f.data FROM \"" .. file .. "_idx\" AS fi, \"" .. file .. [[" + AS f WHERE fi.type = ? AND fi.val = ? AND fi.romset = ? AND f.rowid = fi.data]]) + stmt:bind_values(tag1, tag2, set) + if stmt:step() == sql.ROW then + data = stmt:get_value(0) + end + stmt:finalize() + return data + end + + local ver, dbver local filepath local fh + for path in mame_manager:ui():options().entries.historypath:value():gmatch("([^;]+)") do filepath = lfs.env_replace(path) .. "/" .. file - fh = io.open(filepath, "rb") + fh = io.open(filepath, "r") if fh then break end + return end - if not fh then + -- remove unsafe chars from file for use in sql statement + file = file:gsub("[^%w%._]", "") + + local stmt = db:prepare("SELECT version FROM version WHERE datfile = ?") + stmt:bind_values(file) + if stmt:step() == sql.ROW then + dbver = stmt:get_value(0) + end + stmt:finalize() + + if not dbver then + db:exec("CREATE TABLE \"" .. file .. [[_idx" ( + type VARCHAR NOT NULL, + val VARCHAR NOT NULL, + romset VARCHAR NOT NULL, + data INTEGER NOT NULL)]]) + db:exec("CREATE TABLE \"" .. file .. "\" (data CLOB NOT NULL)") + db:exec("CREATE INDEX typeval ON \"" .. file .. "_idx\"(type, val)") + elseif not fh then + -- data in database but missing file, just use what we have + return read, dbver + elseif not fh and not dbver then return nil end - do - local inblock = false - local buffer = fh:read("a") - if vertag then - local match = buffer:match(vertag .. "%s*([^%s]+)") + + if vertag then + for line in fh:lines() do + local match = line:match(vertag .. "%s*([^%s]+)") if match then ver = match + break end end + else + -- use file ctime for version + ver = tostring(lfs.attributes(filepath, "change")) + end + if ver == dbver then + return read, dbver + end + + if dbver then + db:exec("DELETE FROM \"" .. file .. "\"") + db:exec("DELETE FROM \"" .. file .. "_idx\"") + stmt = db:prepare("UPDATE version SET version = ? WHERE datfile = ?") + else + stmt = db:prepare("INSERT INTO version VALUES (?, ?)") + end + stmt:bind_values(ver, file) + stmt:step() + stmt:finalize() + + do + local inblock = false + fh:seek("set") + local buffer = fh:read("a") + db:exec("BEGIN TRANSACTION") local function gmatchpos() local pos = 1 local function iter() - local spos, epos = buffer:find("\n$", pos, true) - if not spos then - return nil + local tag1, tag2, data, start, inblock = false + while not data do + local spos, epos, match = buffer:find("\n($[^\n]*)", pos) + if not spos then + return nil + end + if match ~= "$end" and not inblock then + if not tag1 then + tag1 = match + else + tag2 = match + start = epos + 1 + inblock = true + end + elseif inblock == true then + data = buffer:sub(start, spos) + inblock = false + end + pos = epos end - spos = spos + 1 - local spos, epos, match = buffer:find("([^\n]+)", spos) - pos = epos + 1 - return match, pos, iter + return tag1, tag2, data end return iter end - for line, epos, iter in gmatchpos() do - - local flag = line:sub(1, 1) - if flag ~= "#" then - if flag == "$" then - if line:sub(1, 4) == "$end" then - inblock = false - elseif not inblock then - local tag, set = line:match("^%$([^%s=]+)=?([^%s]*)") - if set and set ~= "" then - local tags = {} - local sets = {} - local tag1 = "" - tag:gsub("([^,]+)", function(s) tags[#tags + 1] = s end) - set:gsub("([^,]+)", function(s) sets[#sets + 1] = s end) - repeat - tag1, epos = iter() - until tag1:sub(1, 1) == "$" - tag1 = tag1:match("^$([^%s]*)") - if not data[tag1] then - data[tag1] = {} - end - for num1, tag2 in pairs(tags) do - if not data[tag1][tag2] then - data[tag1][tag2] = {} - end - for num2, set in pairs(sets) do - data[tag1][tag2][set] = epos - end + for info1, info2, data in gmatchpos() do + local tag, set = info1:match("^%$([^%s=]+)=?([^%s]*)") + if set and set ~= "" then + local tags = {} + local sets = {} + tag:gsub("([^,]+)", function(s) tags[#tags + 1] = s end) + set:gsub("([^,]+)", function(s) sets[#sets + 1] = s end) + if #tags > 0 and #sets > 0 then + local tag1 = info2:match("^$([^%s]*)") + if fixupcb then + data = fixupcb(data) + end + stmt = db:prepare("INSERT INTO \"" .. file .. "\" VALUES (?)") + stmt:bind_values(data) + stmt:step() + local row = stmt:last_insert_rowid() + stmt:finalize() + for num1, tag2 in pairs(tags) do + for num2, set in pairs(sets) do + if fixupcb then + fixupcb(data) end + stmt = db:prepare("INSERT INTO \"" .. file .. "_idx\" VALUES (?, ?, ?, ?)") + stmt:bind_values(tag1, tag2, set, row) + stmt:step() + stmt:finalize() end - inblock = true end end end end + db:exec("END TRANSACTION") end fh:close() - fh = io.open(filepath, "r") - local function read(tag1, tag2, set) - local output = {} - if not data[tag1][tag2][set] then - return nil - end - fh:seek("set", data[tag1][tag2][set]) - for line in fh:lines() do - if line:sub(1, 4) == "$end" then - return table.concat(output, "\n") - end - output[#output + 1] = line - end - end return read, ver end