abraunegg-onedrive/src/itemdb.d

248 lines
6 KiB
D
Raw Normal View History

import std.datetime, std.path, std.exception, std.string;
2015-09-14 23:56:14 +02:00
import sqlite;
enum ItemType
{
file,
dir
}
struct Item
{
string id;
string name;
ItemType type;
string eTag;
string cTag;
SysTime mtime;
string parentId;
string crc32;
}
final class ItemDatabase
{
// increment this for every change in the db schema
immutable int itemDatabaseVersion = 1;
2015-09-14 23:56:14 +02:00
Database db;
Statement insertItemStmt;
2015-09-17 16:28:24 +02:00
Statement updateItemStmt;
2015-09-14 23:56:14 +02:00
Statement selectItemByIdStmt;
Statement selectItemByParentIdStmt;
this(const(char)[] filename)
{
db = Database(filename);
db.exec("CREATE TABLE IF NOT EXISTS item (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
eTag TEXT NOT NULL,
cTag TEXT,
2015-09-14 23:56:14 +02:00
mtime TEXT NOT NULL,
2015-09-17 00:16:23 +02:00
parentId TEXT,
crc32 TEXT,
FOREIGN KEY (parentId) REFERENCES item (id) ON DELETE CASCADE
2015-09-14 23:56:14 +02:00
)");
db.exec("CREATE INDEX IF NOT EXISTS name_idx ON item (name)");
2015-09-17 00:16:23 +02:00
db.exec("PRAGMA foreign_keys = ON");
db.exec("PRAGMA recursive_triggers = ON");
db.setVersion(itemDatabaseVersion);
2015-09-17 16:28:24 +02:00
insertItemStmt = db.prepare("INSERT OR REPLACE INTO item (id, name, type, eTag, cTag, mtime, parentId, crc32) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
updateItemStmt = db.prepare("
2015-09-17 00:16:23 +02:00
UPDATE item
SET name = ?2, type = ?3, eTag = ?4, cTag = ?5, mtime = ?6, parentId = ?7, crc32 = ?8
WHERE id = ?1
");
2015-09-14 23:56:14 +02:00
selectItemByIdStmt = db.prepare("SELECT id, name, type, eTag, cTag, mtime, parentId, crc32 FROM item WHERE id = ?");
selectItemByParentIdStmt = db.prepare("SELECT id FROM item WHERE parentId = ?");
}
void insert(const ref Item item)
2015-09-14 23:56:14 +02:00
{
bindItem(item, insertItemStmt);
insertItemStmt.exec();
2015-09-14 23:56:14 +02:00
}
void update(const ref Item item)
2015-09-17 16:28:24 +02:00
{
bindItem(item, updateItemStmt);
updateItemStmt.exec();
2015-09-17 16:28:24 +02:00
}
void upsert(const ref Item item)
2015-09-17 16:28:24 +02:00
{
auto s = db.prepare("SELECT COUNT(*) FROM item WHERE id = ?");
s.bind(1, item.id);
2015-09-17 16:28:24 +02:00
auto r = s.exec();
Statement* stmt;
if (r.front[0] == "0") stmt = &insertItemStmt;
else stmt = &updateItemStmt;
bindItem(item, *stmt);
stmt.exec();
2015-09-17 16:28:24 +02:00
}
2015-09-18 21:42:27 +02:00
Item[] selectChildren(const(char)[] id)
2015-09-14 23:56:14 +02:00
{
2015-09-18 21:42:27 +02:00
selectItemByParentIdStmt.bind(1, id);
auto res = selectItemByParentIdStmt.exec();
Item[] items;
foreach (row; res) {
Item item;
bool found = selectById(row[0], item);
assert(found);
items ~= item;
2015-09-14 23:56:14 +02:00
}
2015-09-18 21:42:27 +02:00
return items;
2015-09-14 23:56:14 +02:00
}
bool selectById(const(char)[] id, out Item item)
{
selectItemByIdStmt.bind(1, id);
auto r = selectItemByIdStmt.exec();
if (!r.empty) {
item = buildItem(r);
return true;
}
return false;
}
bool selectByPath(const(char)[] path, out Item item)
{
2015-09-20 19:07:16 +02:00
path = "root/" ~ path.chompPrefix("."); // HACK
// initialize the search
2015-09-14 23:56:14 +02:00
string[2][] candidates; // [id, parentId]
auto s = db.prepare("SELECT id, parentId FROM item WHERE name = ?");
s.bind(1, baseName(path));
auto r = s.exec();
foreach (row; r) candidates ~= [row[0].dup, row[1].dup];
2015-09-20 19:07:16 +02:00
path = dirName(path);
if (path != ".") {
2015-09-14 23:56:14 +02:00
s = db.prepare("SELECT parentId FROM item WHERE id = ? AND name = ?");
2015-09-20 19:07:16 +02:00
// discard the candidates that do not have the correct parent
2015-09-14 23:56:14 +02:00
do {
2015-09-20 19:07:16 +02:00
s.bind(2, baseName(path));
2015-09-14 23:56:14 +02:00
string[2][] newCandidates;
newCandidates.reserve(candidates.length);
2015-09-20 19:07:16 +02:00
foreach (candidate; candidates) {
s.bind(1, candidate[1]);
r = s.exec();
if (!r.empty) {
string[2] c = [candidate[0], r.front[0].idup];
newCandidates ~= c;
2015-09-18 21:42:27 +02:00
}
2015-09-14 23:56:14 +02:00
}
candidates = newCandidates;
2015-09-20 19:07:16 +02:00
path = dirName(path);
} while (path != ".");
}
// reached the root
string[2][] newCandidates;
foreach (candidate; candidates) {
if (!candidate[1]) {
newCandidates ~= candidate;
}
2015-09-14 23:56:14 +02:00
}
2015-09-20 19:07:16 +02:00
candidates = newCandidates;
assert(candidates.length <= 1);
2015-09-14 23:56:14 +02:00
if (candidates.length == 1) return selectById(candidates[0][0], item);
return false;
}
void deleteById(const(char)[] id)
{
auto s = db.prepare("DELETE FROM item WHERE id = ?");
s.bind(1, id);
s.exec();
}
// returns true if the item has the specified parent
bool hasParent(T)(const(char)[] itemId, T parentId)
if (is(T : const(char)[]) || is(T : const(char[])[]))
{
auto s = db.prepare("SELECT parentId FROM item WHERE id = ?");
while (true) {
s.bind(1, itemId);
auto r = s.exec();
if (r.empty) break;
auto currParentId = r.front[0];
static if (is(T : const(char)[])) {
if (currParentId == parentId) return true;
} else {
foreach (id; parentId) if (currParentId == id) return true;
}
itemId = currParentId.dup;
}
return false;
}
private void bindItem(const ref Item item, ref Statement stmt)
{
with (stmt) with (item) {
bind(1, id);
bind(2, name);
string typeStr = null;
final switch (type) with (ItemType) {
case file: typeStr = "file"; break;
case dir: typeStr = "dir"; break;
}
bind(3, typeStr);
bind(4, eTag);
bind(5, cTag);
bind(6, mtime.toISOExtString());
bind(7, parentId);
bind(8, crc32);
}
}
2015-09-14 23:56:14 +02:00
private Item buildItem(Statement.Result result)
{
assert(!result.empty && result.front.length == 8);
Item item = {
id: result.front[0].dup,
name: result.front[1].dup,
eTag: result.front[3].dup,
cTag: result.front[4].dup,
mtime: SysTime.fromISOExtString(result.front[5]),
parentId: result.front[6].dup,
crc32: result.front[7].dup
};
switch (result.front[2]) {
case "file": item.type = ItemType.file; break;
case "dir": item.type = ItemType.dir; break;
default: assert(0);
2015-09-14 23:56:14 +02:00
}
return item;
}
// computes the path of the given item id
// the path is relative to the sync directory ex: "./Music/Turbo Killer.mp3"
// a trailing slash is never added
2015-09-19 15:38:43 +02:00
string computePath(const(char)[] id)
2015-09-14 23:56:14 +02:00
{
string path;
2015-09-19 15:38:43 +02:00
auto s = db.prepare("SELECT name, parentId FROM item WHERE id = ?");
2015-09-14 23:56:14 +02:00
while (true) {
s.bind(1, id);
auto r = s.exec();
enforce(!r.empty, "Unknow item id");
if (r.front[1]) {
2015-09-20 19:07:16 +02:00
if (path) path = r.front[0].idup ~ "/" ~ path;
else path = r.front[0].idup;
} else {
// root
if (path) path = "./" ~ path;
else path = ".";
break;
}
2015-09-14 23:56:14 +02:00
id = r.front[1].dup;
}
2015-09-20 19:07:16 +02:00
return path;
2015-09-14 23:56:14 +02:00
}
}