You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
3.9 KiB
145 lines
3.9 KiB
#pragma once |
|
#include "MString.h" |
|
#include <sqlite3.h> |
|
#include <time.h> |
|
#include <variant> |
|
|
|
using michlib::MString; |
|
|
|
class GenericCache |
|
{ |
|
public: |
|
virtual bool Put(const MString& key, const MString& value, size_t ttl) const = 0; |
|
virtual std::pair<MString, bool> Get(const MString& key) const = 0; |
|
virtual ~GenericCache() {} |
|
}; |
|
|
|
class FakeCache: public GenericCache |
|
{ |
|
public: |
|
virtual bool Put([[maybe_unused]] const MString& key, [[maybe_unused]] const MString& value, [[maybe_unused]] size_t ttl) const override { return false; } |
|
virtual std::pair<MString, bool> Get([[maybe_unused]] const MString& key) const override { return {"", false}; } |
|
virtual ~FakeCache() override {} |
|
}; |
|
|
|
class SQLiteCache: public GenericCache |
|
{ |
|
sqlite3* db = nullptr; |
|
|
|
public: |
|
bool Init(const MString& name) |
|
{ |
|
Close(); |
|
auto ret = sqlite3_open(name.Buf(), &db); |
|
if(ret != SQLITE_OK) |
|
{ |
|
Close(); |
|
return false; |
|
} |
|
// Create table |
|
sqlite3_stmt* sqst; |
|
int i; |
|
|
|
i = sqlite3_prepare_v2(db, |
|
"CREATE TABLE IF NOT EXISTS `cache`('key' TEXT PRIMARY KEY ON CONFLICT REPLACE NOT NULL ON CONFLICT FAIL, 'value' BLOB NOT NULL ON CONFLICT FAIL, " |
|
"'exptime' INTEGER NOT NULL ON CONFLICT FAIL) WITHOUT ROWID, STRICT;", |
|
-1, &sqst, 0); |
|
i = sqlite3_step(sqst); |
|
if(i != SQLITE_DONE) |
|
{ |
|
sqlite3_finalize(sqst); |
|
Close(); |
|
return false; |
|
} |
|
sqlite3_finalize(sqst); |
|
sqlite3_busy_timeout(db, 1000); |
|
return true; |
|
} |
|
|
|
void Close() |
|
{ |
|
if(db != nullptr) sqlite3_close(db); |
|
db = nullptr; |
|
} |
|
|
|
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override |
|
{ |
|
if(!*this) return false; |
|
sqlite3_stmt* sqst = nullptr; |
|
int i = SQLITE_OK; |
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "INSERT OR REPLACE into `cache` VALUES(?1,?2,?3);", -1, &sqst, 0); |
|
if(i == SQLITE_OK) i = sqlite3_bind_text(sqst, 1, key.Buf(), -1, SQLITE_STATIC); |
|
if(i == SQLITE_OK) i = sqlite3_bind_blob64(sqst, 2, value.Buf(), value.Len(), SQLITE_STATIC); |
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 3, time(nullptr) + ttl); |
|
if(i == SQLITE_OK) i = sqlite3_step(sqst); |
|
sqlite3_finalize(sqst); |
|
|
|
return i == SQLITE_OK; |
|
} |
|
|
|
virtual std::pair<MString, bool> Get(const MString& key) const override |
|
{ |
|
if(!*this) return {"", false}; |
|
|
|
sqlite3_stmt* sqst = nullptr; |
|
int i = SQLITE_OK; |
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "SELECT value from `cache` WHERE key=?1 AND exptime>?2;", -1, &sqst, 0); |
|
if(i == SQLITE_OK) i = sqlite3_bind_text(sqst, 1, key.Buf(), -1, SQLITE_STATIC); |
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 2, time(nullptr)); |
|
if(i == SQLITE_OK) i = sqlite3_step(sqst); |
|
if(i == SQLITE_ROW) |
|
{ |
|
auto p = sqlite3_column_blob(sqst, 0); |
|
auto sz = sqlite3_column_bytes(sqst, 0); |
|
if(p != nullptr) |
|
{ |
|
MString out(p, sz); |
|
sqlite3_finalize(sqst); |
|
return {std::move(out), true}; |
|
} |
|
} |
|
sqlite3_finalize(sqst); |
|
return {"", false}; |
|
} |
|
|
|
virtual ~SQLiteCache() override |
|
{ |
|
if(!*this) return; |
|
|
|
sqlite3_stmt* sqst = nullptr; |
|
int i = SQLITE_OK; |
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "DELETE from `cache` WHERE exptime<?1;", -1, &sqst, 0); |
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 1, time(nullptr)); |
|
if(i == SQLITE_OK) i = sqlite3_step(sqst); |
|
sqlite3_finalize(sqst); |
|
sqlite3_close_v2(db); |
|
} |
|
|
|
explicit operator bool() const { return db != nullptr; } |
|
}; |
|
|
|
inline GenericCache* CreateCache(const MString& cachedesc) |
|
{ |
|
auto i = cachedesc.GetPos(':'); |
|
if(i == 0) |
|
{ |
|
if(cachedesc == "no") return new FakeCache; |
|
return nullptr; |
|
} |
|
|
|
auto name = cachedesc.SubStr(1, i - 1); |
|
auto par = cachedesc.SubStr(i + 1, cachedesc.Len() - i); |
|
|
|
if(name == "sqlite") |
|
{ |
|
auto ret = new SQLiteCache; |
|
ret->Init(par); |
|
if(*ret) return ret; |
|
delete ret; |
|
} |
|
|
|
return nullptr; |
|
}
|
|
|