[FarGroup/FarManager] master: Implement import/export for histories and local plugin configs (b141d0b3f)

0 views
Skip to first unread message

farg...@farmanager.com

unread,
Feb 26, 2026, 7:15:57 PM (7 hours ago) Feb 26
to farco...@googlegroups.com
Repository : https://github.com/FarGroup/FarManager
On branch : master
Link : https://github.com/FarGroup/FarManager/commit/b141d0b3ff299265505104480c311ae68e626cad

>---------------------------------------------------------------

commit b141d0b3ff299265505104480c311ae68e626cad
Author: Alex Alabuzhev <alab...@gmail.com>
Date: Fri Feb 27 00:07:47 2026 +0000

Implement import/export for histories and local plugin configs


>---------------------------------------------------------------

b141d0b3ff299265505104480c311ae68e626cad
far/changelog | 5 +
far/configdb.cpp | 371 +++++++++++++++++++++++++++++++++++++++++++++----------
far/vbuild.m4 | 2 +-
3 files changed, 313 insertions(+), 65 deletions(-)

diff --git a/far/changelog b/far/changelog
index 738c2fe5d..04ccf4750 100644
--- a/far/changelog
+++ b/far/changelog
@@ -1,3 +1,8 @@
+--------------------------------------------------------------------------------
+drkns 2026-02-27 00:03:31+00:00 - build 6649
+
+1. Implement import/export for histories and local plugin configs.
+
--------------------------------------------------------------------------------
drkns 2026-02-24 23:46:38+00:00 - build 6648

diff --git a/far/configdb.cpp b/far/configdb.cpp
index 97ec99875..fca4d2c5b 100644
--- a/far/configdb.cpp
+++ b/far/configdb.cpp
@@ -112,15 +112,42 @@ static auto& CreateChild(tinyxml::XMLElement& Parent, const char* Name)
return *e;
}

+static auto base64_tag(std::string_view const Name)
+{
+ return Name + ".base64"s;
+}
+
template<typename T>
static void SetAttribute(tinyxml::XMLElement& Element, const char* Name, T const& Value)
{
if constexpr (std::convertible_to<T, std::string_view>)
- Element.SetAttribute(Name, null_terminated_t<char>(Value).c_str());
+ {
+ std::string_view const StrValue = Value;
+
+ if (std::ranges::find_first_of(StrValue, "\r\n"sv) != StrValue.end())
+ {
+ Element.SetAttribute(Name, base64::encode(view_bytes(StrValue)).c_str());
+ Element.SetAttribute(base64_tag(Name).c_str(), 1);
+ }
+ else
+ Element.SetAttribute(Name, null_terminated_t(StrValue).c_str());
+ }
else
Element.SetAttribute(Name, Value);
}

+static std::optional<std::string> GetAttribute(tinyxml::XMLElement const& Element, const char* Name)
+{
+ const auto Value = Element.Attribute(Name);
+ if (!Value)
+ return {};
+
+ if (!Element.IntAttribute(base64_tag(Name).c_str()))
+ return Value;
+
+ return std::string{ to_string_view(base64::decode(Value)) };
+}
+
class representation_destination
{
public:
@@ -139,7 +166,7 @@ public:

void Save(string_view const File)
{
- const file_ptr XmlFile(_wfsopen(nt_path(File).c_str(), L"w", _SH_DENYWR));
+ const file_ptr XmlFile(_wfsopen(nt_path(File).c_str(), L"wb", _SH_DENYWR));
if (!XmlFile)
throw far_known_exception(far::format(L"Error opening file \"{}\": {}"sv, File, os::format_errno(errno)));

@@ -1210,7 +1237,7 @@ private:
return;

SCOPED_ACTION(auto)(ScopedTransaction());
- Exec("DELETE FROM filetypes;"sv); // delete all before importing
+
primary_key id{};
for (const auto& e: xml_enum(base, "filetype"))
{
@@ -1755,15 +1782,15 @@ private:
};
};

-class HistoryConfigCustom: public HistoryConfig, public sqlite_boilerplate
+class HistoryConfigDb: public HistoryConfig, public sqlite_boilerplate
{
public:
- explicit HistoryConfigCustom(string_view const DbName):
- sqlite_boilerplate(&HistoryConfigCustom::Initialise, DbName, true)
+ explicit HistoryConfigDb(string_view const DbName):
+ sqlite_boilerplate(&HistoryConfigDb::Initialise, DbName, true)
{
}

- ~HistoryConfigCustom() override
+ ~HistoryConfigDb() override
{
WaitAllAsync();
StopEvent.set();
@@ -1774,7 +1801,7 @@ private:
os::event AsyncDeleteAddDone{os::event::type::manual, os::event::state::signaled};
os::event AsyncCommitDone{os::event::type::manual, os::event::state::signaled};
os::event AsyncWork{os::event::type::automatic, os::event::state::nonsignaled};
- [[maybe_unused]] os::thread WorkThread{&HistoryConfigCustom::ThreadProc, this};
+ [[maybe_unused]] os::thread WorkThread{&HistoryConfigDb::ThreadProc, this};

struct AsyncWorkItem
{
@@ -2285,6 +2312,239 @@ private:
ExecuteStatement(stmtDeleteOldViewer, older, MinimumEntries);
}

+ void Import(const representation_source& Representation) override
+ {
+ SCOPED_ACTION(auto)(ScopedTransaction());
+
+ auto historyRoot = Representation.Root().FirstChildElement("history");
+ if (!historyRoot.ToNode())
+ return;
+
+ for (const auto& e: xml_enum(historyRoot.FirstChildElement("items"), "item"))
+ {
+ int kind;
+ if (e.QueryIntAttribute("kind", &kind) != tinyxml::XML_SUCCESS)
+ continue;
+
+ const auto key = e.Attribute("key");
+ if (!key)
+ continue;
+
+ int type;
+ if (e.QueryIntAttribute("type", &type) != tinyxml::XML_SUCCESS)
+ continue;
+
+ const auto name = GetAttribute(e, "name");
+ if (!name)
+ continue;
+
+ const auto lock = e.IntAttribute("lock");
+ const auto time = e.Unsigned64Attribute("time");
+ const auto guid = e.Attribute("guid");
+ const auto file = e.Attribute("file");
+ const auto data = GetAttribute(e, "data");
+
+ AddInternal(
+ kind,
+ encoding::utf8::get_chars(key),
+ type,
+ lock != 0,
+ encoding::utf8::get_chars(*name),
+ os::chrono::nt_clock::from_hectonanoseconds(time),
+ encoding::utf8::get_chars(NullToEmpty(guid)),
+ encoding::utf8::get_chars(NullToEmpty(file)),
+ data? encoding::utf8::get_chars(*data) : L""sv
+ );
+ }
+
+ for (const auto& e: xml_enum(historyRoot.FirstChildElement("editorpositions"), "position"))
+ {
+ const auto name = e.Attribute("name");
+ if (!name)
+ continue;
+
+ const auto time = e.Unsigned64Attribute("time");
+ const auto line = e.IntAttribute("line");
+ const auto linepos = e.IntAttribute("linepos");
+ const auto screenline = e.IntAttribute("screenline");
+ const auto leftpos = e.IntAttribute("leftpos");
+ const auto codepage = e.IntAttribute("codepage");
+
+ ExecuteStatement(stmtSetEditorPos, encoding::utf8::get_chars(name), time, line, linepos, screenline, leftpos, codepage);
+ }
+
+ const auto stmtGetEditorPositionId = create_stmt("SELECT id FROM editorposition_history WHERE name=?1;"sv);
+
+ for (const auto& e: xml_enum(historyRoot.FirstChildElement("editorbookmarks"), "bookmark"))
+ {
+ const auto name = e.Attribute("name");
+ if (!name)
+ continue;
+
+ int num = 0;
+ if (e.QueryIntAttribute("num", &num) != tinyxml::XML_SUCCESS)
+ continue;
+
+ int line = 0;
+ if (e.QueryIntAttribute("line", &line) != tinyxml::XML_SUCCESS)
+ continue;
+
+ int linepos = 0;
+ if (e.QueryIntAttribute("linepos", &linepos) != tinyxml::XML_SUCCESS)
+ continue;
+
+ int screenline = 0;
+ if (e.QueryIntAttribute("screenline", &screenline) != tinyxml::XML_SUCCESS)
+ continue;
+
+ int leftpos = 0;
+ if (e.QueryIntAttribute("leftpos", &leftpos) != tinyxml::XML_SUCCESS)
+ continue;
+
+ if (auto_statement(&stmtGetEditorPositionId)->Bind(encoding::utf8::get_chars(name)).Step())
+ ExecuteStatement(stmtSetEditorBookmark, stmtGetEditorPositionId.GetColInt64(0), num, line, linepos, screenline, leftpos);
+ }
+
+ for (const auto& e: xml_enum(historyRoot.FirstChildElement("viewerpositions"), "position"))
+ {
+ const auto name = e.Attribute("name");
+ if (!name)
+ continue;
+
+ const auto time = e.Unsigned64Attribute("time");
+ const auto filepos = e.Int64Attribute("filepos");
+ const auto leftpos = e.Int64Attribute("leftpos");
+ const auto hex = e.IntAttribute("hex");
+ const auto codepage = e.IntAttribute("codepage");
+
+ ExecuteStatement(stmtSetViewerPos, encoding::utf8::get_chars(name), time, filepos, leftpos, hex, codepage);
+ }
+
+ const auto stmtGetViewerPositionId = create_stmt("SELECT id FROM viewerposition_history WHERE name=?1;"sv);
+
+ for (const auto& e: xml_enum(historyRoot.FirstChildElement("viewerbookmarks"), "bookmark"))
+ {
+ const auto name = e.Attribute("name");
+ if (!name)
+ continue;
+
+ int num = 0;
+ if (e.QueryIntAttribute("num", &num) != tinyxml::XML_SUCCESS)
+ continue;
+
+ int64_t filepos = 0;
+ if (e.QueryInt64Attribute("filepos", &filepos) != tinyxml::XML_SUCCESS)
+ continue;
+
+ int64_t leftpos = 0;
+ if (e.QueryInt64Attribute("leftpos", &leftpos) != tinyxml::XML_SUCCESS)
+ continue;
+
+ if (auto_statement(&stmtGetViewerPositionId)->Bind(encoding::utf8::get_chars(name)).Step())
+ ExecuteStatement(stmtSetViewerBookmark, stmtGetViewerPositionId.GetColInt64(0), num, filepos, leftpos);
+ }
+ }
+
+ void Export(representation_destination& Representation) const override
+ {
+ auto& root = CreateChild(Representation.Root(), "history");
+
+ {
+ auto& historyRoot = CreateChild(root, "items");
+ const auto stmtEnumAllHistory = create_stmt("SELECT kind, key, type, lock, name, time, guid, file, data FROM history ORDER BY kind, key, time;"sv);
+
+ while (stmtEnumAllHistory.Step())
+ {
+ auto& e = CreateChild(historyRoot, "item");
+
+ SetAttribute(e, "kind", stmtEnumAllHistory.GetColInt(0));
+ SetAttribute(e, "key", stmtEnumAllHistory.GetColTextUTF8(1));
+ SetAttribute(e, "type", stmtEnumAllHistory.GetColInt(2));
+ SetAttribute(e, "lock", stmtEnumAllHistory.GetColInt(3));
+ SetAttribute(e, "name", stmtEnumAllHistory.GetColTextUTF8(4));
+ SetAttribute(e, "time", stmtEnumAllHistory.GetColInt64(5));
+ SetAttribute(e, "guid", stmtEnumAllHistory.GetColTextUTF8(6));
+ SetAttribute(e, "file", stmtEnumAllHistory.GetColTextUTF8(7));
+ SetAttribute(e, "data", stmtEnumAllHistory.GetColTextUTF8(8));
+ }
+ }
+
+ {
+ auto& editorRoot = CreateChild(root, "editorpositions");
+ const auto stmtEnumEditorPos = create_stmt("SELECT name, time, line, linepos, screenline, leftpos, codepage FROM editorposition_history ORDER BY time;"sv);
+
+ while (stmtEnumEditorPos.Step())
+ {
+ auto& e = CreateChild(editorRoot, "position");
+
+ SetAttribute(e, "name", stmtEnumEditorPos.GetColTextUTF8(0));
+ SetAttribute(e, "time", stmtEnumEditorPos.GetColInt64(1));
+ SetAttribute(e, "line", stmtEnumEditorPos.GetColInt(2));
+ SetAttribute(e, "linepos", stmtEnumEditorPos.GetColInt(3));
+ SetAttribute(e, "screenline", stmtEnumEditorPos.GetColInt(4));
+ SetAttribute(e, "leftpos", stmtEnumEditorPos.GetColInt(5));
+ SetAttribute(e, "codepage", stmtEnumEditorPos.GetColInt(6));
+ }
+ }
+
+ {
+ auto& bookmarksRoot = CreateChild(root, "editorbookmarks");
+ const auto stmtEnumEditorBookmarks = create_stmt(
+ "SELECT ep.name, eb.num, eb.line, eb.linepos, eb.screenline, eb.leftpos "
+ "FROM editorbookmarks_history AS eb "
+ "JOIN editorposition_history AS ep ON eb.pid=ep.id "
+ "ORDER BY ep.name, eb.num;"sv);
+
+ while (stmtEnumEditorBookmarks.Step())
+ {
+ auto& e = CreateChild(bookmarksRoot, "bookmark");
+
+ SetAttribute(e, "name", stmtEnumEditorBookmarks.GetColTextUTF8(0));
+ SetAttribute(e, "num", stmtEnumEditorBookmarks.GetColInt(1));
+ SetAttribute(e, "line", stmtEnumEditorBookmarks.GetColInt(2));
+ SetAttribute(e, "linepos", stmtEnumEditorBookmarks.GetColInt(3));
+ SetAttribute(e, "screenline", stmtEnumEditorBookmarks.GetColInt(4));
+ SetAttribute(e, "leftpos", stmtEnumEditorBookmarks.GetColInt(5));
+ }
+ }
+
+ {
+ auto& viewerRoot = CreateChild(root, "viewerpositions");
+ const auto stmtEnumViewerPos = create_stmt("SELECT name, time, filepos, leftpos, hex, codepage FROM viewerposition_history ORDER BY time;"sv);
+
+ while (stmtEnumViewerPos.Step())
+ {
+ auto& e = CreateChild(viewerRoot, "position");
+
+ SetAttribute(e, "name", stmtEnumViewerPos.GetColTextUTF8(0));
+ SetAttribute(e, "time", stmtEnumViewerPos.GetColInt64(1));
+ SetAttribute(e, "filepos", stmtEnumViewerPos.GetColInt64(2));
+ SetAttribute(e, "leftpos", stmtEnumViewerPos.GetColInt64(3));
+ SetAttribute(e, "hex", stmtEnumViewerPos.GetColInt(4));
+ SetAttribute(e, "codepage", stmtEnumViewerPos.GetColInt(5));
+ }
+ }
+
+ {
+ auto& bookmarksRoot = CreateChild(root, "viewerbookmarks");
+ const auto stmtEnumViewerBookmarks = create_stmt(
+ "SELECT vp.name, vb.num, vb.filepos, vb.leftpos "
+ "FROM viewerbookmarks_history AS vb "
+ "JOIN viewerposition_history AS vp ON vb.pid=vp.id "
+ "ORDER BY vp.name, vb.num;"sv);
+
+ while (stmtEnumViewerBookmarks.Step())
+ {
+ auto& e = CreateChild(bookmarksRoot, "bookmark");
+
+ SetAttribute(e, "name", stmtEnumViewerBookmarks.GetColTextUTF8(0));
+ SetAttribute(e, "num", stmtEnumViewerBookmarks.GetColInt(1));
+ SetAttribute(e, "filepos", stmtEnumViewerBookmarks.GetColInt64(2));
+ SetAttribute(e, "leftpos", stmtEnumViewerBookmarks.GetColInt64(3));
+ }
+ }
+ }
+
enum statement_id
{
stmtEnum,
@@ -2317,40 +2577,6 @@ private:
};
};

-class HistoryConfigDb final: public HistoryConfigCustom
-{
-public:
- explicit HistoryConfigDb(string_view const Name):
- HistoryConfigCustom(Name)
- {
- }
-
-private:
- // TODO: implementation
- void Import(const representation_source&) override
- {
- LOGNOTICE(L"History import not implemented"sv);
- }
-
- void Export(representation_destination&) const override
- {
- LOGNOTICE(L"History export not implemented"sv);
- }
-};
-
-class HistoryConfigMemory final: public HistoryConfigCustom
-{
-public:
- explicit HistoryConfigMemory(string_view const Name):
- HistoryConfigCustom(Name)
- {
- }
-
-private:
- void Import(const representation_source&) override {}
- void Export(representation_destination&) const override {}
-};
-
bool is_uuid(string_view const Str)
{
return uuid::try_parse(Str).has_value();
@@ -2567,7 +2793,7 @@ config_provider::config_provider(mode Mode):
m_PlCacheCfg([this]{ return CreateDatabase<PluginsCacheConfigDb>(pluginscache_db_name(), true); }),
m_PlHotkeyCfg([this]{ return CreateDatabase<PluginsHotkeysConfigDb>(L"pluginhotkeys"sv, false); }),
m_HistoryCfg([this]{ return CreateDatabase<HistoryConfigDb>(L"history"sv, true); }),
- m_HistoryCfgMem([this]{ return CreateDatabase<HistoryConfigMemory>(SQLiteDb::memory_db_name, true); })
+ m_HistoryCfgMem([this]{ return CreateDatabase<HistoryConfigDb>(SQLiteDb::memory_db_name, true); })
{
}

@@ -2582,6 +2808,18 @@ config_provider::~config_provider()
m_Threads.clear();
}

+constexpr struct
+{
+ string Options::*Path;
+ const char* Name;
+ bool IsLocal;
+}
+PluginsConfigs[]
+{
+ { &Options::ProfilePath, "pluginsconfig", false, },
+ { &Options::LocalProfilePath, "localpluginsconfig", true, },
+};
+
void config_provider::Export(string_view const File)
{
representation_destination Representation;
@@ -2606,21 +2844,24 @@ void config_provider::Export(string_view const File)

{
const auto Ext = L"*.db"sv;
- //TODO: export local plugin settings
- auto& e = CreateChild(root, "pluginsconfig");
- for(const auto& i: os::fs::enum_files(path::join(Global->Opt->ProfilePath, L"PluginsData"sv, Ext)))
+
+ for (const auto& Config: PluginsConfigs)
{
- if (!os::fs::is_file(i))
- continue;
+ auto& e = CreateChild(root, Config.Name);
+ for (const auto& i: os::fs::enum_files(path::join(std::invoke(Config.Path, Global->Opt), L"PluginsData"sv, Ext)))
+ {
+ if (!os::fs::is_file(i))
+ continue;

- const auto FileName = name_ext(i.FileName).first;
- if (!is_uuid(FileName))
- continue;
+ const auto FileName = name_ext(i.FileName).first;
+ if (!is_uuid(FileName))
+ continue;

- auto& PluginRoot = CreateChild(e, "plugin");
- SetAttribute(PluginRoot, "guid", encoding::utf8::get_bytes(FileName));
- Representation.SetRoot(PluginRoot);
- CreatePluginsConfig(FileName)->Export(Representation);
+ auto& PluginRoot = CreateChild(e, "plugin");
+ SetAttribute(PluginRoot, "guid", encoding::utf8::get_bytes(FileName));
+ Representation.SetRoot(PluginRoot);
+ CreatePluginsConfig(FileName, Config.IsLocal)->Export(Representation);
+ }
}
}

@@ -2659,17 +2900,19 @@ void config_provider::Import(string_view const File)
Representation.SetRoot(root.FirstChildElement("shortcuts"));
CreateShortcutsConfig()->Import(Representation);

- //TODO: import local plugin settings
- for (const auto& plugin: xml_enum(root.FirstChildElement("pluginsconfig"), "plugin"))
+ for (const auto& Config: PluginsConfigs)
{
- const auto UuidStr = plugin.Attribute("guid");
- if (!UuidStr)
- continue;
-
- if (const auto Uuid = encoding::utf8::get_chars(UuidStr); is_uuid(Uuid))
+ for (const auto& plugin: xml_enum(root.FirstChildElement(Config.Name), "plugin"))
{
- Representation.SetRoot(&const_cast<tinyxml::XMLElement&>(plugin));
- CreatePluginsConfig(Uuid)->Import(Representation);
+ const auto UuidStr = plugin.Attribute("guid");
+ if (!UuidStr)
+ continue;
+
+ if (const auto Uuid = encoding::utf8::get_chars(UuidStr); is_uuid(Uuid))
+ {
+ Representation.SetRoot(&const_cast<tinyxml::XMLElement&>(plugin));
+ CreatePluginsConfig(Uuid, Config.IsLocal)->Import(Representation);
+ }
}
}
}
diff --git a/far/vbuild.m4 b/far/vbuild.m4
index e0cf2b11a..0deedf4ba 100644
--- a/far/vbuild.m4
+++ b/far/vbuild.m4
@@ -1 +1 @@
-6648
+6649


Reply all
Reply to author
Forward
0 new messages