Merge pull request #5981 from spycrab/qt_filesystem
Qt/GameList: Implement "Filesystem" tab
This commit is contained in:
commit
7971a4d66c
|
@ -4,6 +4,301 @@
|
||||||
|
|
||||||
#include "DolphinQt2/Config/FilesystemWidget.h"
|
#include "DolphinQt2/Config/FilesystemWidget.h"
|
||||||
|
|
||||||
FilesystemWidget::FilesystemWidget(const GameFile& game) : m_game(game)
|
#include <QApplication>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QProgressDialog>
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QStyleFactory>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
|
||||||
|
#include "DiscIO/DiscExtractor.h"
|
||||||
|
#include "DiscIO/Enums.h"
|
||||||
|
#include "DiscIO/Filesystem.h"
|
||||||
|
#include "DolphinQt2/Resources.h"
|
||||||
|
|
||||||
|
constexpr int ENTRY_PARTITION = Qt::UserRole;
|
||||||
|
constexpr int ENTRY_NAME = Qt::UserRole + 1;
|
||||||
|
constexpr int ENTRY_TYPE = Qt::UserRole + 2;
|
||||||
|
|
||||||
|
enum class EntryType
|
||||||
{
|
{
|
||||||
|
Disc = -2,
|
||||||
|
Partition = -1,
|
||||||
|
File = 0,
|
||||||
|
Dir = 1
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(EntryType);
|
||||||
|
|
||||||
|
FilesystemWidget::FilesystemWidget(const GameFile& game)
|
||||||
|
: m_game(game), m_volume(DiscIO::CreateVolumeFromFilename(game.GetFilePath().toStdString()))
|
||||||
|
{
|
||||||
|
CreateWidgets();
|
||||||
|
ConnectWidgets();
|
||||||
|
PopulateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::CreateWidgets()
|
||||||
|
{
|
||||||
|
auto* layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
m_tree_model = new QStandardItemModel(0, 1);
|
||||||
|
m_tree_view = new QTreeView(this);
|
||||||
|
m_tree_view->setModel(m_tree_model);
|
||||||
|
m_tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
m_tree_view->header()->hide();
|
||||||
|
|
||||||
|
// Windows: Set style to (old) windows, which draws branch lines
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (QApplication::style()->objectName() == QStringLiteral("windowsvista"))
|
||||||
|
m_tree_view->setStyle(QStyleFactory::create(QStringLiteral("windows")));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
layout->addWidget(m_tree_view);
|
||||||
|
|
||||||
|
setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::ConnectWidgets()
|
||||||
|
{
|
||||||
|
connect(m_tree_view, &QTreeView::customContextMenuRequested, this,
|
||||||
|
&FilesystemWidget::ShowContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::PopulateView()
|
||||||
|
{
|
||||||
|
auto* disc = new QStandardItem(tr("Disc"));
|
||||||
|
disc->setEditable(false);
|
||||||
|
disc->setIcon(Resources::GetScaledIcon("isoproperties_disc"));
|
||||||
|
disc->setData(QVariant::fromValue(EntryType::Disc), ENTRY_TYPE);
|
||||||
|
m_tree_model->appendRow(disc);
|
||||||
|
m_tree_view->expand(disc->index());
|
||||||
|
|
||||||
|
const auto& partitions = m_volume->GetPartitions();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < partitions.size(); i++)
|
||||||
|
{
|
||||||
|
std::unique_ptr<DiscIO::FileSystem> file_system(
|
||||||
|
DiscIO::CreateFileSystem(m_volume.get(), partitions[i]));
|
||||||
|
|
||||||
|
auto* item = new QStandardItem(tr("Partition %1").arg(i));
|
||||||
|
item->setEditable(false);
|
||||||
|
|
||||||
|
item->setIcon(Resources::GetScaledIcon("isoproperties_disc"));
|
||||||
|
item->setData(static_cast<qlonglong>(i), ENTRY_PARTITION);
|
||||||
|
item->setData(QVariant::fromValue(EntryType::Partition), ENTRY_TYPE);
|
||||||
|
|
||||||
|
PopulateDirectory(static_cast<int>(i), item, file_system->GetRoot());
|
||||||
|
|
||||||
|
disc->appendRow(item);
|
||||||
|
|
||||||
|
if (m_volume->GetGamePartition() == partitions[i])
|
||||||
|
m_tree_view->expand(item->index());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partitions.empty())
|
||||||
|
{
|
||||||
|
PopulateDirectory(-1, disc,
|
||||||
|
DiscIO::CreateFileSystem(m_volume.get(), DiscIO::PARTITION_NONE)->GetRoot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::PopulateDirectory(int partition_id, QStandardItem* root,
|
||||||
|
const DiscIO::FileInfo& directory)
|
||||||
|
{
|
||||||
|
for (const auto& info : directory)
|
||||||
|
{
|
||||||
|
auto* item = new QStandardItem(QString::fromStdString(info.GetName()));
|
||||||
|
item->setEditable(false);
|
||||||
|
item->setIcon(Resources::GetScaledIcon(info.IsDirectory() ? "isoproperties_folder" :
|
||||||
|
"isoproperties_file"));
|
||||||
|
|
||||||
|
if (info.IsDirectory())
|
||||||
|
PopulateDirectory(partition_id, item, info);
|
||||||
|
|
||||||
|
item->setData(partition_id, ENTRY_PARTITION);
|
||||||
|
item->setData(QString::fromStdString(info.GetPath()), ENTRY_NAME);
|
||||||
|
item->setData(QVariant::fromValue(info.IsDirectory() ? EntryType::Dir : EntryType::File),
|
||||||
|
ENTRY_TYPE);
|
||||||
|
root->appendRow(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString SelectFolder()
|
||||||
|
{
|
||||||
|
return QFileDialog::getExistingDirectory(nullptr, QObject::tr("Choose the folder to extract to"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::ShowContextMenu(const QPoint&)
|
||||||
|
{
|
||||||
|
auto* selection = m_tree_view->selectionModel();
|
||||||
|
if (!selection->hasSelection())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto* item = m_tree_model->itemFromIndex(selection->selectedIndexes()[0]);
|
||||||
|
|
||||||
|
QMenu* menu = new QMenu(this);
|
||||||
|
|
||||||
|
DiscIO::Partition partition = GetPartitionFromID(item->data(ENTRY_PARTITION).toInt());
|
||||||
|
QString path = item->data(ENTRY_NAME).toString();
|
||||||
|
|
||||||
|
EntryType type = item->data(ENTRY_TYPE).value<EntryType>();
|
||||||
|
|
||||||
|
if ((type == EntryType::Disc && m_volume->GetPartitions().empty()) ||
|
||||||
|
type == EntryType::Partition)
|
||||||
|
{
|
||||||
|
menu->addAction(tr("Extract System Data..."), this, [this, partition] {
|
||||||
|
auto folder = SelectFolder();
|
||||||
|
|
||||||
|
if (!folder.isEmpty())
|
||||||
|
ExtractSystemData(partition, folder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case EntryType::Disc:
|
||||||
|
menu->addAction(tr("Extract Entire Disc..."), this, [this, partition, path] {
|
||||||
|
auto folder = SelectFolder();
|
||||||
|
|
||||||
|
if (folder.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_volume->GetPartitions().empty())
|
||||||
|
{
|
||||||
|
ExtractPartition(DiscIO::PARTITION_NONE, folder);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto& p : m_volume->GetPartitions())
|
||||||
|
ExtractPartition(p, folder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case EntryType::Partition:
|
||||||
|
menu->addAction(tr("Extract Entire Partition..."), this, [this, partition] {
|
||||||
|
auto folder = SelectFolder();
|
||||||
|
if (!folder.isEmpty())
|
||||||
|
ExtractPartition(partition, folder);
|
||||||
|
});
|
||||||
|
menu->addSeparator();
|
||||||
|
menu->addAction(tr("Check Partition Integrity"), this,
|
||||||
|
[this, partition] { CheckIntegrity(partition); });
|
||||||
|
break;
|
||||||
|
case EntryType::Dir:
|
||||||
|
menu->addAction(tr("Extract Files..."), this, [this, partition, path] {
|
||||||
|
auto folder = SelectFolder();
|
||||||
|
|
||||||
|
if (!folder.isEmpty())
|
||||||
|
ExtractDirectory(partition, path, folder);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case EntryType::File:
|
||||||
|
menu->addAction(tr("Extract File..."), this, [this, partition, path] {
|
||||||
|
auto dest = QFileDialog::getSaveFileName(this, tr("Save File to"));
|
||||||
|
|
||||||
|
if (!dest.isEmpty())
|
||||||
|
ExtractFile(partition, path, dest);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->exec(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscIO::Partition FilesystemWidget::GetPartitionFromID(int id)
|
||||||
|
{
|
||||||
|
return id == -1 ? DiscIO::PARTITION_NONE : m_volume->GetPartitions()[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::ExtractPartition(const DiscIO::Partition& partition, const QString& out)
|
||||||
|
{
|
||||||
|
ExtractDirectory(partition, QStringLiteral(""), out);
|
||||||
|
ExtractSystemData(partition, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::ExtractSystemData(const DiscIO::Partition& partition, const QString& out)
|
||||||
|
{
|
||||||
|
bool success = DiscIO::ExportSystemData(*m_volume, partition, out.toStdString());
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
QMessageBox::information(nullptr, tr("Success"), tr("Successfully extracted system data."));
|
||||||
|
else
|
||||||
|
QMessageBox::critical(nullptr, tr("Error"), tr("Failed to extract system data."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::ExtractDirectory(const DiscIO::Partition& partition, const QString path,
|
||||||
|
const QString& out)
|
||||||
|
{
|
||||||
|
std::unique_ptr<DiscIO::FileSystem> filesystem(
|
||||||
|
DiscIO::CreateFileSystem(m_volume.get(), partition));
|
||||||
|
|
||||||
|
std::unique_ptr<DiscIO::FileInfo> info = filesystem->FindFileInfo(path.toStdString());
|
||||||
|
u32 size = info->GetTotalChildren();
|
||||||
|
|
||||||
|
QProgressDialog* dialog = new QProgressDialog(this);
|
||||||
|
dialog->setMinimum(0);
|
||||||
|
dialog->setMaximum(size);
|
||||||
|
dialog->show();
|
||||||
|
|
||||||
|
bool all = path.isEmpty();
|
||||||
|
|
||||||
|
DiscIO::ExportDirectory(
|
||||||
|
*m_volume, filesystem->GetPartition(), *info, true, path.toStdString(), out.toStdString(),
|
||||||
|
[all, dialog](const std::string& current) {
|
||||||
|
dialog->setLabelText(
|
||||||
|
(all ? QObject::tr("Extracting All Files...") : QObject::tr("Extracting Directory..."))
|
||||||
|
.append(QStringLiteral(" %1").arg(QString::fromStdString(current))));
|
||||||
|
dialog->setValue(dialog->value() + 1);
|
||||||
|
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
return dialog->wasCanceled();
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::ExtractFile(const DiscIO::Partition& partition, const QString& path,
|
||||||
|
const QString& out)
|
||||||
|
{
|
||||||
|
std::unique_ptr<DiscIO::FileSystem> filesystem(
|
||||||
|
DiscIO::CreateFileSystem(m_volume.get(), partition));
|
||||||
|
bool success = DiscIO::ExportFile(
|
||||||
|
*m_volume, partition, filesystem->FindFileInfo(path.toStdString()).get(), out.toStdString());
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
QMessageBox::information(nullptr, tr("Success"), tr("Successfully extracted file."));
|
||||||
|
else
|
||||||
|
QMessageBox::critical(nullptr, tr("Error"), tr("Failed to extract file."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilesystemWidget::CheckIntegrity(const DiscIO::Partition& partition)
|
||||||
|
{
|
||||||
|
QProgressDialog* dialog = new QProgressDialog(this);
|
||||||
|
std::future<bool> is_valid = std::async(
|
||||||
|
std::launch::async, [this, partition] { return m_volume->CheckIntegrity(partition); });
|
||||||
|
|
||||||
|
dialog->setLabelText(tr("Verifying integrity of partition..."));
|
||||||
|
dialog->setMinimum(0);
|
||||||
|
dialog->setMaximum(0);
|
||||||
|
dialog->show();
|
||||||
|
|
||||||
|
while (is_valid.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
dialog->close();
|
||||||
|
|
||||||
|
if (is_valid.get())
|
||||||
|
QMessageBox::information(nullptr, tr("Success"),
|
||||||
|
tr("Integrity check completed. No errors have been found"));
|
||||||
|
else
|
||||||
|
QMessageBox::critical(nullptr, tr("Error"),
|
||||||
|
tr("Integrity check for partition failed. The disc image is most "
|
||||||
|
"likely corrupted or has been patched incorrectly."));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,21 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "DiscIO/Volume.h"
|
||||||
#include "DolphinQt2/GameList/GameFile.h"
|
#include "DolphinQt2/GameList/GameFile.h"
|
||||||
|
|
||||||
|
class QStandardItem;
|
||||||
|
class QStandardItemModel;
|
||||||
|
class QTreeView;
|
||||||
|
|
||||||
|
namespace DiscIO
|
||||||
|
{
|
||||||
|
class FileInfo;
|
||||||
|
struct Partition;
|
||||||
|
};
|
||||||
|
|
||||||
class FilesystemWidget final : public QWidget
|
class FilesystemWidget final : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -15,5 +27,24 @@ public:
|
||||||
explicit FilesystemWidget(const GameFile& game);
|
explicit FilesystemWidget(const GameFile& game);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void CreateWidgets();
|
||||||
|
void ConnectWidgets();
|
||||||
|
void PopulateView();
|
||||||
|
void PopulateDirectory(int partition_id, QStandardItem* root, const DiscIO::FileInfo& directory);
|
||||||
|
|
||||||
|
void ShowContextMenu(const QPoint&);
|
||||||
|
|
||||||
|
void ExtractPartition(const DiscIO::Partition& partition, const QString& out);
|
||||||
|
void ExtractDirectory(const DiscIO::Partition& partition, const QString path, const QString& out);
|
||||||
|
void ExtractFile(const DiscIO::Partition& pratition, const QString& path, const QString& out);
|
||||||
|
void ExtractSystemData(const DiscIO::Partition& partition, const QString& out);
|
||||||
|
void CheckIntegrity(const DiscIO::Partition& partition);
|
||||||
|
|
||||||
|
DiscIO::Partition GetPartitionFromID(int id);
|
||||||
|
|
||||||
|
QStandardItemModel* m_tree_model;
|
||||||
|
QTreeView* m_tree_view;
|
||||||
|
|
||||||
GameFile m_game;
|
GameFile m_game;
|
||||||
|
std::unique_ptr<DiscIO::Volume> m_volume;
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,9 +18,14 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
|
||||||
|
|
||||||
QTabWidget* tab_widget = new QTabWidget(this);
|
QTabWidget* tab_widget = new QTabWidget(this);
|
||||||
InfoWidget* info = new InfoWidget(game);
|
InfoWidget* info = new InfoWidget(game);
|
||||||
FilesystemWidget* filesystem = new FilesystemWidget(game);
|
|
||||||
tab_widget->addTab(info, tr("Info"));
|
tab_widget->addTab(info, tr("Info"));
|
||||||
|
|
||||||
|
if (DiscIO::IsDisc(game.GetPlatformID()))
|
||||||
|
{
|
||||||
|
FilesystemWidget* filesystem = new FilesystemWidget(game);
|
||||||
tab_widget->addTab(filesystem, tr("Filesystem"));
|
tab_widget->addTab(filesystem, tr("Filesystem"));
|
||||||
|
}
|
||||||
|
|
||||||
layout->addWidget(tab_widget);
|
layout->addWidget(tab_widget);
|
||||||
|
|
||||||
QDialogButtonBox* ok_box = new QDialogButtonBox(QDialogButtonBox::Ok);
|
QDialogButtonBox* ok_box = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||||
|
|
Loading…
Reference in New Issue