CubicSDR/src/BookmarkMgr.cpp

678 lines
22 KiB
C++

// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "BookmarkMgr.h"
#include "CubicSDR.h"
#include "DataTree.h"
#define BOOKMARK_RECENTS_MAX 25
BookmarkEntry::~BookmarkEntry() {
delete node;
}
BookmarkMgr::BookmarkMgr() {
rangesSorted = false;
}
//
void BookmarkMgr::saveToFile(const std::string& bookmarkFn, bool backup, bool useFullpath) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
DataTree s("cubicsdr_bookmarks");
DataNode *header = s.rootNode()->newChild("header");
header->newChild("version")->element()->set(wxString(CUBICSDR_VERSION).ToStdWstring());
DataNode *branches = s.rootNode()->newChild("branches");
*branches->newChild("active") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("active")?1:0;
*branches->newChild("range") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("range")?1:0;
*branches->newChild("bookmark") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("bookmark")?1:0;
*branches->newChild("recent") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("recent")?1:0;
DataNode *view_ranges = s.rootNode()->newChild("ranges");
for (const auto& re_i : ranges) {
DataNode *range = view_ranges->newChild("range");
*range->newChild("label") = re_i->label;
*range->newChild("freq") = re_i->freq;
*range->newChild("start") = re_i->startFreq;
*range->newChild("end") = re_i->endFreq;
}
DataNode *modems = s.rootNode()->newChild("modems");
for (auto &bmd_i : bmData) {
DataNode *group = modems->newChild("group");
*group->newChild("@name") = bmd_i.first;
*group->newChild("@expanded") = (getExpandState(bmd_i.first)?std::string("true"):std::string("false"));
for (auto &bm_i : bmd_i.second ) {
//if a matching demodulator exists, use its data instead to be be saved, because output_device could have been
//modified by the user. So, save that "live" version instead.
auto matchingDemod = wxGetApp().getDemodMgr().getLastDemodulatorWith(bm_i->type,
bm_i->label,
bm_i->frequency,
bm_i->bandwidth);
if (matchingDemod != nullptr) {
wxGetApp().getDemodMgr().saveInstance(group->newChild("modem"), matchingDemod);
}
else {
group->newChildCloneFrom("modem", bm_i->node);
}
}
}
DataNode *recent_modems = s.rootNode()->newChild("recent_modems");
for (const auto& demod : wxGetApp().getDemodMgr().getDemodulators()) {
wxGetApp().getDemodMgr().saveInstance(recent_modems->newChild("modem"),demod);
}
for (auto &r_i : this->recents) {
recent_modems->newChildCloneFrom("modem", r_i->node);
}
wxFileName saveFile;
wxFileName saveFileBackup;
if (useFullpath) {
saveFile.Assign(bookmarkFn);
saveFileBackup.Assign(bookmarkFn + ".backup");
}
else {
saveFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn);
saveFileBackup.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".backup");
}
if (saveFile.IsDirWritable()) {
// Hopefully leave at least a readable backup in case of failure..
if (backup && saveFile.FileExists() && (!saveFileBackup.FileExists() || saveFileBackup.IsFileWritable())) {
wxCopyFile(saveFile.GetFullPath(wxPATH_NATIVE).ToStdString(), saveFileBackup.GetFullPath(wxPATH_NATIVE).ToStdString());
}
s.SaveToFileXML(saveFile.GetFullPath(wxPATH_NATIVE).ToStdString());
}
}
bool BookmarkMgr::loadFromFile(const std::string& bookmarkFn, bool backup, bool useFullpath) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
wxFileName loadFile;
wxFileName failFile;
wxFileName lastLoaded;
wxFileName backupFile;
if (useFullpath) {
loadFile.Assign(bookmarkFn);
failFile.Assign(bookmarkFn + ".failedload");
lastLoaded.Assign(bookmarkFn + ".lastloaded");
backupFile.Assign(bookmarkFn + ".backup");
}
else {
loadFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn);
failFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".failedload");
lastLoaded.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".lastloaded");
backupFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".backup");
}
DataTree s;
bool loadStatusOk = true;
// File exists but is not readable
if (loadFile.FileExists() && !loadFile.IsFileReadable()) {
return false;
}
// New instance of bookmark savefiles
if (backup && !loadFile.FileExists() && !lastLoaded.FileExists() && !backupFile.FileExists()) {
loadDefaultRanges();
return true;
}
// Attempt to load file
if (!s.LoadFromFileXML(loadFile.GetFullPath(wxPATH_NATIVE).ToStdString())) {
return false;
}
//Check if it is a bookmark file, read the root node.
if (s.rootNode()->getName() != "cubicsdr_bookmarks") {
return false;
}
// Clear any active data
bmData.clear();
recents.clear();
ranges.clear();
bmDataSorted.clear();
if (s.rootNode()->hasAnother("branches")) {
DataNode *branches = s.rootNode()->getNext("branches");
int bActive = 1, bRange = 0, bBookmark = 1, bRecent = 1;
if (branches->hasAnother("active")) branches->getNext("active")->element()->get(bActive);
if (branches->hasAnother("range")) branches->getNext("range")->element()->get(bRange);
if (branches->hasAnother("bookmark")) branches->getNext("bookmark")->element()->get(bBookmark);
if (branches->hasAnother("recent")) branches->getNext("recent")->element()->get(bRecent);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("active", bActive != 0);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("range", bRange != 0);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("bookmark", bBookmark != 0);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("recent", bRecent != 0);
}
if (s.rootNode()->hasAnother("ranges")) {
DataNode *view_ranges = s.rootNode()->getNext("ranges");
while (view_ranges->hasAnother("range")) {
DataNode *range = view_ranges->getNext("range");
BookmarkRangeEntryPtr re(new BookmarkRangeEntry);
if (range->hasAnother("label")) range->getNext("label")->element()->get(re->label);
if (range->hasAnother("freq")) range->getNext("freq")->element()->get(re->freq);
if (range->hasAnother("start")) range->getNext("start")->element()->get(re->startFreq);
if (range->hasAnother("end")) range->getNext("end")->element()->get(re->endFreq);
addRange(re);
}
}
if (s.rootNode()->hasAnother("modems")) {
DataNode *modems = s.rootNode()->getNext("modems");
while (modems->hasAnother("group")) {
DataNode *group = modems->getNext("group");
std::string groupExpandState = "true";
std::string groupName = "Unnamed";
if (group->hasAnother("@name")) {
groupName = group->getNext("@name")->element()->toString();
}
if (group->hasAnother("@expanded")) {
groupExpandState = group->getNext("@expanded")->element()->toString();
}
setExpandState(groupName, (groupExpandState == "true"));
while (group->hasAnother("modem")) {
DataNode *modem = group->getNext("modem");
BookmarkEntryPtr be = nodeToBookmark(modem);
if (be) {
addBookmark(groupName, be);
} else {
std::cout << "error loading bookmarked modem.." << std::endl;
loadStatusOk = false;
}
}
}
}
if (s.rootNode()->hasAnother("recent_modems")) {
DataNode *recent_modems = s.rootNode()->getNext("recent_modems");
while (recent_modems->hasAnother("modem")) {
DataNode *modem = recent_modems->getNext("modem");
BookmarkEntryPtr be = nodeToBookmark(modem);
if (be) {
addRecent(be);
} else {
std::cout << "error loading recent modem.." << std::endl;
loadStatusOk = false;
}
}
}
if (backup) {
if (loadStatusOk) { // Loaded OK; keep a copy
if (loadFile.IsDirWritable()) {
if (loadFile.FileExists() && (!lastLoaded.FileExists() || lastLoaded.IsFileWritable())) {
wxCopyFile(loadFile.GetFullPath(wxPATH_NATIVE).ToStdString(), lastLoaded.GetFullPath(wxPATH_NATIVE).ToStdString());
}
}
} else {
if (loadFile.IsDirWritable()) { // Load failed; keep a copy of the failed bookmark file for analysis?
if (loadFile.FileExists() && (!failFile.FileExists() || failFile.IsFileWritable())) {
wxCopyFile(loadFile.GetFullPath(wxPATH_NATIVE).ToStdString(), failFile.GetFullPath(wxPATH_NATIVE).ToStdString());
}
}
}
}
return loadStatusOk;
}
void BookmarkMgr::loadDefaultRanges() {
addRange(std::make_shared<BookmarkRangeEntry>(L"2200 Meters (135.7-137.8 kHz)", 136750, 135700, 137800));
addRange(std::make_shared<BookmarkRangeEntry>(L"630 Meters (472-479 kHz)", 475500, 472000, 479000));
addRange(std::make_shared<BookmarkRangeEntry>(L"160 Meters (1.8-2 MHz)", 1900000, 1800000, 2000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"80 Meters (3.5-4.0 MHz)", 3750000, 3500000, 4000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"60 Meters (5.332-5.405Mhz)", 5368500, 5332000, 5405000));
addRange(std::make_shared<BookmarkRangeEntry>(L"40 Meters (7.0-7.3 MHz)", 7150000, 7000000, 7300000));
addRange(std::make_shared<BookmarkRangeEntry>(L"30 Meters (10.1-10.15 MHz)", 10125000, 10100000, 10150000));
addRange(std::make_shared<BookmarkRangeEntry>(L"20 Meters (14.0-14.35 MHz)", 14175000, 14000000, 14350000));
addRange(std::make_shared<BookmarkRangeEntry>(L"17 Meters (17.044-19.092 MHz)", 18068180, 17044180, 19092180));
addRange(std::make_shared<BookmarkRangeEntry>(L"15 Meters (21-21.45 MHz)", 21225000, 21000000, 21450000));
addRange(std::make_shared<BookmarkRangeEntry>(L"12 Meters (24.89-24.99 MHz)", 24940000, 24890000, 24990000));
addRange(std::make_shared<BookmarkRangeEntry>(L"10 Meters (28-29.7 MHz)", 28850000, 28000000, 29700000));
addRange(std::make_shared<BookmarkRangeEntry>(L"6 Meters (50-54 MHz)", 52000000, 50000000, 54000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"4 Meters (70-70.5 MHz)", 70250000, 70000000, 70500000));
addRange(std::make_shared<BookmarkRangeEntry>(L"2 Meters (144-148 MHz)", 146000000, 144000000, 148000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"1.25 Meters (219-225 MHz)", 222000000, 219000000, 225000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"70 cm (420-450 MHz)", 435000000, 420000000, 450000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"33 cm (902-928 MHz)", 915000000, 902000000, 928000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"23 cm (1240-1300 MHz)", 1270000000, 1240000000, 1300000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"13 cm lower (2300-2310 MHz)", 2305000000, 2300000000, 2310000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"13 cm upper (2390-2450 MHz)", 2420000000, 2390000000, 2450000000));
}
void BookmarkMgr::resetBookmarks() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
// Clear any active data
bmData.clear();
recents.clear();
ranges.clear();
bmDataSorted.clear();
loadDefaultRanges();
}
bool BookmarkMgr::hasLastLoad(const std::string& bookmarkFn) {
wxFileName lastLoaded(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".lastloaded");
return lastLoaded.FileExists() && lastLoaded.IsFileReadable();
}
bool BookmarkMgr::hasBackup(const std::string& bookmarkFn) {
wxFileName backupFile(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".backup");
return backupFile.FileExists() && backupFile.IsFileReadable();
}
void BookmarkMgr::addBookmark(const std::string& group, const DemodulatorInstancePtr& demod) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
//Create a BookmarkEntry from demod data, saving its
//characteristics in be->node.
BookmarkEntryPtr be = demodToBookmarkEntry(demod);
bmData[group].push_back(be);
bmDataSorted[group] = false;
}
void BookmarkMgr::addBookmark(const std::string& group, const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
bmData[group].push_back(be);
bmDataSorted[group] = false;
}
void BookmarkMgr::removeBookmark(const std::string& group, const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (bmData.find(group) == bmData.end()) {
return;
}
auto i = std::find(bmData[group].begin(), bmData[group].end(), be);
if (i != bmData[group].end()) {
bmData[group].erase(i);
}
}
void BookmarkMgr::removeBookmark(const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto &bmd_i : bmData) {
auto i = std::find(bmd_i.second.begin(), bmd_i.second.end(), be);
if (i != bmd_i.second.end()) {
bmd_i.second.erase(i);
}
}
}
void BookmarkMgr::moveBookmark(const BookmarkEntryPtr& be, const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto &bmd_i : bmData) {
auto i = std::find(bmd_i.second.begin(), bmd_i.second.end(), be);
if (i != bmd_i.second.end()) {
if (bmd_i.first == group) {
return;
}
bmData[group].push_back(*i);
bmd_i.second.erase(i);
bmDataSorted[group] = false;
bmDataSorted[bmd_i.first] = false;
return;
}
}
}
void BookmarkMgr::addGroup(const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (bmData.find(group) == bmData.end()) {
BookmarkList dummy = bmData[group];
}
}
void BookmarkMgr::removeGroup(const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto i = bmData.find(group);
if (i != bmData.end()) {
bmData.erase(group);
}
}
void BookmarkMgr::renameGroup(const std::string& group, const std::string& ngroup) {
if (group == ngroup) {
return;
}
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto i = bmData.find(group);
auto it = bmData.find(ngroup);
if (i != bmData.end() && it != bmData.end()) {
for (const auto& ii : bmData[group]) {
bmData[ngroup].push_back(ii);
}
bmData.erase(group);
} else if (i != bmData.end()) {
bmData[ngroup] = bmData[group];
bmData.erase(group);
}
}
BookmarkList BookmarkMgr::getBookmarks(const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (bmData.find(group) == bmData.end()) {
return BookmarkList();
}
if (!bmDataSorted[group]) {
std::sort(bmData[group].begin(), bmData[group].end(), [](const BookmarkEntryPtr a, const BookmarkEntryPtr b) -> bool { return a->frequency < b->frequency;});
bmDataSorted[group] = true;
}
return bmData[group];
}
void BookmarkMgr::getGroups(BookmarkNames &arr) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto & i : bmData) {
arr.push_back(i.first);
}
}
void BookmarkMgr::getGroups(wxArrayString &arr) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto & i : bmData) {
arr.push_back(i.first);
}
}
void BookmarkMgr::setExpandState(const std::string& groupName, bool state) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
expandState[groupName] = state;
}
bool BookmarkMgr::getExpandState(const std::string& groupName) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (expandState.find(groupName) == expandState.end()) {
return true;
}
return expandState[groupName];
}
void BookmarkMgr::updateActiveList() {
if (wxGetApp().isShuttingDown()) {
return;
}
BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView();
if (bmv) {
bmv->updateActiveList();
}
}
void BookmarkMgr::updateBookmarks() {
BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView();
if (bmv) {
bmv->updateBookmarks();
}
}
void BookmarkMgr::updateBookmarks(const std::string& group) {
BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView();
if (bmv) {
bmv->updateBookmarks(group);
}
}
void BookmarkMgr::addRecent(const DemodulatorInstancePtr& demod) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
recents.push_back(demodToBookmarkEntry(demod));
trimRecents();
}
void BookmarkMgr::addRecent(const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
recents.push_back(be);
trimRecents();
}
void BookmarkMgr::removeRecent(const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto bm_i = std::find(recents.begin(),recents.end(), be);
if (bm_i != recents.end()) {
recents.erase(bm_i);
}
}
BookmarkList BookmarkMgr::getRecents() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
//return a copy
return recents;
}
void BookmarkMgr::clearRecents() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
recents.clear();
}
void BookmarkMgr::trimRecents() {
if (recents.size() > BOOKMARK_RECENTS_MAX) {
recents.erase(recents.begin(), recents.begin()+1);
}
}
void BookmarkMgr::addRange(const BookmarkRangeEntryPtr& re) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
ranges.push_back(re);
rangesSorted = false;
}
void BookmarkMgr::removeRange(const BookmarkRangeEntryPtr& re) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto re_i = std::find(ranges.begin(), ranges.end(), re);
if (re_i != ranges.end()) {
ranges.erase(re_i);
}
}
BookmarkRangeList BookmarkMgr::getRanges() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (!rangesSorted) {
std::sort(ranges.begin(), ranges.end(), [](const BookmarkRangeEntryPtr a, const BookmarkRangeEntryPtr b) -> bool { return a->freq < b->freq;});
rangesSorted = true;
}
return ranges;
}
void BookmarkMgr::clearRanges() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
ranges.clear();
}
BookmarkEntryPtr BookmarkMgr::demodToBookmarkEntry(const DemodulatorInstancePtr& demod) {
BookmarkEntryPtr be(new BookmarkEntry);
be->bandwidth = demod->getBandwidth();
be->type = demod->getDemodulatorType();
be->label = demod->getDemodulatorUserLabel();
be->frequency = demod->getFrequency();
//fine to do so here, so long nobody overrides be->node, DataNode will be
//deleted at last BookmarkEntryPtr be ref.
be->node = new DataNode;
wxGetApp().getDemodMgr().saveInstance(be->node, demod);
return be;
}
std::wstring BookmarkMgr::getSafeWstringValue(DataNode* node, const std::string& childNodeName) {
std::wstring decodedWString;
if (node != nullptr) {
DataNode* childNode = node->getNext(childNodeName.c_str());
//1) decode as encoded wstring:
try {
childNode->element()->get(decodedWString);
} catch (const DataTypeMismatchException &) {
//2) wstring decode fail, try simple std::string
std::string decodedStdString;
try {
childNode->element()->get(decodedStdString);
//use wxString for a clean conversion to a wstring:
decodedWString = wxString(decodedStdString).ToStdWstring();
} catch (const DataTypeMismatchException &) {
//nothing works, return an empty string.
decodedWString = L"";
}
}
}
return decodedWString;
}
BookmarkEntryPtr BookmarkMgr::nodeToBookmark(DataNode *node) {
if (!node->hasAnother("frequency") || !node->hasAnother("type") || !node->hasAnother("bandwidth")) {
return nullptr;
}
BookmarkEntryPtr be(new BookmarkEntry());
node->getNext("frequency")->element()->get(be->frequency);
node->getNext("type")->element()->get(be->type);
node->getNext("bandwidth")->element()->get(be->bandwidth);
if (node->hasAnother("user_label")) {
be->label = BookmarkMgr::getSafeWstringValue( node, "user_label");
}
node->rewindAll();
//fine to do so here, so long nobody overrides be->node, DataNode will be
//deleted at last BookmarkEntryPtr be ref.
//copy data from *node.
be->node = new DataNode("node",*node);
return be;
}
std::wstring BookmarkMgr::getBookmarkEntryDisplayName(const BookmarkEntryPtr& bmEnt) {
std::wstring dispName = bmEnt->label;
if (dispName.empty()) {
std::string freqStr = frequencyToStr(bmEnt->frequency) + " " + bmEnt->type;
dispName = wxString(freqStr).ToStdWstring();
}
return dispName;
}
std::wstring BookmarkMgr::getActiveDisplayName(const DemodulatorInstancePtr& demod) {
std::wstring activeName = demod->getDemodulatorUserLabel();
if (activeName.empty()) {
std::string wstr = frequencyToStr(demod->getFrequency()) + " " + demod->getDemodulatorType();
activeName = wxString(wstr).ToStdWstring();
}
return activeName;
}
void BookmarkMgr::removeActive(const DemodulatorInstancePtr& demod) {
if (demod == nullptr) {
return;
}
//Delete demodulator
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, true);
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false);
wxGetApp().removeDemodulator(demod);
wxGetApp().getDemodMgr().deleteThread(demod);
}