Finish mounting feature

To pull this one off, a new class called VPath was created that denotes
"virtual" paths that can exist anywhere among the list of mount points.
It is functionally identical to QString, except that implicit conversion
between QString and VPath is not allowed. This makes it easy to spot
errors in path resolution at compile time, since get_real_path must be
called to resolve a VPath into an absolute path that can be passed into
a Qt function as a QString.

Other functions, such as the get_*_suffix functions, also return an
absolute path QString for convenience.

As for the rest of the functions that return a VPath, you will need to
call get_real_path yourself.

Finally, a path resolution cache was added to try to avoid blowing
up what is already a massive lookup cost for assets. The cache
is invalidated when the mount path list is changed.

Currently, this cache isn't bounded. Might need to write an LRU cache
if issues arise.
This commit is contained in:
oldmud0 2021-06-05 14:58:40 -05:00
parent a023657348
commit d27501313c
16 changed files with 296 additions and 168 deletions

View File

@ -37,6 +37,25 @@ class NetworkManager;
class Lobby; class Lobby;
class Courtroom; class Courtroom;
class VPath : QString {
using QString::QString;
public:
explicit VPath(const QString &str) : QString(str) {}
inline QString const &toQString() const { return *this; }
inline bool operator==(const VPath &str) const {
return this->toQString() == str.toQString();
}
inline VPath operator+(const VPath &str) const {
return VPath(this->toQString() + str.toQString());
}
};
inline uint qHash(const VPath &key, uint seed)
{
return qHash(key.toQString(), seed);
}
class AOApplication : public QApplication { class AOApplication : public QApplication {
Q_OBJECT Q_OBJECT
@ -130,23 +149,25 @@ public:
// implementation in path_functions.cpp // implementation in path_functions.cpp
QString get_base_path(); QString get_base_path();
QString get_theme_path(QString p_file, QString p_theme=""); VPath get_theme_path(QString p_file, QString p_theme="");
QString get_character_path(QString p_char, QString p_file); VPath get_character_path(QString p_char, QString p_file);
QString get_misc_path(QString p_misc, QString p_file); VPath get_misc_path(QString p_misc, QString p_file);
QString get_sounds_path(QString p_file); VPath get_sounds_path(QString p_file);
QString get_music_path(QString p_song); VPath get_music_path(QString p_song);
QString get_background_path(QString p_file); VPath get_background_path(QString p_file);
QString get_default_background_path(QString p_file); VPath get_default_background_path(QString p_file);
QString get_evidence_path(QString p_file); VPath get_evidence_path(QString p_file);
QStringList get_asset_paths(QString p_element, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="", QString p_character="", QString p_placeholder=""); QVector<VPath> get_asset_paths(QString p_element, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="", QString p_character="", QString p_placeholder="");
QString get_asset_path(QStringList pathlist); QString get_asset_path(QVector<VPath> pathlist);
QString get_image_path(QStringList pathlist, bool static_image=false); QString get_image_path(QVector<VPath> pathlist, bool static_image=false);
QString get_sfx_path(QStringList pathlist); QString get_sfx_path(QVector<VPath> pathlist);
QString get_config_value(QString p_identifier, QString p_config, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc=""); QString get_config_value(QString p_identifier, QString p_config, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="");
QString get_asset(QString p_element, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="", QString p_character="", QString p_placeholder=""); QString get_asset(QString p_element, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="", QString p_character="", QString p_placeholder="");
QString get_image(QString p_element, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="", QString p_character="", QString p_placeholder=""); QString get_image(QString p_element, QString p_theme="", QString p_subtheme="", QString p_default_theme="", QString p_misc="", QString p_character="", QString p_placeholder="", bool static_image=false);
QString get_sfx(QString p_sfx, QString p_misc="", QString p_character=""); QString get_sfx(QString p_sfx, QString p_misc="", QString p_character="");
QString get_case_sensitive_path(QString p_file); QString get_case_sensitive_path(QString p_file);
QString get_real_path(const VPath &vpath);
void invalidate_lookup_cache();
////// Functions for reading and writing files ////// ////// Functions for reading and writing files //////
// Implementations file_functions.cpp // Implementations file_functions.cpp
@ -281,6 +302,7 @@ public:
QStringList get_call_words(); QStringList get_call_words();
// returns all of the file's lines in a QStringList // returns all of the file's lines in a QStringList
QStringList get_list_file(VPath path);
QStringList get_list_file(QString p_file); QStringList get_list_file(QString p_file);
// Process a file and return its text as a QString // Process a file and return its text as a QString
@ -304,6 +326,7 @@ public:
QVector<server_type> read_serverlist_txt(); QVector<server_type> read_serverlist_txt();
// Returns the value of p_identifier in the design.ini file in p_design_path // Returns the value of p_identifier in the design.ini file in p_design_path
QString read_design_ini(QString p_identifier, VPath p_design_path);
QString read_design_ini(QString p_identifier, QString p_design_path); QString read_design_ini(QString p_identifier, QString p_design_path);
// Returns the coordinates of widget with p_identifier from p_file // Returns the coordinates of widget with p_identifier from p_file
@ -332,19 +355,22 @@ public:
// Returns the sfx with p_identifier from courtroom_sounds.ini in the current theme path // Returns the sfx with p_identifier from courtroom_sounds.ini in the current theme path
QString get_court_sfx(QString p_identifier, QString p_misc=""); QString get_court_sfx(QString p_identifier, QString p_misc="");
// Find the correct suffix for a given file
QString get_suffix(VPath file_to_check, QStringList suffixes);
// Figure out if we can opus this or if we should fall back to wav // Figure out if we can opus this or if we should fall back to wav
QString get_sfx_suffix(QString sound_to_check); QString get_sfx_suffix(VPath sound_to_check);
// Can we use APNG for this? If not, WEBP? If not, GIF? If not, fall back to // Can we use APNG for this? If not, WEBP? If not, GIF? If not, fall back to
// PNG. // PNG.
QString get_image_suffix(QString path_to_check, bool static_image=false); QString get_image_suffix(VPath path_to_check, bool static_image=false);
// Returns the value of p_search_line within target_tag and terminator_tag // Returns the value of p_search_line within target_tag and terminator_tag
QString read_char_ini(QString p_char, QString p_search_line, QString read_char_ini(QString p_char, QString p_search_line,
QString target_tag); QString target_tag);
// Returns a QStringList of all key=value definitions on a given tag. // Returns a QStringList of all key=value definitions on a given tag.
QStringList read_ini_tags(QString p_file, QString target_tag = ""); QStringList read_ini_tags(VPath p_file, QString target_tag = "");
// Sets the char.ini p_search_line key under tag target_tag to value. // Sets the char.ini p_search_line key under tag target_tag to value.
void set_char_ini(QString p_char, QString value, QString p_search_line, void set_char_ini(QString p_char, QString value, QString p_search_line,
@ -489,6 +515,9 @@ public:
// Get if the theme is animated // Get if the theme is animated
bool get_animated_theme(); bool get_animated_theme();
// Get a list of custom mount paths
QStringList get_mount_paths();
// Currently defined subtheme // Currently defined subtheme
QString subtheme; QString subtheme;
@ -514,6 +543,7 @@ private:
QVector<server_type> server_list; QVector<server_type> server_list;
QVector<server_type> favorite_list; QVector<server_type> favorite_list;
QHash<VPath, QString> asset_lookup_cache;
private slots: private slots:
void ms_connect_finished(bool connected, bool will_retry); void ms_connect_finished(bool connected, bool will_retry);

View File

@ -9,6 +9,7 @@
#include <QBitmap> #include <QBitmap>
class AOApplication; class AOApplication;
class VPath;
// "Brief" explanation of what the hell this is: // "Brief" explanation of what the hell this is:
// //

View File

@ -180,6 +180,9 @@ private:
QPushButton *ui_mount_remove; QPushButton *ui_mount_remove;
QPushButton *ui_mount_up; QPushButton *ui_mount_up;
QPushButton *ui_mount_down; QPushButton *ui_mount_down;
QPushButton *ui_mount_clear_cache;
bool asset_cache_dirty = false;
bool needs_default_audiodev(); bool needs_default_audiodev();
void update_values(); void update_values();

View File

@ -19,6 +19,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv)
QObject::connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), QObject::connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)),
SLOT(ms_connect_finished(bool, bool))); SLOT(ms_connect_finished(bool, bool)));
// qApp->setStyleSheet("QFrame {background-color:transparent;} QAbstractItemView {background-color: transparent; color: black;}; QLineEdit {background-color:transparent;}"); // qApp->setStyleSheet("QFrame {background-color:transparent;} QAbstractItemView {background-color: transparent; color: black;}; QLineEdit {background-color:transparent;}");
asset_lookup_cache.reserve(2048);
} }
AOApplication::~AOApplication() AOApplication::~AOApplication()

View File

@ -27,7 +27,7 @@ void AOButton::set_image(QString p_path, QString p_misc)
else else
// Grab a static variant of the image // Grab a static variant of the image
p_image = ao_app->get_image_path(ao_app->get_asset_paths(p_path, ao_app->current_theme, ao_app->get_subtheme(), ao_app->default_theme, p_misc), true); p_image = ao_app->get_image_path(ao_app->get_asset_paths(p_path, ao_app->current_theme, ao_app->get_subtheme(), ao_app->default_theme, p_misc), true);
if (!file_exists(p_image)) { if (p_image.isEmpty()) {
this->setIcon(QIcon()); this->setIcon(QIcon());
this->setIconSize(this->size()); this->setIconSize(this->size());
this->setStyleSheet(""); this->setStyleSheet("");

View File

@ -32,7 +32,7 @@ AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app,
void AOEvidenceButton::set_image(QString p_image) void AOEvidenceButton::set_image(QString p_image)
{ {
QString image_path = ao_app->get_evidence_path(p_image); QString image_path = ao_app->get_real_path(ao_app->get_evidence_path(p_image));
if (file_exists(p_image)) { if (file_exists(p_image)) {
this->setText(""); this->setText("");
this->setStyleSheet( this->setStyleSheet(
@ -57,8 +57,10 @@ void AOEvidenceButton::set_image(QString p_image)
void AOEvidenceButton::set_theme_image(QString p_image) void AOEvidenceButton::set_theme_image(QString p_image)
{ {
QString theme_image_path = ao_app->get_theme_path(p_image); QString theme_image_path = ao_app->get_real_path(
QString default_image_path = ao_app->get_theme_path(p_image, ao_app->default_theme); ao_app->get_theme_path(p_image));
QString default_image_path = ao_app->get_real_path(
ao_app->get_theme_path(p_image, ao_app->default_theme));
QString final_image_path; QString final_image_path;

View File

@ -35,7 +35,8 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image,
gif_name = "evidence_appear_right"; gif_name = "evidence_appear_right";
} }
QString f_evidence_path = ao_app->get_evidence_path(p_evidence_image); QString f_evidence_path = ao_app->get_real_path(
ao_app->get_evidence_path(p_evidence_image));
QPixmap f_pixmap(f_evidence_path); QPixmap f_pixmap(f_evidence_path);
pos_size_type icon_dimensions = pos_size_type icon_dimensions =

View File

@ -20,21 +20,16 @@ AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent)
AOImage::~AOImage() {} AOImage::~AOImage() {}
bool AOImage::set_image(QString p_path, QString p_misc) bool AOImage::set_image(QString p_image, QString p_misc)
{ {
// Check if the user wants animated themes p_image = ao_app->get_image(p_image, ao_app->current_theme, ao_app->get_subtheme(),
if (ao_app->get_animated_theme()) ao_app->default_theme, p_misc, "", "", !ao_app->get_animated_theme());
// We want an animated image
p_path = ao_app->get_image(p_path, ao_app->current_theme, ao_app->get_subtheme(), ao_app->default_theme, p_misc);
else
// Grab a static variant of the image
p_path = ao_app->get_image_path(ao_app->get_asset_paths(p_path, ao_app->current_theme, ao_app->get_subtheme(), ao_app->default_theme, p_misc), true);
if (!file_exists(p_path)) { if (!file_exists(p_image)) {
qDebug() << "Warning: Image" << p_path << "not found! Can't set!"; qDebug() << "Warning: Image" << p_image << "not found! Can't set!";
return false; return false;
} }
path = p_path; path = p_image;
movie->stop(); movie->stop();
movie->setFileName(path); movie->setFileName(path);
if (ao_app->get_animated_theme() && movie->frameCount() > 1) { if (ao_app->get_animated_theme() && movie->frameCount() > 1) {

View File

@ -137,7 +137,7 @@ void BackgroundLayer::load_image(QString p_filename)
{ {
play_once = false; play_once = false;
cull_image = false; cull_image = false;
QString design_path = ao_app->get_background_path("design.ini"); VPath design_path = ao_app->get_background_path("design.ini");
transform_mode = transform_mode =
ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path)); ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path));
stretch = ao_app->read_design_ini("stretch", design_path).startsWith("true"); stretch = ao_app->read_design_ini("stretch", design_path).startsWith("true");

View File

@ -19,7 +19,7 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop,
channel = channel % m_channelmax; channel = channel % m_channelmax;
if (channel < 0) // wtf? if (channel < 0) // wtf?
return; return;
QString f_path = ao_app->get_music_path(p_song); QString f_path = ao_app->get_real_path(ao_app->get_music_path(p_song));
unsigned int flags = BASS_STREAM_PRESCAN | BASS_STREAM_AUTOFREE | unsigned int flags = BASS_STREAM_PRESCAN | BASS_STREAM_AUTOFREE |
BASS_UNICODE | BASS_ASYNCFILE; BASS_UNICODE | BASS_ASYNCFILE;

View File

@ -14,7 +14,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
// Setting up the basics. // Setting up the basics.
setWindowFlag(Qt::WindowCloseButtonHint); setWindowFlag(Qt::WindowCloseButtonHint);
setWindowTitle(tr("Settings")); setWindowTitle(tr("Settings"));
resize(400, 408); resize(450, 408);
ui_settings_buttons = new QDialogButtonBox(this); ui_settings_buttons = new QDialogButtonBox(this);
@ -907,7 +907,12 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
QFileDialog::ShowDirsOnly); QFileDialog::ShowDirsOnly);
if (dir.isEmpty()) if (dir.isEmpty())
return; return;
ui_mount_list->addItem(dir); QListWidgetItem *dir_item = new QListWidgetItem(dir);
ui_mount_list->addItem(dir_item);
ui_mount_list->setCurrentItem(dir_item);
// quick hack to update buttons
emit ui_mount_list->itemSelectionChanged();
}); });
ui_mount_remove = new QPushButton(tr("Remove"), ui_assets_tab); ui_mount_remove = new QPushButton(tr("Remove"), ui_assets_tab);
@ -918,7 +923,9 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
auto selected = ui_mount_list->selectedItems(); auto selected = ui_mount_list->selectedItems();
if (selected.isEmpty()) if (selected.isEmpty())
return; return;
ui_mount_list->removeItemWidget(selected[0]); delete selected[0];
emit ui_mount_list->itemSelectionChanged();
asset_cache_dirty = true;
}); });
auto *mount_buttons_spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, auto *mount_buttons_spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding,
@ -934,7 +941,13 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
auto selected = ui_mount_list->selectedItems(); auto selected = ui_mount_list->selectedItems();
if (selected.isEmpty()) if (selected.isEmpty())
return; return;
ui_mount_list->setEditTriggers() auto *item = selected[0];
int row = ui_mount_list->row(item);
ui_mount_list->takeItem(row);
int new_row = qMax(1, row - 1);
ui_mount_list->insertItem(new_row, item);
ui_mount_list->setCurrentRow(new_row);
asset_cache_dirty = true;
}); });
ui_mount_down = new QPushButton(tr(""), ui_assets_tab); ui_mount_down = new QPushButton(tr(""), ui_assets_tab);
@ -942,6 +955,49 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app)
ui_mount_down->setMaximumWidth(40); ui_mount_down->setMaximumWidth(40);
ui_mount_down->setEnabled(false); ui_mount_down->setEnabled(false);
ui_mount_buttons_layout->addWidget(ui_mount_down, 0, 4, 1, 1); ui_mount_buttons_layout->addWidget(ui_mount_down, 0, 4, 1, 1);
connect(ui_mount_down, &QPushButton::clicked, this, [=] {
auto selected = ui_mount_list->selectedItems();
if (selected.isEmpty())
return;
auto *item = selected[0];
int row = ui_mount_list->row(item);
ui_mount_list->takeItem(row);
int new_row = qMin(ui_mount_list->count() + 1, row + 1);
ui_mount_list->insertItem(new_row, item);
ui_mount_list->setCurrentRow(new_row);
asset_cache_dirty = true;
});
auto *mount_buttons_spacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding,
QSizePolicy::Minimum);
ui_mount_buttons_layout->addItem(mount_buttons_spacer_2, 0, 5, 1, 1);
ui_mount_clear_cache = new QPushButton(tr("Clear Cache"), ui_assets_tab);
ui_mount_clear_cache->setToolTip(tr("Clears the lookup cache for assets. "
"Use this when you have added an asset that takes precedence over another "
"existing asset."));
ui_mount_buttons_layout->addWidget(ui_mount_clear_cache, 0, 6, 1, 1);
connect(ui_mount_clear_cache, &QPushButton::clicked, this, [=] {
asset_cache_dirty = true;
ui_mount_clear_cache->setEnabled(false);
});
connect(ui_mount_list, &QListWidget::itemSelectionChanged, this, [=] {
auto selected_items = ui_mount_list->selectedItems();
bool row_selected = !ui_mount_list->selectedItems().isEmpty();
ui_mount_remove->setEnabled(row_selected);
ui_mount_up->setEnabled(row_selected);
ui_mount_down->setEnabled(row_selected);
if (!row_selected)
return;
int row = ui_mount_list->row(selected_items[0]);
if (row <= 1)
ui_mount_up->setEnabled(false);
if (row >= ui_mount_list->count() - 1)
ui_mount_down->setEnabled(false);
});
update_values(); update_values();
@ -1019,6 +1075,7 @@ void AOOptionsDialog::update_values() {
.arg(ao_app->get_base_path())); .arg(ao_app->get_base_path()));
defaultMount->setFlags(Qt::ItemFlag::NoItemFlags); defaultMount->setFlags(Qt::ItemFlag::NoItemFlags);
ui_mount_list->addItem(defaultMount); ui_mount_list->addItem(defaultMount);
ui_mount_list->addItems(ao_app->get_mount_paths());
} }
void AOOptionsDialog::save_pressed() void AOOptionsDialog::save_pressed()
@ -1091,9 +1148,17 @@ void AOOptionsDialog::save_pressed()
configini->setValue("casing_can_host_cases", configini->setValue("casing_can_host_cases",
ui_casing_cm_cases_textbox->text()); ui_casing_cm_cases_textbox->text());
QStringList mountPaths;
for (int i = 1; i < ui_mount_list->count(); i++)
mountPaths.append(ui_mount_list->item(i)->text());
configini->setValue("mount_paths", mountPaths);
if (audioChanged) if (audioChanged)
ao_app->initBASS(); ao_app->initBASS();
if (asset_cache_dirty)
ao_app->invalidate_lookup_cache();
callwordsini->close(); callwordsini->close();
// We most probably pressed "Restore defaults" at some point. Since we're saving our settings, remove the temporary file. // We most probably pressed "Restore defaults" at some point. Since we're saving our settings, remove the temporary file.

View File

@ -157,8 +157,8 @@ void Courtroom::char_clicked(int n_char)
{ {
if (n_char != -1) if (n_char != -1)
{ {
QString char_ini_path = QString char_ini_path = ao_app->get_real_path(
ao_app->get_character_path(char_list.at(n_char).name, "char.ini"); ao_app->get_character_path(char_list.at(n_char).name, "char.ini"));
qDebug() << "char_ini_path" << char_ini_path; qDebug() << "char_ini_path" << char_ini_path;

View File

@ -501,7 +501,7 @@ void Courtroom::set_widgets()
{ {
QString filename = "courtroom_design.ini"; QString filename = "courtroom_design.ini";
// Update the default theme from the courtroom_design.ini, if it's not defined it will be 'default'. // Update the default theme from the courtroom_design.ini, if it's not defined it will be 'default'.
QSettings settings(ao_app->get_theme_path(filename, ao_app->current_theme), QSettings::IniFormat); QSettings settings(ao_app->get_real_path(ao_app->get_theme_path(filename, ao_app->current_theme)), QSettings::IniFormat);
ao_app->default_theme = settings.value("default_theme", "default").toString(); ao_app->default_theme = settings.value("default_theme", "default").toString();
set_fonts(); set_fonts();
@ -1384,11 +1384,11 @@ void Courtroom::update_character(int p_cid)
custom_obj_menu->setDefaultAction(action); custom_obj_menu->setDefaultAction(action);
objection_custom = ""; objection_custom = "";
} }
if (dir_exists( QString custom_objection_dir = ao_app->get_real_path(
ao_app->get_character_path(current_char, "custom_objections"))) {
ui_custom_objection->show();
QDir directory(
ao_app->get_character_path(current_char, "custom_objections")); ao_app->get_character_path(current_char, "custom_objections"));
if (dir_exists(custom_objection_dir)) {
ui_custom_objection->show();
QDir directory(custom_objection_dir);
QStringList custom_obj = directory.entryList(QStringList() << "*.png" QStringList custom_obj = directory.entryList(QStringList() << "*.png"
<< "*.gif" << "*.gif"
<< "*.apng" << "*.apng"
@ -1523,7 +1523,7 @@ void Courtroom::list_music()
treeItem->setText(0, i_song_listname); treeItem->setText(0, i_song_listname);
treeItem->setText(1, i_song); treeItem->setText(1, i_song);
QString song_path = ao_app->get_music_path(i_song); QString song_path = ao_app->get_real_path(ao_app->get_music_path(i_song));
if (file_exists(song_path)) if (file_exists(song_path))
treeItem->setBackground(0, found_brush); treeItem->setBackground(0, found_brush);
@ -4333,8 +4333,9 @@ void Courtroom::set_iniswap_dropdown()
ui_iniswap_remove->hide(); ui_iniswap_remove->hide();
return; return;
} }
QStringList iniswaps = ao_app->get_list_file( QStringList iniswaps =
ao_app->get_character_path(char_list.at(m_cid).name, "iniswaps.ini")) + ao_app->get_list_file(ao_app->get_base_path() + "iniswaps.ini"); ao_app->get_list_file(ao_app->get_character_path(char_list.at(m_cid).name, "iniswaps.ini")) +
ao_app->get_list_file(ao_app->get_base_path() + "iniswaps.ini");
iniswaps.removeDuplicates(); iniswaps.removeDuplicates();
iniswaps.prepend(char_list.at(m_cid).name); iniswaps.prepend(char_list.at(m_cid).name);
if (iniswaps.size() <= 0) { if (iniswaps.size() <= 0) {
@ -4390,7 +4391,8 @@ void Courtroom::on_iniswap_context_menu_requested(const QPoint &pos)
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addSeparator(); menu->addSeparator();
if (file_exists(ao_app->get_character_path(current_char, "char.ini"))) if (file_exists(ao_app->get_real_path(
ao_app->get_character_path(current_char, "char.ini"))))
menu->addAction(QString("Edit " + current_char + "/char.ini"), this, menu->addAction(QString("Edit " + current_char + "/char.ini"), this,
SLOT(on_iniswap_edit_requested())); SLOT(on_iniswap_edit_requested()));
if (ui_iniswap_dropdown->itemText(ui_iniswap_dropdown->currentIndex()) != if (ui_iniswap_dropdown->itemText(ui_iniswap_dropdown->currentIndex()) !=
@ -4401,7 +4403,7 @@ void Courtroom::on_iniswap_context_menu_requested(const QPoint &pos)
} }
void Courtroom::on_iniswap_edit_requested() void Courtroom::on_iniswap_edit_requested()
{ {
QString p_path = ao_app->get_character_path(current_char, "char.ini"); QString p_path = ao_app->get_real_path(ao_app->get_character_path(current_char, "char.ini"));
if (!file_exists(p_path)) if (!file_exists(p_path))
return; return;
QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); QDesktopServices::openUrl(QUrl::fromLocalFile(p_path));
@ -4478,7 +4480,8 @@ void Courtroom::on_sfx_context_menu_requested(const QPoint &pos)
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addSeparator(); menu->addSeparator();
if (file_exists(ao_app->get_character_path(current_char, "soundlist.ini"))) if (file_exists(ao_app->get_real_path(
ao_app->get_character_path(current_char, "soundlist.ini"))))
menu->addAction(QString("Edit " + current_char + "/soundlist.ini"), this, menu->addAction(QString("Edit " + current_char + "/soundlist.ini"), this,
SLOT(on_sfx_edit_requested())); SLOT(on_sfx_edit_requested()));
else else
@ -4491,10 +4494,10 @@ void Courtroom::on_sfx_context_menu_requested(const QPoint &pos)
void Courtroom::on_sfx_edit_requested() void Courtroom::on_sfx_edit_requested()
{ {
QString p_path = ao_app->get_character_path(current_char, "soundlist.ini"); QString p_path = ao_app->get_real_path(ao_app->get_character_path(current_char, "soundlist.ini"));
if (!file_exists(p_path)) { if (!file_exists(p_path)) {
p_path = ao_app->get_base_path() + "soundlist.ini"; p_path = ao_app->get_base_path() + "soundlist.ini";
} }
QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); QDesktopServices::openUrl(QUrl::fromLocalFile(p_path));
} }
@ -4527,12 +4530,11 @@ void Courtroom::set_effects_dropdown()
// ICON-MAKING HELL // ICON-MAKING HELL
QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options"); QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options");
QString custom_path = VPath custom_path("misc/" + p_effect + "/icons/");
ao_app->get_base_path() + "misc/" + p_effect + "/icons/"; VPath theme_path = ao_app->get_theme_path("effects/icons/");
QString theme_path = ao_app->get_theme_path("effects/icons/"); VPath default_path = ao_app->get_theme_path("effects/icons/", "default");
QString default_path = ao_app->get_theme_path("effects/icons/", "default");
for (int i = 0; i < ui_effects_dropdown->count(); ++i) { for (int i = 0; i < ui_effects_dropdown->count(); ++i) {
QString entry = ui_effects_dropdown->itemText(i); VPath entry = VPath(ui_effects_dropdown->itemText(i));
QString iconpath = ao_app->get_image_suffix(custom_path + entry); QString iconpath = ao_app->get_image_suffix(custom_path + entry);
if (!file_exists(iconpath)) { if (!file_exists(iconpath)) {
iconpath = ao_app->get_image_suffix(theme_path + entry); iconpath = ao_app->get_image_suffix(theme_path + entry);
@ -4566,9 +4568,9 @@ void Courtroom::on_effects_context_menu_requested(const QPoint &pos)
} }
void Courtroom::on_effects_edit_requested() void Courtroom::on_effects_edit_requested()
{ {
QString p_path = ao_app->get_theme_path("effects/"); QString p_path = ao_app->get_real_path(ao_app->get_theme_path("effects/"));
if (!dir_exists(p_path)) { if (!dir_exists(p_path)) {
p_path = ao_app->get_theme_path("effects/", "default"); p_path = ao_app->get_real_path(ao_app->get_theme_path("effects/", "default"));
if (!dir_exists(p_path)) { if (!dir_exists(p_path)) {
return; return;
} }
@ -4578,7 +4580,7 @@ void Courtroom::on_effects_edit_requested()
void Courtroom::on_character_effects_edit_requested() void Courtroom::on_character_effects_edit_requested()
{ {
QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options"); QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options");
QString p_path = ao_app->get_base_path() + "misc/" + p_effect + "/"; QString p_path = ao_app->get_real_path(VPath("misc/" + p_effect + "/"));
if (!dir_exists(p_path)) if (!dir_exists(p_path))
return; return;

View File

@ -2,6 +2,9 @@
bool file_exists(QString file_path) bool file_exists(QString file_path)
{ {
if (file_path.isEmpty())
return false;
QFileInfo check_file(file_path); QFileInfo check_file(file_path);
return check_file.exists() && check_file.isFile(); return check_file.exists() && check_file.isFile();

View File

@ -39,71 +39,58 @@ QString AOApplication::get_base_path()
return base_path; return base_path;
} }
QString AOApplication::get_theme_path(QString p_file, QString p_theme) VPath AOApplication::get_theme_path(QString p_file, QString p_theme)
{ {
if (p_theme == "") if (p_theme == "")
p_theme = current_theme; p_theme = current_theme;
QString path = get_base_path() + "themes/" + p_theme + "/" + p_file; return VPath("themes/" + p_theme + "/" + p_file);
return get_case_sensitive_path(path);
} }
QString AOApplication::get_character_path(QString p_char, QString p_file) VPath AOApplication::get_character_path(QString p_char, QString p_file)
{ {
QString path = get_base_path() + "characters/" + p_char + "/" + p_file; return VPath("characters/" + p_char + "/" + p_file);
return get_case_sensitive_path(path);
} }
QString AOApplication::get_misc_path(QString p_misc, QString p_file) VPath AOApplication::get_misc_path(QString p_misc, QString p_file)
{ {
QString path = get_base_path() + "misc/" + p_misc + "/" + p_file; return VPath("misc/" + p_misc + "/" + p_file);
#ifndef CASE_SENSITIVE_FILESYSTEM
return path;
#else
return get_case_sensitive_path(path);
#endif
} }
QString AOApplication::get_sounds_path(QString p_file) VPath AOApplication::get_sounds_path(QString p_file)
{ {
QString path = get_base_path() + "sounds/general/" + p_file; return VPath("sounds/general/" + p_file);
return get_case_sensitive_path(path);
} }
QString AOApplication::get_music_path(QString p_song) VPath AOApplication::get_music_path(QString p_song)
{ {
if (p_song.startsWith("http")) { if (p_song.startsWith("http")) {
return p_song; // url return VPath(p_song); // url
} }
QString path = get_base_path() + "sounds/music/" + p_song; return VPath("sounds/music/" + p_song);
return get_case_sensitive_path(path);
} }
QString AOApplication::get_background_path(QString p_file) VPath AOApplication::get_background_path(QString p_file)
{ {
QString path = get_base_path() + "background/" +
w_courtroom->get_current_background() + "/" + p_file;
if (courtroom_constructed) { if (courtroom_constructed) {
return get_case_sensitive_path(path); return VPath("background/" + w_courtroom->get_current_background() + "/" + p_file);
} }
return get_default_background_path(p_file); return get_default_background_path(p_file);
} }
QString AOApplication::get_default_background_path(QString p_file) VPath AOApplication::get_default_background_path(QString p_file)
{ {
QString path = get_base_path() + "background/default/" + p_file; return VPath("background/default/" + p_file);
return get_case_sensitive_path(path);
} }
QString AOApplication::get_evidence_path(QString p_file) VPath AOApplication::get_evidence_path(QString p_file)
{ {
QString path = get_base_path() + "evidence/" + p_file; return VPath("evidence/" + p_file);
return get_case_sensitive_path(path);
} }
QStringList AOApplication::get_asset_paths(QString p_element, QString p_theme, QString p_subtheme, QString p_default_theme, QString p_misc, QString p_character, QString p_placeholder) QVector<VPath> AOApplication::get_asset_paths(QString p_element, QString p_theme, QString p_subtheme, QString p_default_theme, QString p_misc, QString p_character, QString p_placeholder)
{ {
QStringList pathlist; QVector<VPath> pathlist;
pathlist += p_element; // The path by itself pathlist += VPath(p_element); // The path by itself
if (p_character != "") if (p_character != "")
pathlist += get_character_path(p_character, p_element); // Character folder pathlist += get_character_path(p_character, p_element); // Character folder
if (p_misc != "" && p_theme != "" && p_subtheme != "") if (p_misc != "" && p_theme != "" && p_subtheme != "")
@ -125,52 +112,47 @@ QStringList AOApplication::get_asset_paths(QString p_element, QString p_theme, Q
return pathlist; return pathlist;
} }
QString AOApplication::get_asset_path(QStringList pathlist) QString AOApplication::get_asset_path(QVector<VPath> pathlist)
{ {
QString path; for (const VPath &p : pathlist) {
for (QString p : pathlist) { QString path = get_real_path(p);
p = get_case_sensitive_path(p); if (!path.isEmpty()) {
if (file_exists(p)) { return path;
path = p;
break;
} }
} }
return path; return QString();
} }
QString AOApplication::get_image_path(QStringList pathlist, bool static_image) QString AOApplication::get_image_path(QVector<VPath> pathlist, bool static_image)
{ {
QString path; for (const VPath &p : pathlist) {
for (QString p : pathlist) { QString path = get_image_suffix(p, static_image);
p = get_case_sensitive_path(get_image_suffix(p, static_image)); if (!path.isEmpty()) {
if (file_exists(p)) { return path;
path = p;
break;
}
}
return path;
}
QString AOApplication::get_sfx_path(QStringList pathlist)
{
QString path;
for (QString p : pathlist) {
p = get_case_sensitive_path(get_sfx_suffix(p));
if (file_exists(p)) {
path = p;
break;
} }
} }
return path; return QString();
} }
QString AOApplication::get_sfx_path(QVector<VPath> pathlist)
{
for (const VPath &p : pathlist) {
QString path = get_sfx_suffix(p);
if (!path.isEmpty()) {
return path;
}
}
return QString();
}
QString AOApplication::get_config_value(QString p_identifier, QString p_config, QString p_theme, QString p_subtheme, QString p_default_theme, QString p_misc) QString AOApplication::get_config_value(QString p_identifier, QString p_config, QString p_theme, QString p_subtheme, QString p_default_theme, QString p_misc)
{ {
QString path; QString path;
// qDebug() << "got request for" << p_identifier << "in" << p_config; // qDebug() << "got request for" << p_identifier << "in" << p_config;
for (QString p : get_asset_paths(p_config, p_theme, p_subtheme, p_default_theme, p_misc)) { for (const VPath &p : get_asset_paths(p_config, p_theme, p_subtheme, p_default_theme, p_misc)) {
p = get_case_sensitive_path(p); path = get_real_path(p);
if (file_exists(p)) { if (!path.isEmpty()) {
QSettings settings(p, QSettings::IniFormat); QSettings settings(path, QSettings::IniFormat);
QVariant value = settings.value(p_identifier); QVariant value = settings.value(p_identifier);
if (value.type() == QVariant::StringList) { if (value.type() == QVariant::StringList) {
// qDebug() << "got" << p << "is a string list, returning" << value.toStringList().join(","); // qDebug() << "got" << p << "is a string list, returning" << value.toStringList().join(",");
@ -190,25 +172,22 @@ QString AOApplication::get_asset(QString p_element, QString p_theme, QString p_s
return get_asset_path(get_asset_paths(p_element, p_theme, p_subtheme, p_default_theme, p_misc, p_character, p_placeholder)); return get_asset_path(get_asset_paths(p_element, p_theme, p_subtheme, p_default_theme, p_misc, p_character, p_placeholder));
} }
QString AOApplication::get_image(QString p_element, QString p_theme, QString p_subtheme, QString p_default_theme, QString p_misc, QString p_character, QString p_placeholder) QString AOApplication::get_image(QString p_element, QString p_theme, QString p_subtheme, QString p_default_theme, QString p_misc, QString p_character, QString p_placeholder,
bool static_image)
{ {
return get_image_path(get_asset_paths(p_element, p_theme, p_subtheme, p_default_theme, p_misc, p_character, p_placeholder)); return get_image_path(get_asset_paths(p_element, p_theme, p_subtheme, p_default_theme, p_misc, p_character, p_placeholder), static_image);
} }
QString AOApplication::get_sfx(QString p_sfx, QString p_misc, QString p_character) QString AOApplication::get_sfx(QString p_sfx, QString p_misc, QString p_character)
{ {
QStringList pathlist = get_asset_paths(p_sfx, current_theme, get_subtheme(), default_theme, p_misc, p_character); QVector<VPath> pathlist = get_asset_paths(p_sfx, current_theme, get_subtheme(), default_theme, p_misc, p_character);
pathlist += get_sounds_path(p_sfx); // Sounds folder path pathlist += get_sounds_path(p_sfx); // Sounds folder path
return get_sfx_path(pathlist); return get_sfx_path(pathlist);
} }
QString AOApplication::get_case_sensitive_path(QString p_file) QString AOApplication::get_case_sensitive_path(QString p_file)
{ {
// no path traversal above base folder #ifdef CASE_SENSITIVE_FILESYSTEM
if (!(p_file.startsWith(get_base_path())))
return get_base_path() + p_file;
#ifdef CASE_SENSITIVE_FILESYSTEM
// first, check to see if it's actually there (also serves as base case for // first, check to see if it's actually there (also serves as base case for
// recursion) // recursion)
QFileInfo file(p_file); QFileInfo file(p_file);
@ -238,3 +217,35 @@ QString AOApplication::get_case_sensitive_path(QString p_file)
return p_file; return p_file;
#endif #endif
} }
QString AOApplication::get_real_path(const VPath &vpath) {
// Try cache first
QString phys_path = asset_lookup_cache.value(vpath);
if (!phys_path.isEmpty() && exists(phys_path)) {
return phys_path;
}
// Cache miss; try all known mount paths
QStringList bases = get_mount_paths();
bases.push_front(get_base_path());
for (const QString &base : bases) {
QDir baseDir(base);
const QString path = baseDir.absoluteFilePath(vpath.toQString());
if (!path.startsWith(baseDir.absolutePath())) {
qWarning() << "invalid path" << path << "(path is outside vfs)";
break;
}
if (exists(get_case_sensitive_path(path))) {
asset_lookup_cache.insert(vpath, path);
return path;
}
}
// File or directory not found
return QString();
}
void AOApplication::invalidate_lookup_cache() {
asset_lookup_cache.clear();
}

View File

@ -124,6 +124,11 @@ QStringList AOApplication::get_call_words()
return get_list_file(get_base_path() + "callwords.ini"); return get_list_file(get_base_path() + "callwords.ini");
} }
QStringList AOApplication::get_list_file(VPath path)
{
return get_list_file(get_real_path(path));
}
QStringList AOApplication::get_list_file(QString p_file) QStringList AOApplication::get_list_file(QString p_file)
{ {
QStringList return_value; QStringList return_value;
@ -275,6 +280,12 @@ QVector<server_type> AOApplication::read_serverlist_txt()
return f_server_list; return f_server_list;
} }
QString AOApplication::read_design_ini(QString p_identifier,
VPath p_design_path)
{
return read_design_ini(p_identifier, get_real_path(p_design_path));
}
QString AOApplication::read_design_ini(QString p_identifier, QString AOApplication::read_design_ini(QString p_identifier,
QString p_design_path) QString p_design_path)
{ {
@ -440,12 +451,14 @@ QString AOApplication::get_chat_markup(QString p_identifier, QString p_chat)
return value.toLatin1(); return value.toLatin1();
// Backwards ass compatibility // Backwards ass compatibility
QStringList backwards_paths{get_theme_path("misc/" + p_chat + "/config.ini"), QVector<VPath> backwards_paths {
get_base_path() + "misc/" + p_chat + get_theme_path("misc/" + p_chat + "/config.ini"),
"/config.ini", VPath("misc/" + p_chat + "/config.ini"),
get_base_path() + "misc/default/config.ini", VPath("misc/default/config.ini"),
get_theme_path("misc/default/config.ini")}; get_theme_path("misc/default/config.ini")
for (const QString &p : backwards_paths) { };
for (const VPath &p : backwards_paths) {
QString value = read_design_ini(p_identifier, p); QString value = read_design_ini(p_identifier, p);
if (!value.isEmpty()) { if (!value.isEmpty()) {
return value.toLatin1(); return value.toLatin1();
@ -482,36 +495,32 @@ QString AOApplication::get_court_sfx(QString p_identifier, QString p_misc)
return ""; return "";
} }
QString AOApplication::get_sfx_suffix(QString sound_to_check) QString AOApplication::get_suffix(VPath path_to_check, QStringList suffixes) {
{ for (const QString &suffix : suffixes) {
if (file_exists(sound_to_check)) QString path = get_real_path(VPath(path_to_check.toQString() + suffix));
return sound_to_check; if (!path.isEmpty())
if (file_exists(sound_to_check + ".opus")) return path;
return sound_to_check + ".opus"; }
if (file_exists(sound_to_check + ".ogg"))
return sound_to_check + ".ogg"; return QString();
if (file_exists(sound_to_check + ".mp3"))
return sound_to_check + ".mp3";
if (file_exists(sound_to_check + ".mp4"))
return sound_to_check + ".mp4";
return sound_to_check + ".wav";
} }
QString AOApplication::get_image_suffix(QString path_to_check, bool static_image) QString AOApplication::get_sfx_suffix(VPath sound_to_check)
{ {
if (file_exists(path_to_check)) return get_suffix(sound_to_check, { "", ".opus", ".ogg", ".mp3", ".wav" });
return path_to_check; }
QString AOApplication::get_image_suffix(VPath path_to_check, bool static_image)
{
QStringList suffixes { "" };
// A better method would to actually use AOImageReader and see if these images have more than 1 frame. // A better method would to actually use AOImageReader and see if these images have more than 1 frame.
// However, that might not be performant. // However, that might not be performant.
if (!static_image) { if (!static_image) {
if (file_exists(path_to_check + ".webp")) suffixes.append({ ".webp", ".apng", ".gif" });
return path_to_check + ".webp";
if (file_exists(path_to_check + ".apng"))
return path_to_check + ".apng";
if (file_exists(path_to_check + ".gif"))
return path_to_check + ".gif";
} }
return path_to_check + ".png"; suffixes.append(".png");
return get_suffix(path_to_check, suffixes);
} }
// returns whatever is to the right of "search_line =" within target_tag and // returns whatever is to the right of "search_line =" within target_tag and
@ -520,7 +529,7 @@ QString AOApplication::get_image_suffix(QString path_to_check, bool static_image
QString AOApplication::read_char_ini(QString p_char, QString p_search_line, QString AOApplication::read_char_ini(QString p_char, QString p_search_line,
QString target_tag) QString target_tag)
{ {
QSettings settings(get_character_path(p_char, "char.ini"), QSettings settings(get_real_path(get_character_path(p_char, "char.ini")),
QSettings::IniFormat); QSettings::IniFormat);
settings.beginGroup(target_tag); settings.beginGroup(target_tag);
QString value = settings.value(p_search_line).value<QString>(); QString value = settings.value(p_search_line).value<QString>();
@ -531,7 +540,7 @@ QString AOApplication::read_char_ini(QString p_char, QString p_search_line,
void AOApplication::set_char_ini(QString p_char, QString value, void AOApplication::set_char_ini(QString p_char, QString value,
QString p_search_line, QString target_tag) QString p_search_line, QString target_tag)
{ {
QSettings settings(get_character_path(p_char, "char.ini"), QSettings settings(get_real_path(get_character_path(p_char, "char.ini")),
QSettings::IniFormat); QSettings::IniFormat);
settings.beginGroup(target_tag); settings.beginGroup(target_tag);
settings.setValue(p_search_line, value); settings.setValue(p_search_line, value);
@ -539,10 +548,10 @@ void AOApplication::set_char_ini(QString p_char, QString value,
} }
// returns all the values of target_tag // returns all the values of target_tag
QStringList AOApplication::read_ini_tags(QString p_path, QString target_tag) QStringList AOApplication::read_ini_tags(VPath p_path, QString target_tag)
{ {
QStringList r_values; QStringList r_values;
QSettings settings(p_path, QSettings::IniFormat); QSettings settings(get_real_path(p_path), QSettings::IniFormat);
if (!target_tag.isEmpty()) if (!target_tag.isEmpty())
settings.beginGroup(target_tag); settings.beginGroup(target_tag);
QStringList keys = settings.allKeys(); QStringList keys = settings.allKeys();
@ -1084,3 +1093,8 @@ bool AOApplication::get_animated_theme()
configini->value("animated_theme", "true").value<QString>(); configini->value("animated_theme", "true").value<QString>();
return result.startsWith("true"); return result.startsWith("true");
} }
QStringList AOApplication::get_mount_paths()
{
return configini->value("mount_paths").value<QStringList>();
}