mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Add search mods request
Add paged search mods request suitable for showing a real time list of results for a user provided search expression.
This commit is contained in:
1 parent
6039715a53
commit
3eb63db313
5 files changed
+200
-2
No files matched your search
@@ -1087,7 +1087,7 @@ modCommands.add(new libCommand.Command({
|
||||
definition: [["list", "l"], "List mods stored in the cluster", (yargs) => {
|
||||
yargs.options({
|
||||
"fields": {
|
||||
describe: "Fields",
|
||||
describe: "Fields to show, supports 'all'.",
|
||||
array: true,
|
||||
type: "string",
|
||||
default: ["name", "version", "title", "factorio_version"],
|
||||
@@ -1109,6 +1109,65 @@ modCommands.add(new libCommand.Command({
|
||||
},
|
||||
}));
|
||||
|
||||
modCommands.add(new libCommand.Command({
|
||||
definition: ["search <factorio-version> [query]", "Search mods stored in the cluster", (yargs) => {
|
||||
yargs.positional("factorio-version", { describe: "Major version of Factorio to search for", type: "string" });
|
||||
yargs.positional("query", { describe: "Search query", type: "string", default: "" });
|
||||
yargs.options({
|
||||
"page": {
|
||||
describe: "Result page to show",
|
||||
type: "number",
|
||||
default: 1,
|
||||
},
|
||||
"page-size": {
|
||||
describe: "Results per page to show",
|
||||
type: "number",
|
||||
default: 10,
|
||||
},
|
||||
"sort": {
|
||||
describe: "sort results by given field",
|
||||
type: "string",
|
||||
},
|
||||
"sort-order": {
|
||||
describe: "order to sort results in (asc/desc)",
|
||||
type: "string",
|
||||
default: "asc",
|
||||
},
|
||||
"fields": {
|
||||
describe: "Fields to show, supports 'all'.",
|
||||
array: true,
|
||||
type: "string",
|
||||
default: ["name", "version", "title", "factorio_version"],
|
||||
},
|
||||
});
|
||||
}],
|
||||
handler: async function(args, control) {
|
||||
let response = await libLink.messages.searchMods.send(control, {
|
||||
"query": args.query,
|
||||
"factorio_version": args.factorioVersion,
|
||||
"page_size": args.pageSize,
|
||||
"page": args.page,
|
||||
"sort": args.sort,
|
||||
"sort_order": args.sortOrder,
|
||||
});
|
||||
let results = response.results.flatMap(result => result.versions);
|
||||
if (!args.fields.includes("all")) {
|
||||
for (let entry of results) {
|
||||
for (let field of Object.keys(entry)) {
|
||||
if (!args.fields.includes(field)) {
|
||||
delete entry[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let issue of response.query_issues) {
|
||||
print(issue);
|
||||
}
|
||||
print(`page ${args.page} of ${response.page_count} (${response.result_count} results)`);
|
||||
print(asTable(results));
|
||||
},
|
||||
}));
|
||||
|
||||
modCommands.add(new libCommand.Command({
|
||||
definition: ["upload <file>", "Upload mod to the cluster", (yargs) => {
|
||||
yargs.positional("file", { describe: "File to upload", type: "string" });
|
||||
|
||||
@@ -800,6 +800,42 @@ messages.listMods = new Request({
|
||||
},
|
||||
});
|
||||
|
||||
messages.searchMods = new Request({
|
||||
type: "search_mods",
|
||||
links: ["control-master"],
|
||||
permission: "core.mod.search",
|
||||
requestRequired: ["query", "factorio_version", "page"],
|
||||
requestProperties: {
|
||||
"query": { type: "string" },
|
||||
"factorio_version": { type: "string" },
|
||||
"page_size": { type: "integer" },
|
||||
"page": { type: "integer" },
|
||||
"sort": { type: "string" },
|
||||
"sort_order": { type: "string" },
|
||||
},
|
||||
responseProperties: {
|
||||
"query_issues": {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
},
|
||||
"page_count": { type: "integer" },
|
||||
"result_count": { type: "integer" },
|
||||
"results": {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
"name": { type: "string" },
|
||||
"versions": {
|
||||
type: "array",
|
||||
items: libData.ModInfo.jsonSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
messages.setModSubscriptions = new Request({
|
||||
type: "set_mod_subscriptions",
|
||||
links: ["control-master"],
|
||||
|
||||
@@ -553,7 +553,13 @@ definePermission({
|
||||
definePermission({
|
||||
name: "core.mod.list",
|
||||
title: "List mods",
|
||||
description: "Get the full list of mods stored on the master",
|
||||
description: "Get the full list of mods stored on the master.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
definePermission({
|
||||
name: "core.mod.search",
|
||||
title: "Search mods",
|
||||
description: "Search through the list of mods stored on the master.",
|
||||
grantByDefault: true,
|
||||
});
|
||||
definePermission({
|
||||
|
||||
@@ -18,6 +18,8 @@ const libUsers = require("@clusterio/lib/users");
|
||||
const BaseConnection = require("./BaseConnection");
|
||||
const routes = require("./routes");
|
||||
|
||||
const strcmp = new Intl.Collator(undefined, { numerice: "true", sensitivity: "base" }).compare;
|
||||
|
||||
const lastQueryLogTime = new libPrometheus.Gauge(
|
||||
"clusterio_master_last_query_log_duration_seconds",
|
||||
"Time in seconds the last log query took to execute."
|
||||
@@ -543,6 +545,94 @@ class ControlConnection extends BaseConnection {
|
||||
return { list: [...this._master.mods.values()].map(mod => mod.toJSON()) };
|
||||
}
|
||||
|
||||
static termsMatchesMod(terms, mod) {
|
||||
for (let term of terms) {
|
||||
if (term.type === "word") {
|
||||
if (!libHelpers.wordMatches(term,
|
||||
mod.name, mod.version, mod.title, mod.author, mod.contact,
|
||||
mod.homepage, mod.description, mod.filename
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
} else if (term.type === "attribute") {
|
||||
if (!libHelpers.wordMatches(term.value, mod[term.name])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async searchModsRequestHandler(message) {
|
||||
let query = libHelpers.parseSearchString(message.data.query, {
|
||||
name: "word",
|
||||
// version
|
||||
title: "word",
|
||||
author: "word",
|
||||
contact: "word",
|
||||
homepage: "word",
|
||||
description: "word",
|
||||
// factorioVersion
|
||||
// dependencies
|
||||
filename: "word",
|
||||
// size
|
||||
hash: "word",
|
||||
});
|
||||
let factorioVersion = message.data.factorio_version;
|
||||
|
||||
let results = new Map();
|
||||
for (let mod of this._master.mods.values()) {
|
||||
if (
|
||||
mod.factorioVersion !== factorioVersion
|
||||
|| !ControlConnection.termsMatchesMod(query.terms, mod)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let result = results.get(mod.name);
|
||||
if (!result) {
|
||||
result = {
|
||||
name: mod.name,
|
||||
versions: [],
|
||||
};
|
||||
results.set(mod.name, result);
|
||||
}
|
||||
result.versions.push(mod);
|
||||
}
|
||||
for (let result of results.values()) {
|
||||
result.versions.sort((a, b) => b.integerVersion - a.integerVersion);
|
||||
result.versions.map(e => e.toJSON());
|
||||
}
|
||||
let resultList = [...results.values()];
|
||||
|
||||
const sort = message.data.sort;
|
||||
if (sort) {
|
||||
const sorters = {
|
||||
name: (a, b) => strcmp(a.versions[0].name, b.versions[0].name),
|
||||
title: (a, b) => strcmp(a.versions[0].title, b.versions[0].title),
|
||||
author: (a, b) => strcmp(a.versions[0].author, b.versions[0].author),
|
||||
};
|
||||
if (!Object.prototype.hasOwnProperty.call(sorters, sort)) {
|
||||
throw new libErrors.RequestError(`Invalid value for sort: ${sort}`);
|
||||
}
|
||||
resultList.sort(sorters[sort]);
|
||||
let order = message.data.sort_order;
|
||||
if (order === "desc") {
|
||||
resultList.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
const page = message.data.page;
|
||||
const pageSize = message.data.page_size || 10;
|
||||
resultList = resultList.slice((page - 1) * pageSize, page * pageSize);
|
||||
|
||||
return {
|
||||
query_issues: query.issues,
|
||||
page_count: Math.ceil(results.size / pageSize),
|
||||
result_count: results.size,
|
||||
results: resultList,
|
||||
};
|
||||
}
|
||||
|
||||
async setModSubscriptionsRequestHandler(message) {
|
||||
this.modSubscriptions = message.data;
|
||||
}
|
||||
|
||||
@@ -929,6 +929,13 @@ describe("Integration of Clusterio", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod search", function() {
|
||||
it("searches the list of mods", async function() {
|
||||
let result = await execCtl("mod search 1.1 name:empty_mod");
|
||||
assert(result.stdout.indexOf("empty_mod") !== -1, "empty_mod is not in the result");
|
||||
});
|
||||
});
|
||||
|
||||
describe("mod download", function() {
|
||||
it("downloads a mod", async function() {
|
||||
await fs.unlink(path.join("temp", "test", "empty_mod_1.0.0.zip"));
|
||||
|
||||
Reference in new issue
Block a user