这四种模式每个键都新开一次 SQLite 连接,第一次 prepare 还要解析整个 schema ——msime.db 有两百多张分表。新增 local_modes/local_database,四个查询统一经它 打开只读连接:路径正是当前数据目录下的 msime.db 或 others.db 时共享一条连接, 其余路径(测试、工具)照旧每次独立打开、用完即关。 只共享这两个随安装包发布的库:msime.db 本来就被引擎整个会话持有,others.db 只在 Server 停止时由安装程序替换;保持其它文件常开会让删除它们的工具或测试在 Windows 上失败。close_default_user_database() 同时释放这些连接,调用方原本就用它在删除 或替换数据目录前放开文件。 引擎测试(本地单独构建,CI 不跑)23/23 通过;不加释放钩子时 input_session、 mixed_expressive_input_session、jianpin_input_session 会因目录删不掉而失败。
103 lines
2.8 KiB
C++
103 lines
2.8 KiB
C++
#include "../contracts/assets/assets.h"
|
|
#include "local_database.h"
|
|
|
|
#include "../core/data_path.h"
|
|
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace metasequoia::local_modes
|
|
{
|
|
namespace
|
|
{
|
|
std::shared_ptr<sqlite3> open_read_only(const std::filesystem::path &path)
|
|
{
|
|
sqlite3 *raw = nullptr;
|
|
// FULLMUTEX: a shared connection is used from the IME worker and from the
|
|
// emoji/kaomoji lookup threads.
|
|
if (sqlite3_open_v2(path_to_utf8(path).c_str(), &raw, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) !=
|
|
SQLITE_OK)
|
|
{
|
|
if (raw != nullptr)
|
|
{
|
|
sqlite3_close(raw);
|
|
}
|
|
return {};
|
|
}
|
|
sqlite3_busy_timeout(raw, 1000);
|
|
return std::shared_ptr<sqlite3>(raw, [](sqlite3 *database) { sqlite3_close(database); });
|
|
}
|
|
|
|
// Only the shipped dictionaries are shared: msime.db is held open by the engine
|
|
// for the whole session anyway, and others.db is replaced only by the installer
|
|
// while the Server is stopped. Keeping anything else open could block a tool or
|
|
// a test that deletes its database afterwards, which Windows refuses while a
|
|
// handle is open.
|
|
bool is_shared_dictionary(const std::filesystem::path &path)
|
|
{
|
|
return path == data_file_path(metasequoia::assets::main_dictionary) ||
|
|
path == data_file_path(metasequoia::assets::other_dictionary);
|
|
}
|
|
|
|
struct CachedConnection
|
|
{
|
|
std::filesystem::path path;
|
|
std::shared_ptr<sqlite3> connection;
|
|
};
|
|
|
|
std::mutex &cache_mutex()
|
|
{
|
|
static std::mutex mutex;
|
|
return mutex;
|
|
}
|
|
|
|
std::vector<CachedConnection> &cache()
|
|
{
|
|
static std::vector<CachedConnection> connections;
|
|
return connections;
|
|
}
|
|
} // namespace
|
|
|
|
std::shared_ptr<sqlite3> open_local_database(const std::filesystem::path &path)
|
|
{
|
|
if (path.empty())
|
|
{
|
|
return {};
|
|
}
|
|
if (!is_shared_dictionary(path))
|
|
{
|
|
return open_read_only(path);
|
|
}
|
|
|
|
const std::lock_guard<std::mutex> guard(cache_mutex());
|
|
auto &connections = cache();
|
|
for (const CachedConnection &cached : connections)
|
|
{
|
|
if (cached.path == path)
|
|
{
|
|
return cached.connection;
|
|
}
|
|
}
|
|
auto connection = open_read_only(path);
|
|
if (connection)
|
|
{
|
|
// At most one entry per shipped dictionary: a connection cached for a
|
|
// data directory that is no longer active is released here.
|
|
connections.erase(
|
|
std::remove_if(connections.begin(), connections.end(),
|
|
[&](const CachedConnection &cached) { return cached.path.filename() == path.filename(); }),
|
|
connections.end());
|
|
connections.push_back({path, connection});
|
|
}
|
|
return connection;
|
|
}
|
|
|
|
void close_cached_local_databases()
|
|
{
|
|
const std::lock_guard<std::mutex> guard(cache_mutex());
|
|
cache().clear();
|
|
}
|
|
} // namespace metasequoia::local_modes
|