sitestat/source/data.d

125 lines
3.3 KiB
D

module data;
import std.file;
import std.datetime;
import std.format;
import d2sqlite3;
static immutable FS_ORIGIN = "_LOCAL_FILESYSTEM_";
struct StoredSession {
long id;
SysTime startTimestamp;
SysTime endTimestamp;
string url;
string fullUrl;
string userAgent;
long eventCount;
}
void storeSession(StoredSession s) {
string origin = extractOrigin(s.url);
if (origin is null) {
throw new Exception("Unable to parse origin from url: " ~ s.url);
} else if (origin.length == 0) {
origin = FS_ORIGIN;
}
Database db = getOrCreateDatabase(origin);
Statement stmt = db.prepare(
"INSERT INTO session " ~
"(start_timestamp, end_timestamp, url, full_url, user_agent, event_count) " ~
"VALUES (?, ?, ?, ?, ?, ?)"
);
stmt.bind(1, formatTimestamp(s.startTimestamp));
stmt.bind(2, formatTimestamp(s.endTimestamp));
stmt.bind(3, s.url);
stmt.bind(4, s.fullUrl);
stmt.bind(5, s.userAgent);
stmt.bind(6, s.eventCount);
stmt.execute();
}
Database getOrCreateDatabase(string origin) {
string filename = dbPath(origin);
if (!exists(filename)) {
initDb(filename);
}
return Database(filename, SQLITE_OPEN_READWRITE);
}
private void initDb(string path) {
if (exists(path)) std.file.remove(path);
Database db = Database(path);
db.run(q"SQL
CREATE TABLE session (
id INTEGER PRIMARY KEY AUTOINCREMENT,
start_timestamp TEXT NOT NULL,
end_timestamp TEXT NOT NULL,
url TEXT NOT NULL,
full_url TEXT NOT NULL,
user_agent TEXT NOT NULL,
event_count INTEGER NOT NULL
);
SQL"
);
}
ulong countSessions(string origin) {
Database db = getOrCreateDatabase(origin);
return db.execute("SELECT COUNT(id) FROM session;").oneValue!ulong;
}
private string formatTimestamp(SysTime t) {
return format!"%04d-%02d-%02d %02d:%02d:%02d"(
t.year, t.month, t.day,
t.hour, t.minute, t.second
);
}
string extractOrigin(string url) {
import std.algorithm : countUntil, startsWith;
ptrdiff_t idx = countUntil(url, "://");
if (idx == -1) return null;
string origin = url[idx + 3 .. $];
ptrdiff_t trailingSlashIdx = countUntil(origin, "/");
if (trailingSlashIdx != -1) {
origin = origin[0 .. trailingSlashIdx];
}
if (startsWith(origin, "www.")) {
origin = origin[4 .. $];
}
return origin;
}
unittest {
assert(extractOrigin("https://www.google.com/search") == "google.com");
assert(extractOrigin("https://litelist.andrewlalis.com") == "litelist.andrewlalis.com");
}
string dbPath(string origin) {
return "sitestat-db_" ~ origin ~ ".sqlite";
}
string originFromDbPath(string path) {
import std.algorithm : countUntil;
ptrdiff_t idx = countUntil(path, "sitestat-db_");
if (idx == -1) return null;
return path[(idx + 12)..$-7];
}
unittest {
assert(originFromDbPath("sitestat-db__LOCAL_FILESYSTEM_.sqlite") == "_LOCAL_FILESYSTEM_");
}
string[] listAllOrigins(string dir = ".") {
import std.array;
auto app = appender!(string[]);
foreach (DirEntry entry; dirEntries(dir, SpanMode.shallow, false)) {
string origin = originFromDbPath(entry.name);
if (origin !is null) {
app ~= origin;
}
}
return app[];
}