14 #include <fc_config.h>
21 #include <QJsonDocument>
22 #include <QJsonObject>
47 QString source()
const {
return m_source; }
50 QFileInfo destination(
const QString &prefix)
const
52 return QFileInfo(prefix + m_destination);
56 bool is_valid()
const {
return m_error.isEmpty(); }
59 QString error()
const {
return m_error; }
65 static file_info from_json(
const QJsonValue &input);
70 : m_source(), m_destination(),
71 m_error(QString::fromUtf8(
_(
"Invalid file")))
76 file_info(
const QString &source,
const QString &destination)
77 : m_source(source), m_destination(destination), m_error()
83 file_info(
const QString &source_destination)
84 : file_info(source_destination, source_destination)
89 void set_error(
const QString &error) { m_error = error; }
94 if (m_destination.isEmpty()) {
96 set_error(QString::fromUtf8(
_(
"Empty path")));
97 }
else if (m_destination.contains(QStringLiteral(
".."))) {
100 QString::fromUtf8(
_(
"Illegal path \"%1\"")).arg(m_destination));
105 QString m_destination;
109 file_info file_info::from_json(
const QJsonValue &input)
111 if (input.isString()) {
113 return file_info(input.toString());
114 }
else if (input.isObject()) {
117 auto obj = input.toObject();
119 if (!obj.contains(
"dest") || !obj[
"dest"].isString()) {
120 auto err = file_info();
122 err.set_error(QString::fromUtf8(
_(
"Missing \"dest\" field")));
125 auto destination = obj[
"dest"].toString();
127 if (obj.contains(
"url")) {
128 if (obj[
"url"].isString()) {
129 return file_info(obj[
"url"].toString(), destination);
131 auto err = file_info();
133 err.set_error(QString::fromUtf8(
_(
"\"url\" field is not a string")));
137 return file_info(destination);
155 return _(
"Recursive dependencies too deep");
158 if (!url.isValid()) {
159 return _(
"No valid URL given");
163 return _(
"This does not look like modpack URL");
166 qInfo().noquote() << QString::fromUtf8(
_(
"Installing modpack %1 from %2"))
168 .arg(url.toString());
171 return _(
"Cannot install to given directory hierarchy");
174 if (mcb !=
nullptr) {
176 mcb(QString::fromUtf8(
_(
"Downloading \"%1\" control file."))
177 .arg(url.fileName()));
181 if (!json.isObject()) {
182 return _(
"Cannot fetch and parse modpack list");
185 auto info_value = json[
"info"];
186 if (!info_value.isObject()) {
188 return _(
"\"info\" is not an object");
190 auto info = info_value.toObject();
192 if (!info[
"options"].isString()) {
194 return _(
"\"info.options\" is not a string");
197 auto list_capstr = info[
"options"].toString();
200 qCritical() <<
"Incompatible control file:";
201 qCritical() <<
" control file options:" << list_capstr;
204 return _(
"Modpack control file is incompatible");
207 if (!info[
"name"].isString()) {
209 return _(
"\"info.name\" is not a string");
211 auto mpname = info[
"name"].toString();
212 if (mpname.isEmpty()) {
213 return _(
"Modpack name is empty");
216 if (!info[
"version"].isString()) {
218 return _(
"\"info.version\" is not a string");
220 auto mpver = info[
"version"].toString();
222 if (!info[
"type"].isString()) {
224 return _(
"\"info.type\" is not a string");
226 auto mptype = info[
"type"].toString();
227 auto type = modpack_type_by_name(qUtf8Printable(mptype),
fc_strcasecmp);
228 if (!modpack_type_is_valid(type)) {
229 return _(
"Illegal modpack type");
232 if (!info[
"base_url"].isString()) {
234 return _(
"\"info.base_url\" is not a string");
236 auto base_url = QUrl(info[
"base_url"].toString());
237 base_url = url.resolved(base_url);
241 if (!base_url.fileName().isEmpty()) {
242 base_url.setPath(base_url.path(QUrl::FullyEncoded)
243 + QStringLiteral(
"/"));
249 auto deps = json[
"dependencies"];
250 if (!deps.isUndefined()) {
251 if (!deps.isArray()) {
253 return _(
"\"dependencies\" is not an array");
256 for (
const auto &depref : deps.toArray()) {
257 if (!depref.isObject()) {
259 return _(
"\"dependencies\" contains a non-object");
263 auto obj = depref.toObject();
265 if (!obj.contains(
"url") || !obj[
"url"].isString()) {
267 return _(
"Dependency has no \"url\" field or it is not a string");
269 auto dep_url = obj[
"url"].toString();
271 if (!obj.contains(
"modpack") || !obj[
"modpack"].isString()) {
274 "Dependency has no \"modpack\" field or it is not a string");
276 auto dep_name = obj[
"modpack"].toString();
278 if (!obj.contains(
"type") || !obj[
"type"].isString()) {
280 return _(
"Dependency has no \"type\" field or it is not a string");
282 auto dep_type_str = obj[
"type"].toString();
285 modpack_type_by_name(qUtf8Printable(dep_type_str),
fc_strcasecmp);
286 if (!modpack_type_is_valid(type)) {
287 qCritical() <<
"Illegal modpack type" << dep_type_str;
288 return _(
"Illegal modpack type");
291 if (!obj.contains(
"version") || !obj[
"version"].isString()) {
294 "Dependency has no \"version\" field or it is not a string");
296 auto dep_version = obj[
"version"].toString();
301 if (!inst_ver || !cvercmp_max(qUtf8Printable(dep_version), inst_ver)) {
302 qInfo() <<
"Dependency modpack" << QString(inst_ver) << dep_version
305 if (mcb !=
nullptr) {
306 mcb(
_(
"Download dependency modpack"));
309 auto dep_qurl = QUrl(dep_url);
310 if (dep_qurl.isRelative()) {
311 dep_qurl = url.resolved(dep_qurl);
317 if (msg !=
nullptr) {
328 std::vector<file_info> required_files;
330 auto files = json[
"files"];
331 if (!files.isArray()) {
333 return _(
"\"files\" is not an array");
337 for (
const auto &fref : files.toArray()) {
338 auto info = file_info::from_json(fref);
339 if (!info.is_valid()) {
341 auto error = info.error();
343 << QString::fromUtf8(
344 _(
"Error parsing modpack control file: file %1:"))
346 qWarning().noquote() << error;
350 return _(
"Error parsing modpack control file");
353 required_files.push_back(info);
359 if (pbcb !=
nullptr) {
360 pbcb(downloaded, required_files.size() + 1);
366 + ((type == MPT_SCENARIO) ? QStringLiteral(
"/scenarios/")
367 : QStringLiteral(
"/" DATASUBDIR
"/"));
370 bool full_success =
true;
371 for (
auto info : required_files) {
372 auto destination = info.destination(local_dir);
375 qDebug() <<
"Create directory:" << destination.absolutePath();
376 if (!destination.absoluteDir().mkpath(
".")) {
377 return _(
"Cannot create required directories");
380 if (mcb !=
nullptr) {
381 mcb(QString::fromUtf8(
_(
"Downloading %1")).arg(info.source()));
385 auto source = base_url.resolved(info.source());
386 qDebug() <<
"Download" << source.toDisplayString() <<
"to"
387 << destination.absoluteFilePath();
390 source, qUtf8Printable(destination.absoluteFilePath()), mcb)) {
391 if (mcb !=
nullptr) {
392 mcb(QString::fromUtf8(
_(
"Failed to download %1"))
393 .arg(info.source()));
395 full_success =
false;
398 if (pbcb !=
nullptr) {
401 pbcb(downloaded, required_files.size() + 1);
406 return _(
"Some parts of the modpack failed to install.");
422 if (!json.isObject()) {
423 return _(
"Cannot fetch and parse modpack list");
426 auto info = json[
"info"];
427 if (!info.isObject()) {
429 return _(
"\"info\" is not an object");
432 if (!info[
"options"].isString()) {
434 return _(
"\"info.options\" is not a string");
437 auto list_capstr = info[
"options"].toString();
440 qCritical() <<
"Incompatible modpack list file:";
441 qCritical() <<
" list file options:" << list_capstr;
444 return _(
"Modpack list is incompatible");
447 if (info[
"message"].isString()) {
448 mcb(info[
"message"].toString());
451 auto modpacks = json[
"modpacks"];
452 if (!modpacks.isArray()) {
454 return _(
"\"modpacks\" is not an array");
457 for (
const auto &mpref : modpacks.toArray()) {
459 if (!mpref.isObject()) {
461 return _(
"\"modpacks\" contains a non-object");
463 auto mp = mpref.toObject();
466 if (!mp[
"name"].isString()) {
468 return _(
"Modpack \"name\" is missing or is not a string");
470 auto name = mp[
"name"].toString();
471 if (
name.isEmpty()) {
472 return _(
"Modpack name is empty");
476 if (!mp[
"version"].isString()) {
478 return _(
"Modpack \"version\" is missing or is not a string");
480 auto version = mp[
"version"].toString();
483 if (!mp[
"license"].isString()) {
485 return _(
"Modpack \"license\" is missing or is not a string");
487 auto license = mp[
"license"].toString();
490 if (!mp[
"type"].isString()) {
492 return _(
"Modpack \"type\" is missing or is not a string");
494 auto type_str = mp[
"type"].toString();
497 modpack_type_by_name(qUtf8Printable(type_str),
fc_strcasecmp);
498 if (!modpack_type_is_valid(type)) {
499 qCritical() <<
"Illegal modpack type" << type_str;
500 return _(
"Illegal modpack type");
504 if (mp.contains(
"subtype") && !mp[
"subtype"].isString()) {
506 return _(
"Modpack \"subtype\" is not a string");
508 auto subtype = mp[
"subtype"].toString(QStringLiteral(
"-"));
511 if (!mp[
"url"].isString()) {
513 return _(
"Modpack \"url\" is missing or is not a string");
515 auto url = QUrl(mp[
"url"].toString());
516 if (!url.isValid()) {
517 qCritical() <<
"Invalid URL" << mp[
"url"].toString() <<
":"
518 << url.errorString();
519 return _(
"Invalid URL");
521 auto resolved = url.isRelative() ?
fcmp->
list_url.resolved(url) : url;
524 if (mp.contains(
"notes") && !mp[
"notes"].isString()) {
526 return _(
"Modpack \"notes\" is not a string");
528 auto notes = mp[
"notes"].toString(QStringLiteral(
""));
531 cb(
name, resolved, version, license, type,
532 QString::fromUtf8(
_(qUtf8Printable(subtype))), notes);
bool has_capabilities(const char *us, const char *them)
This routine returns true if all the mandatory capabilities in us appear in them.
const char * download_modpack(const QUrl &url, const struct fcmp_params *fcmp, const dl_msg_callback &mcb, const dl_pb_callback &pbcb, int recursion)
Download modpack from a given URL.
const char * download_modpack_list(const struct fcmp_params *fcmp, const modpack_list_setup_cb &cb, const dl_msg_callback &mcb)
Download modpack list.
std::function< void(int downloaded, int max)> dl_pb_callback
nf_errmsg dl_msg_callback
std::function< void(const QString &name, const QUrl &url, const QString &version, const QString &license, enum modpack_type type, const QString &subtype, const QString ¬es)> modpack_list_setup_cb
const char * mpdb_installed_version(const char *name, enum modpack_type type)
Return version of modpack.
bool mpdb_update_modpack(const char *name, enum modpack_type type, const char *version)
Update modpack information in database.
bool netfile_download_file(const QUrl &url, const char *filename, const nf_errmsg &cb)
Fetch file from given URL and save as given filename.
QJsonDocument netfile_get_json_file(const QUrl &url, const nf_errmsg &cb)
Fetch a JSON file from the net.
static int recursion[AIT_LAST]
int fc_strcasecmp(const char *str0, const char *str1)
Compare strings like strcmp(), but ignoring case.