diff --git a/USERGUIDE.md b/USERGUIDE.md index de413fe8b..f2474d348 100644 --- a/USERGUIDE.md +++ b/USERGUIDE.md @@ -1,8 +1,26 @@ EmulationStation Desktop Edition - User Guide ============================================= +**Note:** This document is intended as a quick start guide, for more in-depth information and details on how to compile EmulationStation and perform more advanced configuration, please refer to the [INSTALL.md](INSTALL.md) document. + + ### Getting started +Getting started with EmulationStation is very easy, just make sure to install the software properly, either manually as built from source code or using one of the supplied packages. On Windows you'll use the installer instead of a package. + +Upon first startup, ES will create its home directory, by default the location is ~/.emulationstation. + +On Unix this defaults to /home//.emulationstation/ and on Windows it defaults to C:\Users\\.emulationstation\ + +A settings file, `es_settings.cfg` will be generated with all the default settings, and a `es_systems.cfg` file will also be copied from the program resource folder, that contains the game ROM and emulator settings. + +There's a log file in the home directory as well named `es_log.txt`, please refer to this in case on any errors as it should provide information on what went wrong. + +Upon startup with the default settings, ES is set to the gamelist view style `AUTOMATIC`. In this mode the application will look for any game media files (videos and images) and set the view style accordingly. If at least one image is found for any game for a certain system, the view style `DETAILED` will be shown and if at least one video file is found, the view style `VIDEO` will be selected. The gamelist view styles are described in more detail later in this document. + + +### Main screen + ### Main menu diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index 4aa88db67..46eb93643 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -292,6 +292,8 @@ void GuiMetaDataEd::fetchDone(const ScraperSearchResult& result) MetaDataList* metadata = nullptr; metadata = new MetaDataList(*mMetaData); + mMediaFilesUpdated = result.savedNewImages; + // Check if any values were manually changed before starting the scraping. // If so, it's these values we should compare against when scraping, not // the values previously saved for the game. @@ -356,8 +358,7 @@ void GuiMetaDataEd::close() std::function closeFunc; closeFunc = [this] { delete this; }; - if (dirty) - { + if (dirty) { // Changes were made, ask if the user wants to save them. mWindow->pushGui(new GuiMsgBox(mWindow, getHelpStyle(), "SAVE CHANGES?", @@ -366,6 +367,9 @@ void GuiMetaDataEd::close() )); } else { + // Always save if the media files have been changed (i.e. newly scraped images). + if (mMediaFilesUpdated) + save(); closeFunc(); } } diff --git a/es-app/src/guis/GuiMetaDataEd.h b/es-app/src/guis/GuiMetaDataEd.h index 0afd8066b..00d1a25b7 100644 --- a/es-app/src/guis/GuiMetaDataEd.h +++ b/es-app/src/guis/GuiMetaDataEd.h @@ -62,6 +62,7 @@ private: std::function mDeleteFunc; bool mMetadataUpdated; + bool mMediaFilesUpdated; }; #endif // ES_APP_GUIS_GUI_META_DATA_ED_H diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp index 39f2473d4..a0e26e23c 100644 --- a/es-app/src/scrapers/Scraper.cpp +++ b/es-app/src/scrapers/Scraper.cpp @@ -11,10 +11,11 @@ #include "utils/StringUtil.h" #include "FileData.h" #include "GamesDBJSONScraper.h" -#include "ScreenScraper.h" #include "Log.h" +#include "ScreenScraper.h" #include "Settings.h" #include "SystemData.h" + #include #include @@ -45,14 +46,15 @@ std::unique_ptr startMediaURLsFetch(const std::string& game ScraperSearchParams params; // Check if the scraper in the settings still exists as a registered scraping source. - if (scraper_request_funcs.find(name) == scraper_request_funcs.end()) + if (scraper_request_funcs.find(name) == scraper_request_funcs.end()) { LOG(LogWarning) << "Warning - Configured scraper (" << name << ") unavailable, scraping aborted."; - else + } + else { // Specifically use the TheGamesDB function as this type of request // will never occur for ScreenScraper. - thegamesdb_generate_json_scraper_requests(gameIDs, handle->mRequestQueue, - handle->mResults); + thegamesdb_generate_json_scraper_requests(gameIDs, handle->mRequestQueue, handle->mResults); + } return handle; } @@ -167,6 +169,8 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, std::vector scrapeFiles; + mResult.savedNewImages = false; + if (Settings::getInstance()->getBool("Scrape3DBoxes") && result.box3dUrl != "") { mediaFileInfo.fileURL = result.box3dUrl; mediaFileInfo.fileFormat = result.box3dFormat; @@ -265,11 +269,13 @@ MDResolveHandle::MDResolveHandle(const ScraperSearchResult& result, setError("Error saving resized image. Out of memory? Disk full?"); return; } + + mResult.savedNewImages = true; } // If it's not cached, then initiate the download. else { mFuncs.push_back(ResolvePair(downloadImageAsync(it->fileURL, filePath, - it->existingMediaFile), [this, filePath] { + it->existingMediaFile, mResult.savedNewImages), [this, filePath] { })); } } @@ -300,12 +306,13 @@ void MDResolveHandle::update() } std::unique_ptr downloadImageAsync(const std::string& url, - const std::string& saveAs, const std::string& existingMediaFile) + const std::string& saveAs, const std::string& existingMediaFile, bool& savedNewImage) { return std::unique_ptr(new ImageDownloadHandle( url, saveAs, existingMediaFile, + savedNewImage, Settings::getInstance()->getInt("ScraperResizeMaxWidth"), Settings::getInstance()->getInt("ScraperResizeMaxHeight"))); } @@ -314,6 +321,7 @@ ImageDownloadHandle::ImageDownloadHandle( const std::string& url, const std::string& path, const std::string& existingMediaPath, + bool& savedNewImage, int maxWidth, int maxHeight) : mSavePath(path), @@ -322,6 +330,7 @@ ImageDownloadHandle::ImageDownloadHandle( mMaxHeight(maxHeight), mReq(new HttpReq(url)) { + mSavedNewImagePtr = &savedNewImage; } void ImageDownloadHandle::update() @@ -377,6 +386,9 @@ void ImageDownloadHandle::update() return; } + // If this image was successfully saved, update savedNewImages in ScraperSearchResult. + *mSavedNewImagePtr = true; + setStatus(ASYNC_DONE); } diff --git a/es-app/src/scrapers/Scraper.h b/es-app/src/scrapers/Scraper.h index d83530fa2..4206bf419 100644 --- a/es-app/src/scrapers/Scraper.h +++ b/es-app/src/scrapers/Scraper.h @@ -64,6 +64,9 @@ struct ScraperSearchResult { std::string coverFormat; std::string marqueeFormat; std::string screenshotFormat; + + // Indicate whether any new images were downloaded and saved. + bool savedNewImages; }; // So let me explain why I've abstracted this so heavily. @@ -184,6 +187,7 @@ public: const std::string& url, const std::string& path, const std::string& existingMediaPath, + bool& savedNewImage, int maxWidth, int maxHeight); @@ -193,6 +197,7 @@ private: std::unique_ptr mReq; std::string mSavePath; std::string mExistingMediaFile; + bool *mSavedNewImagePtr; int mMaxWidth; int mMaxHeight; }; @@ -206,7 +211,7 @@ std::string getSaveAsPath(const ScraperSearchParams& params, // Will resize according to Settings::getInt("ScraperResizeMaxWidth") and // Settings::getInt("ScraperResizeMaxHeight"). std::unique_ptr downloadImageAsync(const std::string& url, - const std::string& saveAs, const std::string& existingMediaPath); + const std::string& saveAs, const std::string& existingMediaPath, bool& savedNewImage); // Resolves all metadata assets that need to be downloaded. std::unique_ptr resolveMetaDataAssets(const ScraperSearchResult& result, diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 68a19661f..903173141 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -326,7 +326,7 @@ std::shared_ptr ViewController::getGameListView(SystemData* syste selectedViewType = VIDEO; break; } - else if (!(*it)->getThumbnailPath().empty()) { + else if (!(*it)->getImagePath().empty()) { selectedViewType = DETAILED; // Don't break out in case any subsequent files have videos. } diff --git a/es-app/src/views/gamelist/BasicGameListView.cpp b/es-app/src/views/gamelist/BasicGameListView.cpp index 1cccf5881..fd966ea25 100644 --- a/es-app/src/views/gamelist/BasicGameListView.cpp +++ b/es-app/src/views/gamelist/BasicGameListView.cpp @@ -13,11 +13,8 @@ #include "Settings.h" #include "SystemData.h" -BasicGameListView::BasicGameListView( - Window* window, - FileData* root) - : ISimpleGameListView(window, root), - mList(window) +BasicGameListView::BasicGameListView(Window* window, FileData* root) + : ISimpleGameListView(window, root), mList(window) { mList.setSize(mSize.x(), mSize.y() * 0.8f); mList.setPosition(0, mSize.y() * 0.2f); @@ -38,8 +35,7 @@ void BasicGameListView::onThemeChanged(const std::shared_ptr& theme) void BasicGameListView::onFileChanged(FileData* file, FileChangeType change) { - if (change == FILE_METADATA_CHANGED) - { + if (change == FILE_METADATA_CHANGED) { // Might switch to a detailed view. ViewController::get()->reloadGameListView(this); return; @@ -146,6 +142,28 @@ void BasicGameListView::remove(FileData *game, bool deleteFile) if (deleteFile) Utils::FileSystem::removeFile(game->getPath()); + // Remove all game media files as well. + if (Utils::FileSystem::exists(game->getVideoPath())) + Utils::FileSystem::removeFile(game->getVideoPath()); + + if (Utils::FileSystem::exists(game->getMiximagePath())) + Utils::FileSystem::removeFile(game->getMiximagePath()); + + if (Utils::FileSystem::exists(game->getScreenshotPath())) + Utils::FileSystem::removeFile(game->getScreenshotPath()); + + if (Utils::FileSystem::exists(game->getCoverPath())) + Utils::FileSystem::removeFile(game->getCoverPath()); + + if (Utils::FileSystem::exists(game->getMarqueePath())) + Utils::FileSystem::removeFile(game->getMarqueePath()); + + if (Utils::FileSystem::exists(game->get3DBoxPath())) + Utils::FileSystem::removeFile(game->get3DBoxPath()); + + if (Utils::FileSystem::exists(game->getThumbnailPath())) + Utils::FileSystem::removeFile(game->getThumbnailPath()); + FileData* parent = game->getParent(); // Select next element in list, or previous if none. if (getCursor() == game) { @@ -164,6 +182,11 @@ void BasicGameListView::remove(FileData *game, bool deleteFile) if (mList.size() == 0) addPlaceholder(); + // If a game has been deleted, immediately remove the entry from gamelist.xml + // regardless of the value of the setting SaveGamelistsMode. + game->setDeletionFlag(); + parent->getSystem()->writeMetaData(); + // Remove before repopulating (removes from parent), then update the view. delete game; onFileChanged(parent, FILE_REMOVED); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 0fdf343c1..d55cbf1e3 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -77,7 +77,7 @@ void Settings::setDefaults() // UI settings. mStringMap["StartupSystem"] = ""; - mStringMap["GamelistViewStyle"] = "detailed"; + mStringMap["GamelistViewStyle"] = "automatic"; mStringMap["TransitionStyle"] = "instant"; mStringMap["ThemeSet"] = ""; mStringMap["UIMode"] = "Full";