summaryrefslogtreecommitdiffstatshomepage
path: root/plugins/data/data_history.lua
blob: b233405e98428ca9acda27ecbc80a8a7911d3d89 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
local dat = {}

local db = require('data/database')
local ver, info
local file = 'history.xml'
local tablename

local function init()
	-- check for old history table
	if db.get_version('history.dat') then
		db.exec([[DROP TABLE "history.dat";]])
		db.exec([[DROP TABLE "history.dat_idx";]])
		db.set_version('history.dat', nil)
	end

	local fh, filepath, dbver
	fh, filepath, tablename, dbver = db.open_data_file(file)
	if not fh then
		if dbver then
			-- data in database but missing file, just use what we have
			ver = dbver
		end
		return
	end

	-- scan file for version
	for line in fh:lines() do
		local match = line:match('<history([^>]*)>')
		if match then
			match = match:match('version="([^"]*)"')
			if match then
				ver = match
				break
			end
		end
	end
	if (not ver) or (ver == dbver) then
		fh:close()
		ver = dbver
		return
	end

	if not dbver then
		db.exec(
			string.format(
				[[CREATE TABLE "%s_idx" (
					name VARCHAR NOT NULL,
					list VARCHAR NOT NULL,
					data INTEGER NOT NULL);]],
				tablename))
		db.check(string.format('creating %s index table', file))
		db.exec(string.format([[CREATE TABLE "%s" (data CLOB NOT NULL);]], tablename))
		db.check(string.format('creating %s data table', file))
		db.exec(
			string.format(
				[[CREATE INDEX "namelist_%s" ON "%s_idx" (name, list);]],
				tablename, tablename))
		db.check(string.format('creating %s name/list index', file))
	end

	local slaxml = require('xml')

	db.exec([[BEGIN TRANSACTION;]])
	if not db.check(string.format('starting %s transaction', file)) then
		fh:close()
		ver = dbver
		return
	end

	-- clean out previous data and update the version
	if dbver then
		db.exec(string.format([[DELETE FROM "%s";]], tablename))
		if not db.check(string.format('deleting previous %s data', file)) then
			db.exec([[ROLLBACK TRANSACTION;]])
			fh:close()
			ver = dbver
			return
		end
		db.exec(string.format([[DELETE FROM "%s_idx";]], tablename))
		if not db.check(string.format('deleting previous %s data', file)) then
			db.exec([[ROLLBACK TRANSACTION;]])
			fh:close()
			ver = dbver
			return
		end
	end
	db.set_version(file, ver)
	if not db.check(string.format('updating %s version', file)) then
		db.exec([[ROLLBACK TRANSACTION;]])
		fh:close()
		ver = dbver
		return
	end

	fh:seek('set')
	local buffer = fh:read('a')

	local lasttag
	local entry = {}
	local rowid

	local dataquery = db.prepare(
		string.format([[INSERT INTO "%s" (data) VALUES (?);]], tablename))
	local indexquery = db.prepare(
		string.format([[INSERT INTO "%s_idx" (name, list, data) VALUES (?, ?, ?);]], tablename))

	local parser = slaxml:parser{
		startElement = function(name)
			lasttag = name
			if name == 'entry' then
				entry = {}
				rowid = nil
			elseif (name == 'system') or (name == 'item') then
				table.insert(entry, {})
			end
		end,
		attribute = function(name, value)
			if (name == 'name') or (name == 'list') then
				entry[#entry][name] = value
			end
		end,
		text = function(text, cdata)
			if lasttag == 'text' then
				text = text:gsub('\r', '') -- strip carriage returns
				dataquery:bind_values(text)
				while true do
					local status = dataquery:step()
					if status == db.DONE then
						rowid = dataquery:last_insert_rowid();
						break
					elseif result == db.BUSY then
						emu.print_error(string.format('Database busy: inserting %s data', file))
						-- FIXME: how to abort parse and roll back?
						break
					elseif result ~= db.ROW then
						db.check(string.format('inserting %s data', file))
						break
					end
				end
				dataquery:reset()
			end
		end,
		closeElement = function(name)
			if (name == 'entry') and rowid then
				for num, entry in pairs(entry) do
					indexquery:bind_values(entry.name, entry.list or '', rowid)
					while true do
						local status = indexquery:step()
						if status == db.DONE then
							break
						elseif status == db.BUSY then
							emu.print_error(string.format('Database busy: inserting %s data', file))
							-- FIXME: how to abort parse and roll back?
							break
						elseif result ~= db.ROW then
							db.check(string.format('inserting %s data', file))
							break
						end
					end
					indexquery:reset()
				end
			end
		end
	}

	parser:parse(buffer, { stripWhitespace = true })
	dataquery:finalize()
	indexquery:finalize()
	fh:close()

	db.exec([[COMMIT TRANSACTION;]])
	if not db.check(string.format('committing %s transaction', file)) then
		db.exec([[ROLLBACK TRANSACTION;]])
		ver = dbver
	end
end

if db then
	init()
end

function dat.check(set, softlist)
	if not ver then
		return nil
	end

	info = nil
	local query = db.prepare(
		string.format(
			[[SELECT f.data
				FROM "%s_idx" AS fi LEFT JOIN "%s" AS f ON fi.data = f.rowid
				WHERE fi.name = ? AND fi.list = ?;]],
			tablename, tablename))
	query:bind_values(set, softlist or '')
	while not info do
		local status = query:step()
		if status == db.ROW then
			info = query:get_value(0)
		elseif status == db.DONE then
			break
		elseif status ~= db.BUSY then
			db.check(string.format('reading %s data', file))
			break
		end
	end
	query:finalize()
	return info and _p('plugin-data', 'History') or nil
end

function dat.get()
	return info
end

function dat.ver()
	return ver
end

return dat