diff --git a/include/aoapplication.h b/include/aoapplication.h index b0a87b5..0c61d7b 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -37,6 +37,25 @@ class NetworkManager; class Lobby; 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 { Q_OBJECT @@ -130,23 +149,25 @@ public: // implementation in path_functions.cpp QString get_base_path(); - QString get_theme_path(QString p_file, QString p_theme=""); - QString get_character_path(QString p_char, QString p_file); - QString get_misc_path(QString p_misc, QString p_file); - QString get_sounds_path(QString p_file); - QString get_music_path(QString p_song); - QString get_background_path(QString p_file); - QString get_default_background_path(QString p_file); - QString 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=""); - QString get_asset_path(QStringList pathlist); - QString get_image_path(QStringList pathlist, bool static_image=false); - QString get_sfx_path(QStringList pathlist); + VPath get_theme_path(QString p_file, QString p_theme=""); + VPath get_character_path(QString p_char, QString p_file); + VPath get_misc_path(QString p_misc, QString p_file); + VPath get_sounds_path(QString p_file); + VPath get_music_path(QString p_song); + VPath get_background_path(QString p_file); + VPath get_default_background_path(QString p_file); + VPath get_evidence_path(QString p_file); + QVector 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(QVector pathlist); + QString get_image_path(QVector pathlist, bool static_image=false); + QString get_sfx_path(QVector 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_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_case_sensitive_path(QString p_file); + QString get_real_path(const VPath &vpath); + void invalidate_lookup_cache(); ////// Functions for reading and writing files ////// // Implementations file_functions.cpp @@ -281,6 +302,7 @@ public: QStringList get_call_words(); // returns all of the file's lines in a QStringList + QStringList get_list_file(VPath path); QStringList get_list_file(QString p_file); // Process a file and return its text as a QString @@ -304,6 +326,7 @@ public: QVector read_serverlist_txt(); // 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); // 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 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 - 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 // 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 QString read_char_ini(QString p_char, QString p_search_line, QString target_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. void set_char_ini(QString p_char, QString value, QString p_search_line, @@ -489,6 +515,9 @@ public: // Get if the theme is animated bool get_animated_theme(); + // Get a list of custom mount paths + QStringList get_mount_paths(); + // Currently defined subtheme QString subtheme; @@ -514,6 +543,7 @@ private: QVector server_list; QVector favorite_list; + QHash asset_lookup_cache; private slots: void ms_connect_finished(bool connected, bool will_retry); diff --git a/include/aolayer.h b/include/aolayer.h index 1984b77..90108af 100644 --- a/include/aolayer.h +++ b/include/aolayer.h @@ -9,6 +9,7 @@ #include class AOApplication; +class VPath; // "Brief" explanation of what the hell this is: // diff --git a/include/aooptionsdialog.h b/include/aooptionsdialog.h index 62ce2f7..3ea8ccc 100644 --- a/include/aooptionsdialog.h +++ b/include/aooptionsdialog.h @@ -180,6 +180,9 @@ private: QPushButton *ui_mount_remove; QPushButton *ui_mount_up; QPushButton *ui_mount_down; + QPushButton *ui_mount_clear_cache; + + bool asset_cache_dirty = false; bool needs_default_audiodev(); void update_values(); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index dd75b0a..355db03 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -19,6 +19,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) QObject::connect(net_manager, SIGNAL(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;}"); + asset_lookup_cache.reserve(2048); } AOApplication::~AOApplication() diff --git a/src/aobutton.cpp b/src/aobutton.cpp index 7f8c13f..fb9da7d 100644 --- a/src/aobutton.cpp +++ b/src/aobutton.cpp @@ -27,7 +27,7 @@ void AOButton::set_image(QString p_path, QString p_misc) else // 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); - if (!file_exists(p_image)) { + if (p_image.isEmpty()) { this->setIcon(QIcon()); this->setIconSize(this->size()); this->setStyleSheet(""); diff --git a/src/aoevidencebutton.cpp b/src/aoevidencebutton.cpp index aea903a..11fed59 100644 --- a/src/aoevidencebutton.cpp +++ b/src/aoevidencebutton.cpp @@ -32,7 +32,7 @@ AOEvidenceButton::AOEvidenceButton(QWidget *p_parent, AOApplication *p_ao_app, 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)) { this->setText(""); this->setStyleSheet( @@ -57,8 +57,10 @@ void AOEvidenceButton::set_image(QString p_image) void AOEvidenceButton::set_theme_image(QString p_image) { - QString theme_image_path = ao_app->get_theme_path(p_image); - QString default_image_path = ao_app->get_theme_path(p_image, ao_app->default_theme); + QString theme_image_path = ao_app->get_real_path( + 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; diff --git a/src/aoevidencedisplay.cpp b/src/aoevidencedisplay.cpp index ba1c170..c635a02 100644 --- a/src/aoevidencedisplay.cpp +++ b/src/aoevidencedisplay.cpp @@ -35,7 +35,8 @@ void AOEvidenceDisplay::show_evidence(QString p_evidence_image, 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); pos_size_type icon_dimensions = diff --git a/src/aoimage.cpp b/src/aoimage.cpp index e1bd8b8..fbf8c99 100644 --- a/src/aoimage.cpp +++ b/src/aoimage.cpp @@ -20,21 +20,16 @@ AOImage::AOImage(QWidget *parent, AOApplication *p_ao_app) : QLabel(parent) 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 - if (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); + p_image = ao_app->get_image(p_image, ao_app->current_theme, ao_app->get_subtheme(), + ao_app->default_theme, p_misc, "", "", !ao_app->get_animated_theme()); - if (!file_exists(p_path)) { - qDebug() << "Warning: Image" << p_path << "not found! Can't set!"; + if (!file_exists(p_image)) { + qDebug() << "Warning: Image" << p_image << "not found! Can't set!"; return false; } - path = p_path; + path = p_image; movie->stop(); movie->setFileName(path); if (ao_app->get_animated_theme() && movie->frameCount() > 1) { diff --git a/src/aolayer.cpp b/src/aolayer.cpp index f0d5779..27e7a65 100644 --- a/src/aolayer.cpp +++ b/src/aolayer.cpp @@ -137,7 +137,7 @@ void BackgroundLayer::load_image(QString p_filename) { play_once = 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 = ao_app->get_scaling(ao_app->read_design_ini("scaling", design_path)); stretch = ao_app->read_design_ini("stretch", design_path).startsWith("true"); diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 8a3142e..c4d8496 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -19,7 +19,7 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop, channel = channel % m_channelmax; if (channel < 0) // wtf? 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 | BASS_UNICODE | BASS_ASYNCFILE; diff --git a/src/aooptionsdialog.cpp b/src/aooptionsdialog.cpp index 6f1b348..a035a74 100644 --- a/src/aooptionsdialog.cpp +++ b/src/aooptionsdialog.cpp @@ -14,7 +14,7 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) // Setting up the basics. setWindowFlag(Qt::WindowCloseButtonHint); setWindowTitle(tr("Settings")); - resize(400, 408); + resize(450, 408); ui_settings_buttons = new QDialogButtonBox(this); @@ -907,7 +907,12 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) QFileDialog::ShowDirsOnly); if (dir.isEmpty()) 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); @@ -918,7 +923,9 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) auto selected = ui_mount_list->selectedItems(); if (selected.isEmpty()) 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, @@ -934,7 +941,13 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) auto selected = ui_mount_list->selectedItems(); if (selected.isEmpty()) 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); @@ -942,6 +955,49 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_mount_down->setMaximumWidth(40); ui_mount_down->setEnabled(false); 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(); @@ -1019,6 +1075,7 @@ void AOOptionsDialog::update_values() { .arg(ao_app->get_base_path())); defaultMount->setFlags(Qt::ItemFlag::NoItemFlags); ui_mount_list->addItem(defaultMount); + ui_mount_list->addItems(ao_app->get_mount_paths()); } void AOOptionsDialog::save_pressed() @@ -1091,9 +1148,17 @@ void AOOptionsDialog::save_pressed() configini->setValue("casing_can_host_cases", 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) ao_app->initBASS(); + if (asset_cache_dirty) + ao_app->invalidate_lookup_cache(); + callwordsini->close(); // We most probably pressed "Restore defaults" at some point. Since we're saving our settings, remove the temporary file. diff --git a/src/charselect.cpp b/src/charselect.cpp index 510d8c0..08df96f 100644 --- a/src/charselect.cpp +++ b/src/charselect.cpp @@ -157,8 +157,8 @@ void Courtroom::char_clicked(int n_char) { if (n_char != -1) { - QString char_ini_path = - ao_app->get_character_path(char_list.at(n_char).name, "char.ini"); + QString char_ini_path = ao_app->get_real_path( + ao_app->get_character_path(char_list.at(n_char).name, "char.ini")); qDebug() << "char_ini_path" << char_ini_path; diff --git a/src/courtroom.cpp b/src/courtroom.cpp index fe5f74d..4b9c72c 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -501,7 +501,7 @@ void Courtroom::set_widgets() { QString filename = "courtroom_design.ini"; // 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(); set_fonts(); @@ -1384,11 +1384,11 @@ void Courtroom::update_character(int p_cid) custom_obj_menu->setDefaultAction(action); objection_custom = ""; } - if (dir_exists( - ao_app->get_character_path(current_char, "custom_objections"))) { - ui_custom_objection->show(); - QDir directory( + QString custom_objection_dir = ao_app->get_real_path( 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" << "*.gif" << "*.apng" @@ -1523,7 +1523,7 @@ void Courtroom::list_music() treeItem->setText(0, i_song_listname); 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)) treeItem->setBackground(0, found_brush); @@ -4333,8 +4333,9 @@ void Courtroom::set_iniswap_dropdown() ui_iniswap_remove->hide(); return; } - QStringList iniswaps = 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"); + QStringList iniswaps = + 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.prepend(char_list.at(m_cid).name); if (iniswaps.size() <= 0) { @@ -4390,7 +4391,8 @@ void Courtroom::on_iniswap_context_menu_requested(const QPoint &pos) menu->setAttribute(Qt::WA_DeleteOnClose); 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, SLOT(on_iniswap_edit_requested())); 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() { - 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)) return; 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->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, SLOT(on_sfx_edit_requested())); else @@ -4491,10 +4494,10 @@ void Courtroom::on_sfx_context_menu_requested(const QPoint &pos) 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)) { p_path = ao_app->get_base_path() + "soundlist.ini"; - } + } QDesktopServices::openUrl(QUrl::fromLocalFile(p_path)); } @@ -4527,12 +4530,11 @@ void Courtroom::set_effects_dropdown() // ICON-MAKING HELL QString p_effect = ao_app->read_char_ini(current_char, "effects", "Options"); - QString custom_path = - ao_app->get_base_path() + "misc/" + p_effect + "/icons/"; - QString theme_path = ao_app->get_theme_path("effects/icons/"); - QString default_path = ao_app->get_theme_path("effects/icons/", "default"); + VPath custom_path("misc/" + p_effect + "/icons/"); + VPath theme_path = ao_app->get_theme_path("effects/icons/"); + VPath default_path = ao_app->get_theme_path("effects/icons/", "default"); 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); if (!file_exists(iconpath)) { 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() { - 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)) { - 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)) { return; } @@ -4578,7 +4580,7 @@ void Courtroom::on_effects_edit_requested() void Courtroom::on_character_effects_edit_requested() { 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)) return; diff --git a/src/file_functions.cpp b/src/file_functions.cpp index e64a46b..37cdc32 100644 --- a/src/file_functions.cpp +++ b/src/file_functions.cpp @@ -2,6 +2,9 @@ bool file_exists(QString file_path) { + if (file_path.isEmpty()) + return false; + QFileInfo check_file(file_path); return check_file.exists() && check_file.isFile(); diff --git a/src/path_functions.cpp b/src/path_functions.cpp index 9700529..b1a5e48 100644 --- a/src/path_functions.cpp +++ b/src/path_functions.cpp @@ -39,71 +39,58 @@ QString AOApplication::get_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 == "") p_theme = current_theme; - QString path = get_base_path() + "themes/" + p_theme + "/" + p_file; - return get_case_sensitive_path(path); + return VPath("themes/" + p_theme + "/" + p_file); } -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 get_case_sensitive_path(path); + return VPath("characters/" + p_char + "/" + p_file); } -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; -#ifndef CASE_SENSITIVE_FILESYSTEM - return path; -#else - return get_case_sensitive_path(path); -#endif + return VPath("misc/" + p_misc + "/" + p_file); } -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 get_case_sensitive_path(path); + return VPath("sounds/general/" + p_file); } -QString AOApplication::get_music_path(QString p_song) +VPath AOApplication::get_music_path(QString p_song) { if (p_song.startsWith("http")) { - return p_song; // url + return VPath(p_song); // url } - QString path = get_base_path() + "sounds/music/" + p_song; - return get_case_sensitive_path(path); + return VPath("sounds/music/" + p_song); } -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) { - return get_case_sensitive_path(path); + return VPath("background/" + w_courtroom->get_current_background() + "/" + 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 get_case_sensitive_path(path); + return VPath("background/default/" + p_file); } -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 get_case_sensitive_path(path); + return VPath("evidence/" + p_file); } -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 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; - pathlist += p_element; // The path by itself + QVector pathlist; + pathlist += VPath(p_element); // The path by itself if (p_character != "") pathlist += get_character_path(p_character, p_element); // Character folder 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; } -QString AOApplication::get_asset_path(QStringList pathlist) +QString AOApplication::get_asset_path(QVector pathlist) { - QString path; - for (QString p : pathlist) { - p = get_case_sensitive_path(p); - if (file_exists(p)) { - path = p; - break; + for (const VPath &p : pathlist) { + QString path = get_real_path(p); + if (!path.isEmpty()) { + return path; } } - return path; + return QString(); } -QString AOApplication::get_image_path(QStringList pathlist, bool static_image) +QString AOApplication::get_image_path(QVector pathlist, bool static_image) { - QString path; - for (QString p : pathlist) { - p = get_case_sensitive_path(get_image_suffix(p, static_image)); - if (file_exists(p)) { - 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; + for (const VPath &p : pathlist) { + QString path = get_image_suffix(p, static_image); + if (!path.isEmpty()) { + return path; } } - return path; + return QString(); } + +QString AOApplication::get_sfx_path(QVector 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 path; // 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)) { - p = get_case_sensitive_path(p); - if (file_exists(p)) { - QSettings settings(p, QSettings::IniFormat); + for (const VPath &p : get_asset_paths(p_config, p_theme, p_subtheme, p_default_theme, p_misc)) { + path = get_real_path(p); + if (!path.isEmpty()) { + QSettings settings(path, QSettings::IniFormat); QVariant value = settings.value(p_identifier); if (value.type() == QVariant::StringList) { // 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)); } -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) { - QStringList pathlist = get_asset_paths(p_sfx, current_theme, get_subtheme(), default_theme, p_misc, p_character); + QVector 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 return get_sfx_path(pathlist); } QString AOApplication::get_case_sensitive_path(QString p_file) { - // no path traversal above base folder - if (!(p_file.startsWith(get_base_path()))) - return get_base_path() + p_file; - - #ifdef CASE_SENSITIVE_FILESYSTEM +#ifdef CASE_SENSITIVE_FILESYSTEM // first, check to see if it's actually there (also serves as base case for // recursion) QFileInfo file(p_file); @@ -238,3 +217,35 @@ QString AOApplication::get_case_sensitive_path(QString p_file) return p_file; #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(); +} diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index f6a5d6c..c92287f 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -124,6 +124,11 @@ QStringList AOApplication::get_call_words() 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 return_value; @@ -275,6 +280,12 @@ QVector AOApplication::read_serverlist_txt() 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 p_design_path) { @@ -440,12 +451,14 @@ QString AOApplication::get_chat_markup(QString p_identifier, QString p_chat) return value.toLatin1(); // Backwards ass compatibility - QStringList backwards_paths{get_theme_path("misc/" + p_chat + "/config.ini"), - get_base_path() + "misc/" + p_chat + - "/config.ini", - get_base_path() + "misc/default/config.ini", - get_theme_path("misc/default/config.ini")}; - for (const QString &p : backwards_paths) { + QVector backwards_paths { + get_theme_path("misc/" + p_chat + "/config.ini"), + VPath("misc/" + p_chat + "/config.ini"), + VPath("misc/default/config.ini"), + get_theme_path("misc/default/config.ini") + }; + + for (const VPath &p : backwards_paths) { QString value = read_design_ini(p_identifier, p); if (!value.isEmpty()) { return value.toLatin1(); @@ -482,36 +495,32 @@ QString AOApplication::get_court_sfx(QString p_identifier, QString p_misc) return ""; } -QString AOApplication::get_sfx_suffix(QString sound_to_check) -{ - if (file_exists(sound_to_check)) - return sound_to_check; - if (file_exists(sound_to_check + ".opus")) - return sound_to_check + ".opus"; - if (file_exists(sound_to_check + ".ogg")) - return sound_to_check + ".ogg"; - 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_suffix(VPath path_to_check, QStringList suffixes) { + for (const QString &suffix : suffixes) { + QString path = get_real_path(VPath(path_to_check.toQString() + suffix)); + if (!path.isEmpty()) + return path; + } + + return QString(); } -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 path_to_check; + return get_suffix(sound_to_check, { "", ".opus", ".ogg", ".mp3", ".wav" }); +} + +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. // However, that might not be performant. if (!static_image) { - if (file_exists(path_to_check + ".webp")) - 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"; + suffixes.append({ ".webp", ".apng", ".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 @@ -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 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); settings.beginGroup(target_tag); QString value = settings.value(p_search_line).value(); @@ -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, 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); settings.beginGroup(target_tag); 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 -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; - QSettings settings(p_path, QSettings::IniFormat); + QSettings settings(get_real_path(p_path), QSettings::IniFormat); if (!target_tag.isEmpty()) settings.beginGroup(target_tag); QStringList keys = settings.allKeys(); @@ -1084,3 +1093,8 @@ bool AOApplication::get_animated_theme() configini->value("animated_theme", "true").value(); return result.startsWith("true"); } + +QStringList AOApplication::get_mount_paths() +{ + return configini->value("mount_paths").value(); +}