feat: add OPDS server reordering with sortOrder field and numeric stepper
Servers are sorted by a persistent sortOrder field (ties broken alphabetically). On-device editing uses a new NumericStepperActivity with side buttons for ±1 and face buttons for ±10. The web UI gets up/down arrow buttons and a POST /api/opds/reorder endpoint. Made-with: Cursor
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <esp_mac.h>
|
||||
#include <mbedtls/base64.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
@@ -81,6 +82,7 @@ bool OpdsServerStore::saveToFile() const {
|
||||
obj["username"] = server.username;
|
||||
obj["password_obf"] = obfuscateToBase64(server.password);
|
||||
obj["download_path"] = server.downloadPath;
|
||||
obj["sort_order"] = server.sortOrder;
|
||||
}
|
||||
|
||||
String json;
|
||||
@@ -116,13 +118,32 @@ bool OpdsServerStore::loadFromFile() {
|
||||
if (!server.password.empty()) needsResave = true;
|
||||
}
|
||||
server.downloadPath = obj["download_path"] | std::string("/");
|
||||
server.sortOrder = obj["sort_order"] | 0;
|
||||
if (server.sortOrder == 0) needsResave = true;
|
||||
servers.push_back(std::move(server));
|
||||
}
|
||||
|
||||
// Assign sequential sort orders to servers loaded without one
|
||||
bool anyZero = false;
|
||||
for (const auto& s : servers) {
|
||||
if (s.sortOrder == 0) {
|
||||
anyZero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (anyZero) {
|
||||
for (size_t i = 0; i < servers.size(); i++) {
|
||||
if (servers[i].sortOrder == 0) {
|
||||
servers[i].sortOrder = static_cast<int>(i) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sortServers();
|
||||
LOG_DBG("OPS", "Loaded %zu OPDS servers from file", servers.size());
|
||||
|
||||
if (needsResave) {
|
||||
LOG_DBG("OPS", "Resaving JSON with obfuscated passwords");
|
||||
LOG_DBG("OPS", "Resaving JSON with sort_order / obfuscated passwords");
|
||||
saveToFile();
|
||||
}
|
||||
return true;
|
||||
@@ -149,6 +170,7 @@ bool OpdsServerStore::migrateFromSettings() {
|
||||
server.url = SETTINGS.opdsServerUrl;
|
||||
server.username = SETTINGS.opdsUsername;
|
||||
server.password = SETTINGS.opdsPassword;
|
||||
server.sortOrder = 1;
|
||||
servers.push_back(std::move(server));
|
||||
|
||||
if (saveToFile()) {
|
||||
@@ -171,7 +193,15 @@ bool OpdsServerStore::addServer(const OpdsServer& server) {
|
||||
}
|
||||
|
||||
servers.push_back(server);
|
||||
LOG_DBG("OPS", "Added server: %s", server.name.c_str());
|
||||
if (servers.back().sortOrder == 0) {
|
||||
int maxOrder = 0;
|
||||
for (size_t i = 0; i + 1 < servers.size(); i++) {
|
||||
maxOrder = std::max(maxOrder, servers[i].sortOrder);
|
||||
}
|
||||
servers.back().sortOrder = maxOrder + 1;
|
||||
}
|
||||
LOG_DBG("OPS", "Added server: %s (order=%d)", server.name.c_str(), servers.back().sortOrder);
|
||||
sortServers();
|
||||
return saveToFile();
|
||||
}
|
||||
|
||||
@@ -181,7 +211,8 @@ bool OpdsServerStore::updateServer(size_t index, const OpdsServer& server) {
|
||||
}
|
||||
|
||||
servers[index] = server;
|
||||
LOG_DBG("OPS", "Updated server: %s", server.name.c_str());
|
||||
sortServers();
|
||||
LOG_DBG("OPS", "Updated server: %s (order=%d)", server.name.c_str(), server.sortOrder);
|
||||
return saveToFile();
|
||||
}
|
||||
|
||||
@@ -201,3 +232,30 @@ const OpdsServer* OpdsServerStore::getServer(size_t index) const {
|
||||
}
|
||||
return &servers[index];
|
||||
}
|
||||
|
||||
bool OpdsServerStore::moveServer(size_t index, int direction) {
|
||||
if (index >= servers.size()) return false;
|
||||
|
||||
size_t target;
|
||||
if (direction < 0) {
|
||||
if (index == 0) return false;
|
||||
target = index - 1;
|
||||
} else {
|
||||
if (index + 1 >= servers.size()) return false;
|
||||
target = index + 1;
|
||||
}
|
||||
|
||||
std::swap(servers[index].sortOrder, servers[target].sortOrder);
|
||||
sortServers();
|
||||
return saveToFile();
|
||||
}
|
||||
|
||||
void OpdsServerStore::sortServers() {
|
||||
std::sort(servers.begin(), servers.end(), [](const OpdsServer& a, const OpdsServer& b) {
|
||||
if (a.sortOrder != b.sortOrder) return a.sortOrder < b.sortOrder;
|
||||
const auto& nameA = a.name.empty() ? a.url : a.name;
|
||||
const auto& nameB = b.name.empty() ? b.url : b.name;
|
||||
return std::lexicographical_compare(nameA.begin(), nameA.end(), nameB.begin(), nameB.end(),
|
||||
[](char ca, char cb) { return tolower(ca) < tolower(cb); });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user