aboutproject_moc.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * aboutproject_moc.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "aboutproject_moc.h"
  12. #include "ui_aboutproject_moc.h"
  13. #if defined(VCMI_ANDROID)
  14. #include <QAndroidJniObject>
  15. #endif
  16. #if defined(VCMI_IOS)
  17. #include "ios/iOS_utils.h"
  18. #endif
  19. #include "../updatedialog_moc.h"
  20. #include "../helper.h"
  21. #include "../../lib/GameConstants.h"
  22. #include "../../lib/VCMIDirs.h"
  23. #include "../../lib/filesystem/CZipSaver.h"
  24. #include "../../lib/json/JsonUtils.h"
  25. #include "../../lib/filesystem/Filesystem.h"
  26. void AboutProjectView::hideAndStretchWidget(QGridLayout * layout, QWidget * toHide, QWidget * toStretch)
  27. {
  28. toHide->hide();
  29. int index = layout->indexOf(toStretch);
  30. int row;
  31. int col;
  32. int unused;
  33. layout->getItemPosition(index, &row, &col, &unused, &unused);
  34. layout->removeWidget(toHide);
  35. layout->removeWidget(toStretch);
  36. layout->addWidget(toStretch, row, col, 1, -1);
  37. }
  38. AboutProjectView::AboutProjectView(QWidget * parent)
  39. : QWidget(parent)
  40. , ui(std::make_unique<Ui::AboutProjectView>())
  41. {
  42. ui->setupUi(this);
  43. ui->lineEditUserDataDir->setText(pathToQString(VCMIDirs::get().userDataPath()));
  44. ui->lineEditGameDir->setText(pathToQString(VCMIDirs::get().binaryPath()));
  45. ui->lineEditTempDir->setText(pathToQString(VCMIDirs::get().userLogsPath()));
  46. ui->lineEditConfigDir->setText(pathToQString(VCMIDirs::get().userConfigPath()));
  47. ui->lineEditBuildVersion->setText(QString::fromStdString(GameConstants::VCMI_VERSION));
  48. ui->lineEditOperatingSystem->setText(QSysInfo::prettyProductName());
  49. #ifdef VCMI_MOBILE
  50. // On mobile platforms these directories are generally not accessible from phone itself, only via USB connection from PC
  51. // Remove "Open" buttons and stretch line with text into now-empty space
  52. hideAndStretchWidget(ui->gridLayout, ui->openGameDataDir, ui->lineEditGameDir);
  53. #ifdef VCMI_ANDROID
  54. hideAndStretchWidget(ui->gridLayout, ui->openUserDataDir, ui->lineEditUserDataDir);
  55. hideAndStretchWidget(ui->gridLayout, ui->openTempDir, ui->lineEditTempDir);
  56. hideAndStretchWidget(ui->gridLayout, ui->openConfigDir, ui->lineEditConfigDir);
  57. #endif
  58. #endif
  59. }
  60. AboutProjectView::~AboutProjectView() = default;
  61. void AboutProjectView::changeEvent(QEvent *event)
  62. {
  63. if(event->type() == QEvent::LanguageChange)
  64. ui->retranslateUi(this);
  65. QWidget::changeEvent(event);
  66. }
  67. void AboutProjectView::on_updatesButton_clicked()
  68. {
  69. UpdateDialog::showUpdateDialog(true);
  70. }
  71. void AboutProjectView::on_openGameDataDir_clicked()
  72. {
  73. Helper::revealDirectoryInFileBrowser(ui->lineEditGameDir->text());
  74. }
  75. void AboutProjectView::on_openUserDataDir_clicked()
  76. {
  77. Helper::revealDirectoryInFileBrowser(ui->lineEditUserDataDir->text());
  78. }
  79. void AboutProjectView::on_openTempDir_clicked()
  80. {
  81. Helper::revealDirectoryInFileBrowser(ui->lineEditTempDir->text());
  82. }
  83. void AboutProjectView::on_openConfigDir_clicked()
  84. {
  85. Helper::revealDirectoryInFileBrowser(ui->lineEditConfigDir->text());
  86. }
  87. void AboutProjectView::on_pushButtonDiscord_clicked()
  88. {
  89. QDesktopServices::openUrl(QUrl("https://discord.gg/chBT42V"));
  90. }
  91. void AboutProjectView::on_pushButtonGithub_clicked()
  92. {
  93. QDesktopServices::openUrl(QUrl("https://github.com/vcmi/vcmi"));
  94. }
  95. void AboutProjectView::on_pushButtonHomepage_clicked()
  96. {
  97. QDesktopServices::openUrl(QUrl("https://vcmi.eu/"));
  98. }
  99. void AboutProjectView::on_pushButtonBugreport_clicked()
  100. {
  101. QDesktopServices::openUrl(QUrl("https://github.com/vcmi/vcmi/issues"));
  102. }
  103. static QString gatherDeviceInfo()
  104. {
  105. QString info;
  106. QTextStream ts(&info);
  107. ts << "VCMI version: " << QString::fromStdString(GameConstants::VCMI_VERSION) << '\n';
  108. ts << "Operating system: " << QSysInfo::prettyProductName() << " (" << QSysInfo::kernelVersion() << ")" << '\n';
  109. ts << "CPU architecture: " << QSysInfo::currentCpuArchitecture() << '\n';
  110. ts << "Qt version: " << QT_VERSION_STR << '\n';
  111. #if defined(VCMI_ANDROID)
  112. QString model = QAndroidJniObject::getStaticObjectField(
  113. "android/os/Build",
  114. "MODEL",
  115. "Ljava/lang/String;"
  116. ).toString();
  117. QString manufacturer = QAndroidJniObject::getStaticObjectField(
  118. "android/os/Build",
  119. "MANUFACTURER",
  120. "Ljava/lang/String;"
  121. ).toString();
  122. ts << "Device model: " << model << '\n';
  123. ts << "Manufacturer: " << manufacturer << '\n';
  124. #endif
  125. #if defined(VCMI_IOS)
  126. ts << "Device model: " << QString::fromStdString(iOS_utils::iphoneHardwareId()) << '\n';
  127. ts << "Manufacturer: " << "Apple" << '\n';
  128. #endif
  129. return info;
  130. }
  131. void AboutProjectView::on_pushButtonExportLogs_clicked()
  132. {
  133. QDir tempDir(ui->lineEditTempDir->text());
  134. #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
  135. // cleanup old temp archives from previous runs (delete now)
  136. {
  137. QDir tdir(QDir::tempPath());
  138. const QFileInfoList old = tdir.entryInfoList(QStringList() << "vcmi-logs-*.zip", QDir::Files, QDir::Name);
  139. for (const QFileInfo & fi : old)
  140. QFile::remove(fi.absoluteFilePath());
  141. }
  142. // On mobile: write archive to system temp and send via platform share (no save dialog)
  143. const QString tmpDir = QDir::tempPath();
  144. const QString outPath = QDir(tmpDir).filePath(QString("vcmi-logs-%1.zip").arg(QString::number(QDateTime::currentMSecsSinceEpoch())));
  145. #else
  146. const QString defaultName = QDir::home().filePath("vcmi-logs.zip");
  147. QString outPath = QFileDialog::getSaveFileName(this, tr("Save logs"), defaultName, tr("Zip archives (*.zip)"));
  148. if (outPath.isEmpty())
  149. return;
  150. if (!outPath.endsWith(".zip", Qt::CaseInsensitive))
  151. outPath += ".zip";
  152. #endif
  153. QFileInfoList files = tempDir.entryInfoList({ "*.txt" }, QDir::Files, QDir::Name);
  154. files.append(QDir(ui->lineEditConfigDir->text()).entryInfoList({ "*.json", "*.ini" }, QDir::Files, QDir::Name));
  155. // build data dir file/folder listing and add as a virtual text file
  156. const QString dataDirPath = ui->lineEditUserDataDir->text();
  157. QString listing;
  158. QDir dataDir(dataDirPath);
  159. QDirIterator it(dataDirPath, QDir::NoDotAndDotDot | QDir::AllEntries, QDirIterator::Subdirectories);
  160. QTextStream ts(&listing);
  161. while (it.hasNext())
  162. {
  163. const QString path = it.next();
  164. const QString rel = dataDir.relativeFilePath(path);
  165. QFileInfo info(path);
  166. if (rel.startsWith(QLatin1String("Saves/")))
  167. continue;
  168. if (info.isDir())
  169. ts << QChar('D') << QLatin1Char(' ') << rel << '\n';
  170. else
  171. ts << QChar('F') << QLatin1Char(' ') << rel << QLatin1String(" (") << info.size() << QLatin1String(")") << '\n';
  172. }
  173. try
  174. {
  175. // create zip and add .txt files
  176. std::shared_ptr<CIOApi> api = std::make_shared<CDefaultIOApi>();
  177. boost::filesystem::path archivePath(outPath.toStdString());
  178. CZipSaver saver(api, archivePath);
  179. for (const QFileInfo & fi : files)
  180. {
  181. // Skip persistent storage to avoid logging private data (e.g. lobby login tokens)
  182. if (fi.fileName().compare(QStringLiteral("persistentStorage.json"), Qt::CaseInsensitive) == 0)
  183. continue;
  184. QFile f(fi.absoluteFilePath());
  185. if (!f.open(QIODevice::ReadOnly))
  186. continue;
  187. QByteArray data = f.readAll();
  188. auto stream = saver.addFile(fi.fileName().toStdString());
  189. stream->write(reinterpret_cast<const ui8 *>(data.constData()), data.size());
  190. }
  191. // try to include the last save reported in settings.json
  192. {
  193. auto json = JsonUtils::assembleFromFiles("config/settings.json");
  194. if(!json["general"].isNull() && !json["general"]["lastSave"].isNull())
  195. {
  196. try
  197. {
  198. auto lastSavePath = json["general"]["lastSave"].String();
  199. const auto rsave = ResourcePath(lastSavePath, EResType::SAVEGAME);
  200. const auto * rhandler = CResourceHandler::get();
  201. if(!rhandler->existsResource(rsave))
  202. return;
  203. size_t pos = lastSavePath.find_last_of("/\\");
  204. std::string name = (pos == std::string::npos)? lastSavePath : lastSavePath.substr(pos + 1);
  205. const auto & [data, length] = rhandler->load(rsave)->readAll();
  206. auto stream = saver.addFile(name + ".VSGM1");
  207. stream->write(data.get(), length);
  208. }
  209. catch(const std::exception& e)
  210. {
  211. // ignore errors here
  212. }
  213. }
  214. }
  215. // add generated listing as game-directory-structure.txt
  216. if (!listing.isEmpty())
  217. {
  218. QByteArray data = listing.toUtf8();
  219. auto stream = saver.addFile(std::string("data-directory-structure.txt"));
  220. stream->write(reinterpret_cast<const ui8 *>(data.constData()), data.size());
  221. }
  222. // add device information as device-info.txt
  223. {
  224. const QString deviceInfo = gatherDeviceInfo();
  225. if (!deviceInfo.isEmpty())
  226. {
  227. QByteArray dataDev = deviceInfo.toUtf8();
  228. auto streamDev = saver.addFile(std::string("device-info.txt"));
  229. streamDev->write(reinterpret_cast<const ui8 *>(dataDev.constData()), dataDev.size());
  230. }
  231. }
  232. }
  233. catch (const std::exception & e)
  234. {
  235. QMessageBox::critical(this, tr("Error"), tr("Failed to create archive: %1").arg(QString::fromUtf8(e.what())));
  236. return;
  237. }
  238. // On mobile platforms, send file via platform and remove temporary file afterwards.
  239. #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
  240. QMessageBox::information(this, tr("Send logs"), tr("The archive will be sent via another application. Share your logs e.g. over discord to developers."));
  241. Helper::sendFileToApp(outPath);
  242. #else
  243. // desktop: notify user and do not auto-send
  244. QMessageBox::information(this, tr("Success"), tr("Logs saved to %1, please send them to the developers").arg(outPath));
  245. #endif
  246. }