Skip to content
Permalink
Browse files
Merge pull request #5981 from spycrab/qt_filesystem
Qt/GameList: Implement "Filesystem" tab
  • Loading branch information
leoetlino committed Sep 7, 2017
2 parents e52d8ef + 8b346b7 commit 7971a4d
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 3 deletions.
@@ -4,6 +4,301 @@

#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,15 +5,46 @@
#pragma once

#include <QDialog>
#include <memory>

#include "DiscIO/Volume.h"
#include "DolphinQt2/GameList/GameFile.h"

class QStandardItem;
class QStandardItemModel;
class QTreeView;

namespace DiscIO
{
class FileInfo;
struct Partition;
};

class FilesystemWidget final : public QWidget
{
Q_OBJECT
public:
explicit FilesystemWidget(const GameFile& game);

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;
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);
InfoWidget* info = new InfoWidget(game);
FilesystemWidget* filesystem = new FilesystemWidget(game);
tab_widget->addTab(info, tr("Info"));
tab_widget->addTab(filesystem, tr("Filesystem"));

if (DiscIO::IsDisc(game.GetPlatformID()))
{
FilesystemWidget* filesystem = new FilesystemWidget(game);
tab_widget->addTab(filesystem, tr("Filesystem"));
}

layout->addWidget(tab_widget);

QDialogButtonBox* ok_box = new QDialogButtonBox(QDialogButtonBox::Ok);

0 comments on commit 7971a4d

Please sign in to comment.