mirror of
https://github.com/FunKey-Project/RetroFE.git
synced 2025-12-12 17:58:53 +01:00
Removed collection database concept. Replacing with a metadata concept.
This commit is contained in:
parent
86f80f06cb
commit
898d2dedb3
@ -79,9 +79,9 @@ set(RETROFE_HEADERS
|
||||
"${RETROFE_DIR}/Source/Collection/Item.h"
|
||||
"${RETROFE_DIR}/Source/Collection/MenuParser.h"
|
||||
"${RETROFE_DIR}/Source/Control/UserInput.h"
|
||||
"${RETROFE_DIR}/Source/Database/CollectionDatabase.h"
|
||||
"${RETROFE_DIR}/Source/Database/Configuration.h"
|
||||
"${RETROFE_DIR}/Source/Database/DB.h"
|
||||
"${RETROFE_DIR}/Source/Database/MetadataDatabase.h"
|
||||
"${RETROFE_DIR}/Source/Database/MamelistMetadata.h"
|
||||
"${RETROFE_DIR}/Source/Execute/AttractMode.h"
|
||||
"${RETROFE_DIR}/Source/Execute/Launcher.h"
|
||||
@ -122,9 +122,9 @@ set(RETROFE_SOURCES
|
||||
"${RETROFE_DIR}/Source/Collection/Item.cpp"
|
||||
"${RETROFE_DIR}/Source/Collection/MenuParser.cpp"
|
||||
"${RETROFE_DIR}/Source/Control/UserInput.cpp"
|
||||
"${RETROFE_DIR}/Source/Database/CollectionDatabase.cpp"
|
||||
"${RETROFE_DIR}/Source/Database/Configuration.cpp"
|
||||
"${RETROFE_DIR}/Source/Database/DB.cpp"
|
||||
"${RETROFE_DIR}/Source/Database/MetadataDatabase.cpp"
|
||||
"${RETROFE_DIR}/Source/Database/MamelistMetadata.cpp"
|
||||
"${RETROFE_DIR}/Source/Execute/AttractMode.cpp"
|
||||
"${RETROFE_DIR}/Source/Execute/Launcher.cpp"
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
* along with RetroFE. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "CollectionInfo.h"
|
||||
#include "Item.h"
|
||||
#include "../Database/Configuration.h"
|
||||
#include <sstream>
|
||||
|
||||
@ -32,6 +33,14 @@ CollectionInfo::CollectionInfo(std::string name,
|
||||
|
||||
CollectionInfo::~CollectionInfo()
|
||||
{
|
||||
std::vector<Item *>::iterator it = Items.begin();
|
||||
|
||||
while(it != Items.end())
|
||||
{
|
||||
delete *it;
|
||||
Items.erase(it);
|
||||
it = Items.begin();
|
||||
}
|
||||
}
|
||||
|
||||
std::string CollectionInfo::GetName() const
|
||||
@ -74,6 +83,10 @@ void CollectionInfo::GetExtensions(std::vector<std::string> &extensions)
|
||||
extensions.push_back(token);
|
||||
}
|
||||
}
|
||||
std::vector<Item *> *CollectionInfo::GetItems()
|
||||
{
|
||||
return &Items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class Item;
|
||||
|
||||
class CollectionInfo
|
||||
{
|
||||
public:
|
||||
@ -17,6 +19,7 @@ public:
|
||||
std::string GetMetadataType() const;
|
||||
std::string GetMetadataPath() const;
|
||||
std::string GetExtensions() const;
|
||||
std::vector<Item *> *GetItems();
|
||||
void GetExtensions(std::vector<std::string> &extensions);
|
||||
|
||||
private:
|
||||
@ -25,4 +28,5 @@ private:
|
||||
std::string Extensions;
|
||||
std::string MetadataType;
|
||||
std::string MetadataPath;
|
||||
std::vector<Item *> Items;
|
||||
};
|
||||
|
||||
@ -15,86 +15,28 @@
|
||||
*/
|
||||
#include "CollectionInfoBuilder.h"
|
||||
#include "CollectionInfo.h"
|
||||
#include "Item.h"
|
||||
#include "../Database/Configuration.h"
|
||||
#include "../Database/MetadataDatabase.h"
|
||||
#include "../Database/DB.h"
|
||||
#include "../Utility/Log.h"
|
||||
#include "../Utility/Utils.h"
|
||||
#include <dirent.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
CollectionInfoBuilder::CollectionInfoBuilder(Configuration &c)
|
||||
CollectionInfoBuilder::CollectionInfoBuilder(Configuration &c, DB &db)
|
||||
: Conf(c)
|
||||
, MetaDB(db, c)
|
||||
{
|
||||
}
|
||||
|
||||
CollectionInfoBuilder::~CollectionInfoBuilder()
|
||||
{
|
||||
std::map<std::string, CollectionInfo *>::iterator it = InfoMap.begin();
|
||||
|
||||
for(it == InfoMap.begin(); it != InfoMap.end(); ++it)
|
||||
{
|
||||
delete it->second;
|
||||
}
|
||||
|
||||
InfoMap.clear();
|
||||
}
|
||||
|
||||
bool CollectionInfoBuilder::LoadAllCollections()
|
||||
CollectionInfo *CollectionInfoBuilder::BuildCollection(std::string name)
|
||||
{
|
||||
std::vector<std::string> collections;
|
||||
|
||||
Conf.GetChildKeyCrumbs("collections", collections);
|
||||
|
||||
if(collections.size() == 0)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_ERROR, "Collections", "No collections were found. Please configure Settings.conf");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool retVal = true;
|
||||
std::vector<std::string>::iterator it;
|
||||
|
||||
for(it = collections.begin(); it != collections.end(); ++it)
|
||||
{
|
||||
// todo: There is nothing that should really stop us from creating a collection
|
||||
// in the main folder. I just need to find some time to look at the impacts if
|
||||
// I remove this conditional check.
|
||||
if(*it != "Main")
|
||||
{
|
||||
std::string oldCollection = Conf.GetCurrentCollection();
|
||||
Conf.SetCurrentCollection(*it);
|
||||
if(ImportCollection(*it))
|
||||
{
|
||||
Logger::Write(Logger::ZONE_INFO, "Collections", "Adding collection " + *it);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Continue processing the rest of the collections if an error occurs during import.
|
||||
// ImportCollection() will print out an error to the log file.
|
||||
retVal = false;
|
||||
}
|
||||
Conf.SetCurrentCollection(oldCollection);
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void CollectionInfoBuilder::GetCollections(std::vector<CollectionInfo *> &collections)
|
||||
{
|
||||
std::map<std::string, CollectionInfo *>::iterator InfoMapIt;
|
||||
|
||||
for(InfoMapIt = InfoMap.begin(); InfoMapIt != InfoMap.end(); ++InfoMapIt)
|
||||
{
|
||||
collections.push_back(InfoMapIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
bool CollectionInfoBuilder::ImportCollection(std::string name)
|
||||
{
|
||||
// create a new instance if one does not exist
|
||||
if(InfoMap.find(name) != InfoMap.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
std::string listItemsPathKey = "collections." + name + ".list.path";
|
||||
std::string listFilterKey = "collections." + name + ".list.filter";
|
||||
std::string extensionsKey = "collections." + name + ".list.extensions";
|
||||
@ -128,7 +70,87 @@ bool CollectionInfoBuilder::ImportCollection(std::string name)
|
||||
Logger::Write(Logger::ZONE_WARNING, "Collections", ss.str());
|
||||
}
|
||||
|
||||
InfoMap[name] = new CollectionInfo(name, listItemsPath, extensions, metadataType, metadataPath);
|
||||
CollectionInfo *collection = new CollectionInfo(name, listItemsPath, extensions, metadataType, metadataPath);
|
||||
std::vector<Item *> *list = collection->GetItems();
|
||||
|
||||
return (InfoMap[name] != NULL);
|
||||
ImportDirectory(collection);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
bool CollectionInfoBuilder::ImportDirectory(CollectionInfo *info)
|
||||
{
|
||||
DIR *dp;
|
||||
struct dirent *dirp;
|
||||
std::string path = info->GetListPath();
|
||||
std::map<std::string, Item *> includeFilter;
|
||||
std::map<std::string, Item *> excludeFilter;
|
||||
bool retVal = true;
|
||||
std::string includeFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Include.txt";
|
||||
std::string excludeFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Exclude.txt";
|
||||
std::string launcher;
|
||||
|
||||
(void)Conf.GetProperty("collections." + info->GetName() + ".launcher", launcher);
|
||||
|
||||
dp = opendir(path.c_str());
|
||||
std::vector<std::string> extensions;
|
||||
info->GetExtensions(extensions);
|
||||
std::vector<std::string>::iterator extensionsIt;
|
||||
|
||||
if(dp == NULL)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_ERROR, "CollectionInfoBuilder", "Could not read directory \"" + path + "\"");
|
||||
//todo: store into a database
|
||||
}
|
||||
else
|
||||
{
|
||||
while((dirp = readdir(dp)) != NULL)
|
||||
{
|
||||
std::string file = dirp->d_name;
|
||||
|
||||
Utils::NormalizeBackSlashes(file);
|
||||
size_t position = file.find_last_of(".");
|
||||
std::string basename = (std::string::npos == position)? file : file.substr(0, position);
|
||||
|
||||
if((includeFilter.size() == 0 || includeFilter.find(basename) != includeFilter.end()) &&
|
||||
(excludeFilter.size() == 0 || excludeFilter.find(basename) == excludeFilter.end()))
|
||||
{
|
||||
for(extensionsIt = extensions.begin(); extensionsIt != extensions.end(); ++extensionsIt)
|
||||
{
|
||||
std::string comparator = "." + *extensionsIt;
|
||||
int start = file.length() - comparator.length() + 1;
|
||||
|
||||
if(start >= 0)
|
||||
{
|
||||
if(file.compare(start, comparator.length(), *extensionsIt) == 0)
|
||||
{
|
||||
Item *i = new Item();
|
||||
i->SetName(basename);
|
||||
i->SetFullTitle(basename);
|
||||
i->SetTitle(basename);
|
||||
i->SetLauncher(launcher);
|
||||
info->GetItems()->push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MetaDB.UpdateMetadata(info);
|
||||
|
||||
while(includeFilter.size() > 0)
|
||||
{
|
||||
std::map<std::string, Item *>::iterator it = includeFilter.begin();
|
||||
delete it->second;
|
||||
includeFilter.erase(it);
|
||||
}
|
||||
while(excludeFilter.size() > 0)
|
||||
{
|
||||
std::map<std::string, Item *>::iterator it = excludeFilter.begin();
|
||||
delete it->second;
|
||||
excludeFilter.erase(it);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -3,6 +3,8 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../Database/DB.h"
|
||||
#include "../Database/MetadataDatabase.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
@ -10,16 +12,16 @@
|
||||
class Configuration;
|
||||
class CollectionInfo;
|
||||
|
||||
|
||||
class CollectionInfoBuilder
|
||||
{
|
||||
public:
|
||||
CollectionInfoBuilder(Configuration &c);
|
||||
CollectionInfoBuilder(Configuration &c, DB &db);
|
||||
virtual ~CollectionInfoBuilder();
|
||||
bool LoadAllCollections();
|
||||
void GetCollections(std::vector<CollectionInfo *> &keys);
|
||||
CollectionInfo *BuildCollection(std::string collectionName);
|
||||
|
||||
private:
|
||||
bool ImportCollection(std::string name);
|
||||
std::map<std::string, CollectionInfo *> InfoMap;
|
||||
MetadataDatabase MetaDB;
|
||||
bool ImportDirectory(CollectionInfo *info);
|
||||
Configuration &Conf;
|
||||
};
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
#include "MenuParser.h"
|
||||
#include "CollectionInfo.h"
|
||||
#include "Item.h"
|
||||
#include "../Utility/Log.h"
|
||||
#include "../Database/Configuration.h"
|
||||
#include "../Database/CollectionDatabase.h"
|
||||
#include "../Database/DB.h"
|
||||
#include <algorithm>
|
||||
#include <rapidxml.hpp>
|
||||
@ -39,11 +39,11 @@ MenuParser::~MenuParser()
|
||||
}
|
||||
|
||||
//todo: clean up this method, too much nesting
|
||||
bool MenuParser::GetMenuItems(CollectionDatabase *cdb, std::string collectionName, std::vector<Item *> &items)
|
||||
bool MenuParser::GetMenuItems(CollectionInfo *collection)
|
||||
{
|
||||
bool retVal = false;
|
||||
//todo: magic string
|
||||
std::string menuFilename = Configuration::GetAbsolutePath() + "/Collections/" + collectionName + "/Menu.xml";
|
||||
std::string menuFilename = Configuration::GetAbsolutePath() + "/Collections/" + collection->GetName() + "/Menu.xml";
|
||||
rapidxml::xml_document<> doc;
|
||||
rapidxml::xml_node<> * rootNode;
|
||||
|
||||
@ -93,18 +93,23 @@ bool MenuParser::GetMenuItems(CollectionDatabase *cdb, std::string collectionNam
|
||||
item->SetFullTitle(title);
|
||||
item->SetName(collectionAttribute->value());
|
||||
item->SetIsLeaf(false);
|
||||
items.push_back(item);
|
||||
collection->GetItems()->push_back(item);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string collectionName = collectionAttribute->value();
|
||||
Logger::Write(Logger::ZONE_INFO, "Menu", "Loading collection into menu: " + collectionName);
|
||||
cdb->GetCollection(collectionAttribute->value(), items);
|
||||
|
||||
//todo: unsupported option with this refactor
|
||||
// need to append the collection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort( items.begin(), items.end(), VectorSort);
|
||||
// todo: sorting should occur within the collection itself, not externally
|
||||
std::vector<Item *> *items = collection->GetItems();
|
||||
std::sort( items->begin(), items->end(), VectorSort);
|
||||
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
@ -2,16 +2,14 @@
|
||||
* file 'LICENSE.txt', which is part of this source code package.
|
||||
*/
|
||||
#pragma once
|
||||
#include "Item.h"
|
||||
#include <vector>
|
||||
|
||||
class CollectionDatabase;
|
||||
class CollectionInfo;
|
||||
|
||||
class MenuParser
|
||||
{
|
||||
public:
|
||||
MenuParser();
|
||||
virtual ~MenuParser();
|
||||
bool GetMenuItems(CollectionDatabase *cdb, std::string collectionName, std::vector<Item *> &items);
|
||||
bool GetMenuItems(CollectionInfo *cdb);
|
||||
|
||||
};
|
||||
|
||||
@ -1,759 +0,0 @@
|
||||
/* This file is part of RetroFE.
|
||||
*
|
||||
* RetroFE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* RetroFE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with RetroFE. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "CollectionDatabase.h"
|
||||
#include "../Collection/CollectionInfoBuilder.h"
|
||||
#include "../Collection/CollectionInfo.h"
|
||||
#include "../Collection/Item.h"
|
||||
#include "../Utility/Log.h"
|
||||
#include "../Utility/Utils.h"
|
||||
#include "MamelistMetadata.h"
|
||||
#include "Configuration.h"
|
||||
#include "DB.h"
|
||||
#include <algorithm>
|
||||
#include <dirent.h>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <rapidxml.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <sys/types.h>
|
||||
#include <sqlite3.h>
|
||||
#include <zlib.h>
|
||||
#include <exception>
|
||||
|
||||
CollectionDatabase::CollectionDatabase(DB &db, Configuration &c)
|
||||
: Config(c)
|
||||
, DBInstance(db)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
CollectionDatabase::~CollectionDatabase()
|
||||
{
|
||||
}
|
||||
|
||||
bool CollectionDatabase::ResetDatabase()
|
||||
{
|
||||
bool retVal = true;
|
||||
int rc;
|
||||
char *error = NULL;
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Erasing");
|
||||
|
||||
std::string sql;
|
||||
sql.append("DROP TABLE IF EXISTS CollectionItems;");
|
||||
sql.append("DROP TABLE IF EXISTS Meta;");
|
||||
sql.append("DROP TABLE IF EXISTS Collections;");
|
||||
|
||||
rc = sqlite3_exec(handle, sql.c_str(), NULL, 0, &error);
|
||||
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unable to create Configurations table. Error: " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool CollectionDatabase::Initialize()
|
||||
{
|
||||
int rc;
|
||||
char *error = NULL;
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
|
||||
std::string sql;
|
||||
sql.append("CREATE TABLE IF NOT EXISTS CollectionItems(");
|
||||
sql.append("collectionName TEXT KEY,");
|
||||
sql.append("filePath TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("name TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("hidden INT NOT NULL DEFAULT 0);");
|
||||
|
||||
sql.append("CREATE TABLE IF NOT EXISTS Meta(");
|
||||
sql.append("collectionName TEXT KEY,");
|
||||
sql.append("name TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("title TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("year TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("manufacturer TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("cloneOf TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("players INTEGER,");
|
||||
sql.append("buttons INTEGER);");
|
||||
sql.append("CREATE UNIQUE INDEX IF NOT EXISTS MetaUniqueId ON Meta(collectionName, name);");
|
||||
|
||||
sql.append("CREATE TABLE IF NOT EXISTS Collections(");
|
||||
sql.append("collectionName TEXT KEY,");
|
||||
sql.append("crc32 UNSIGNED INTEGER NOT NULL DEFAULT 0);");
|
||||
sql.append("CREATE UNIQUE INDEX IF NOT EXISTS CollectionsUniqueId ON Collections(collectionName);");
|
||||
|
||||
rc = sqlite3_exec(handle, sql.c_str(), NULL, 0, &error);
|
||||
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unable to create Configurations table. Error: " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CollectionDatabase::Import()
|
||||
{
|
||||
bool retVal = true;
|
||||
|
||||
// should keep allocation here
|
||||
CollectionInfoBuilder cib(Config);
|
||||
|
||||
(void)cib.LoadAllCollections();
|
||||
|
||||
std::vector<CollectionInfo *> collections;
|
||||
cib.GetCollections(collections);
|
||||
|
||||
std::vector<CollectionInfo *>::iterator it;
|
||||
for(it = collections.begin(); it != collections.end() && retVal; ++it)
|
||||
{
|
||||
CollectionInfo *info = *it;
|
||||
std::string title = info->GetName();
|
||||
unsigned long crc32 = CalculateCollectionCrc32(info);
|
||||
|
||||
std::stringstream crcStr;
|
||||
crcStr << crc32;
|
||||
|
||||
if(title != "Main")
|
||||
{
|
||||
if(CollectionChanged(info, crc32))
|
||||
{
|
||||
std::string msg = "Detected collection \"" + title + "\" has changed (new CRC: " + crcStr.str() + "). Rebuilding database for this collection.";
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", msg);
|
||||
|
||||
(void)ImportDirectory(info, crc32);
|
||||
retVal = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream ss;
|
||||
std::string msg = "Collection \"" + title + "\" has not changed (CRC: " + crcStr.str() + "). Using existing database settings.";
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", msg);
|
||||
|
||||
}
|
||||
}
|
||||
//std::cout << "Importing collection metadata for " << info->GetFullTitle() << " (collections." << info->GetName() << ")" << std::endl;
|
||||
//ImportMetadata(info);
|
||||
}
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Updating complete");
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
unsigned long CollectionDatabase::CalculateCollectionCrc32(CollectionInfo *info)
|
||||
{
|
||||
unsigned long crc = crc32(0L, Z_NULL, 0);
|
||||
|
||||
// start off by reading all of the contents in the collection configuration folders
|
||||
std::string settingsFile = info->GetSettingsPath() + "/Settings.conf";
|
||||
crc = CrcFile(settingsFile, crc);
|
||||
|
||||
|
||||
std::string includeFile = info->GetSettingsPath() + "/Include.txt";
|
||||
crc = CrcFile(includeFile, crc);
|
||||
|
||||
std::string excludeFile = info->GetSettingsPath() + "/Exclude.txt";
|
||||
crc = CrcFile(excludeFile, crc);
|
||||
|
||||
std::string mamelistFile = info->GetSettingsPath() + "/Mamelist.xml";
|
||||
crc = CrcFile(mamelistFile, crc);
|
||||
|
||||
DIR *dp;
|
||||
struct dirent *dirp;
|
||||
std::string path = info->GetListPath();
|
||||
dp = opendir(path.c_str());
|
||||
|
||||
if(dp == NULL)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", "Could not read directory for caching \"" + info->GetListPath() + "\"");
|
||||
return crc;
|
||||
}
|
||||
|
||||
std::vector<std::string> extensions;
|
||||
info->GetExtensions(extensions);
|
||||
std::vector<std::string>::iterator extensionsIt;
|
||||
|
||||
// md5sum each filename for the matching extension
|
||||
while((dirp = readdir(dp)) != NULL)
|
||||
{
|
||||
std::string file = dirp->d_name;
|
||||
for(extensionsIt = extensions.begin(); extensionsIt != extensions.end(); ++extensionsIt)
|
||||
{
|
||||
std::string comparator = "." + *extensionsIt;
|
||||
int start = file.length() - comparator.length() + 1;
|
||||
|
||||
if(start >= 0 && file.compare(start, comparator.length(), *extensionsIt) == 0)
|
||||
{
|
||||
std::string filename = dirp->d_name;
|
||||
filename.append("\n");
|
||||
crc = crc32(crc, (const unsigned char *)filename.c_str(), (unsigned int)filename.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
unsigned long CollectionDatabase::CrcFile(std::string file, unsigned long crc)
|
||||
{
|
||||
// CRC both the filename and its contents
|
||||
crc = crc32(crc, (const unsigned char *)file.c_str(), (unsigned int)file.length());
|
||||
std::ifstream ifFile(file.c_str());
|
||||
if(ifFile.good())
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << ifFile.rdbuf();
|
||||
|
||||
crc = crc32(crc, (const unsigned char *)ss.str().c_str(), (unsigned int)ss.str().length());
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Crcing \"" + file + "\"");
|
||||
ifFile.close();
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool CollectionDatabase::CollectionChanged(CollectionInfo *info, unsigned long crc32)
|
||||
{
|
||||
bool retVal = true;
|
||||
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
int rc;
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
sqlite3_prepare_v2(handle,
|
||||
"SELECT crc32 "
|
||||
"FROM Collections WHERE collectionName=? and crc32=?;",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int(stmt, 2, crc32);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if(rc == SQLITE_ROW)
|
||||
{
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool CollectionDatabase::SetHidden(std::string collectionName, Item *item, bool hidden)
|
||||
{
|
||||
bool retVal = true;
|
||||
char *error = NULL;
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
std::string mode = (hidden) ? "hidden":"visible";
|
||||
int isHidden = (hidden)?1:0;
|
||||
|
||||
Logger::Write(Logger::ZONE_DEBUG, "Database", "Marking \"" + item->GetFullTitle() + "\" " + mode);
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
sqlite3_prepare_v2(handle,
|
||||
"UPDATE CollectionItems SET hidden=? WHERE collectionName=? AND name=?;",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_int(stmt, 1, isHidden);
|
||||
sqlite3_bind_text(stmt, 2, collectionName.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 3, item->GetFullTitle().c_str(), -1, SQLITE_TRANSIENT);
|
||||
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
//todo: This file needs MASSIVE REFACTORING!
|
||||
bool CollectionDatabase::ImportDirectory(CollectionInfo *info, unsigned long crc32)
|
||||
{
|
||||
DIR *dp;
|
||||
struct dirent *dirp;
|
||||
std::string path = info->GetListPath();
|
||||
std::map<std::string, Item *> includeFilter;
|
||||
std::map<std::string, Item *> excludeFilter;
|
||||
std::map<std::string, Item *> includeList;
|
||||
std::map<std::string, Item *> metaList;
|
||||
bool retVal = true;
|
||||
char *error = NULL;
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
std::string includeFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Include.txt";
|
||||
std::string excludeFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Exclude.txt";
|
||||
std::string includeHyperListFile = Configuration::GetAbsolutePath() + "/Collections/" + info->GetName() + "/Include.xml";
|
||||
|
||||
if(!ImportBasicList(info, includeFile, includeFilter))
|
||||
{
|
||||
ImportHyperList(info, includeHyperListFile, includeFilter);
|
||||
|
||||
}
|
||||
//todo: this shouldn't be read twice, perform a copy
|
||||
ImportHyperList(info, includeHyperListFile, metaList);
|
||||
|
||||
(void)ImportBasicList(info, excludeFile, excludeFilter);
|
||||
|
||||
dp = opendir(path.c_str());
|
||||
std::vector<std::string> extensions;
|
||||
info->GetExtensions(extensions);
|
||||
std::vector<std::string>::iterator extensionsIt;
|
||||
|
||||
if(dp == NULL)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", "Could not read directory \"" + info->GetListPath() + "\"");
|
||||
//todo: store into a database
|
||||
}
|
||||
else
|
||||
{
|
||||
while((dirp = readdir(dp)) != NULL)
|
||||
{
|
||||
std::string file = dirp->d_name;
|
||||
|
||||
Utils::NormalizeBackSlashes(file);
|
||||
size_t position = file.find_last_of(".");
|
||||
std::string basename = (std::string::npos == position)? file : file.substr(0, position);
|
||||
|
||||
|
||||
if((includeFilter.size() == 0 || includeFilter.find(basename) != includeFilter.end()) &&
|
||||
(excludeFilter.size() == 0 || excludeFilter.find(basename) == excludeFilter.end()))
|
||||
{
|
||||
for(extensionsIt = extensions.begin(); extensionsIt != extensions.end(); ++extensionsIt)
|
||||
{
|
||||
std::string comparator = "." + *extensionsIt;
|
||||
int start = file.length() - comparator.length() + 1;
|
||||
|
||||
if(start >= 0)
|
||||
{
|
||||
if(file.compare(start, comparator.length(), *extensionsIt) == 0)
|
||||
{
|
||||
if(includeList.find(basename) == includeList.end())
|
||||
{
|
||||
Item *i = new Item();
|
||||
i->SetFullTitle(file);
|
||||
includeList[basename] = i;
|
||||
}
|
||||
if(metaList.find(basename) == metaList.end())
|
||||
{
|
||||
Item *i = new Item();
|
||||
i->SetFullTitle(file);
|
||||
metaList[basename] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while(includeFilter.size() > 0)
|
||||
{
|
||||
std::map<std::string, Item *>::iterator it = includeFilter.begin();
|
||||
delete it->second;
|
||||
includeFilter.erase(it);
|
||||
}
|
||||
while(excludeFilter.size() > 0)
|
||||
{
|
||||
std::map<std::string, Item *>::iterator it = excludeFilter.begin();
|
||||
delete it->second;
|
||||
excludeFilter.erase(it);
|
||||
}
|
||||
|
||||
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Scanning to import \"" + path + "\"");
|
||||
sqlite3_exec(handle, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, &error);
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
sqlite3_prepare_v2(handle,
|
||||
"DELETE FROM Collections WHERE collectionName=?;",
|
||||
-1, &stmt, 0);
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
sqlite3_prepare_v2(handle,
|
||||
"DELETE FROM CollectionItems WHERE collectionName=?;",
|
||||
-1, &stmt, 0);
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
sqlite3_prepare_v2(handle,
|
||||
"DELETE FROM Meta WHERE collectionName=?;",
|
||||
-1, &stmt, 0);
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if(sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error) != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Updating cache collection failure " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
std::map<std::string, Item *>::iterator it;
|
||||
|
||||
if(sqlite3_exec(handle, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, &error) != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Delete cache collection failure " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
for(it = includeList.begin(); it != includeList.end(); it++)
|
||||
{
|
||||
std::string basename = it->first;
|
||||
Item *file = it->second;
|
||||
|
||||
std::string name = file->GetFullTitle();
|
||||
Utils::NormalizeBackSlashes(name);
|
||||
file->SetFullTitle(name);
|
||||
|
||||
sqlite3_prepare_v2(handle,
|
||||
"INSERT OR REPLACE INTO CollectionItems (collectionName, filePath, name) VALUES (?,?,?);",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, file->GetFullTitle().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 3, basename.c_str(), -1, SQLITE_TRANSIENT);
|
||||
|
||||
//todo: better error handling for all of these messages
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
for(it = metaList.begin(); it != metaList.end(); it++)
|
||||
{
|
||||
std::string basename = it->first;
|
||||
Item *file = it->second;
|
||||
|
||||
sqlite3_prepare_v2(handle,
|
||||
"INSERT OR REPLACE INTO Meta (collectionName, name, title, year, manufacturer, cloneOf, players, buttons) VALUES (?,?,?,?,?,?,?,?);",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, basename.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 3, basename.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 4, file->GetYear().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 5, file->GetManufacturer().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 6, file->GetCloneOf().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 7, file->GetNumberPlayersString().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 8, file->GetNumberButtonsString().c_str(), -1, SQLITE_TRANSIENT);
|
||||
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
sqlite3_prepare_v2(handle,
|
||||
"INSERT OR REPLACE INTO Collections (collectionName, crc32) VALUES (?,?);",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_text(stmt, 1, info->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_int(stmt, 2, crc32);
|
||||
|
||||
sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
|
||||
if(sqlite3_exec(handle, "COMMIT TRANSACTION;", NULL, NULL, &error) != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Updating cache collection failure " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Imported files from \"" + path + "\" into database");
|
||||
|
||||
//todo: create a helper method to get this file directly (copy paste hazard)
|
||||
std::string mamelistFile = info->GetSettingsPath() + "/Mamelist.xml";
|
||||
std::ifstream infile(mamelistFile.c_str());
|
||||
|
||||
if(infile.good())
|
||||
{
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Updating Mamelist metadata for \"" + info->GetName() + "\" (\"" + mamelistFile + "\") into database. This will take a while...");
|
||||
MamelistMetadata mld(DBInstance);
|
||||
mld.Import(mamelistFile, info->GetName());
|
||||
}
|
||||
infile.close();
|
||||
|
||||
|
||||
while(includeList.size() > 0)
|
||||
{
|
||||
std::map<std::string, Item *>::iterator it = includeList.begin();
|
||||
delete it->second;
|
||||
includeList.erase(it);
|
||||
}
|
||||
while(metaList.size() > 0)
|
||||
{
|
||||
std::map<std::string, Item *>::iterator it = metaList.begin();
|
||||
delete it->second;
|
||||
metaList.erase(it);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool CollectionDatabase::ImportBasicList(CollectionInfo *info, std::string file, std::map<std::string, Item *> &list)
|
||||
{
|
||||
bool retVal = false;
|
||||
|
||||
Logger::Write(Logger::ZONE_DEBUG, "Database", "Checking to see if \"" + file + "\" exists");
|
||||
|
||||
std::ifstream includeStream(file.c_str());
|
||||
|
||||
if (includeStream.good())
|
||||
{
|
||||
Logger::Write(Logger::ZONE_DEBUG, "Database", "Importing \"" + file + "\"");
|
||||
std::string line;
|
||||
|
||||
while(std::getline(includeStream, line))
|
||||
{
|
||||
if(list.find(line) == list.end())
|
||||
{
|
||||
Item *i = new Item();
|
||||
line.erase( std::remove(line.begin(), line.end(), '\r'), line.end() );
|
||||
|
||||
i->SetFullTitle(line);
|
||||
list[line] = i;
|
||||
Logger::Write(Logger::ZONE_DEBUG, "Database", "Including \"" + line + "\" (if file exists)");
|
||||
}
|
||||
}
|
||||
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
bool CollectionDatabase::ImportHyperList(CollectionInfo *info, std::string hyperlistFile, std::map<std::string, Item *> &list)
|
||||
{
|
||||
bool retVal = false;
|
||||
rapidxml::xml_document<> doc;
|
||||
std::ifstream file(hyperlistFile.c_str());
|
||||
std::vector<char> buffer((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
|
||||
Logger::Write(Logger::ZONE_DEBUG, "Database", "Checking to see if \"" + hyperlistFile + "\" exists");
|
||||
|
||||
if(!file.good())
|
||||
{
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Could not find HyperList file: " + hyperlistFile);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Importing: " + hyperlistFile);
|
||||
buffer.push_back('\0');
|
||||
|
||||
doc.parse<0>(&buffer[0]);
|
||||
|
||||
rapidxml::xml_node<> *root = doc.first_node("menu");
|
||||
|
||||
|
||||
if(!root)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_ERROR, "CollectionDatabase", "Does not appear to be a HyperList file (missing <menu> tag)");
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(rapidxml::xml_node<> *game = root->first_node("game"); game; game = game->next_sibling("game"))
|
||||
{
|
||||
rapidxml::xml_attribute<> *nameXml = game->first_attribute("name");
|
||||
rapidxml::xml_node<> *descriptionXml = game->first_node("description");
|
||||
rapidxml::xml_node<> *cloneofXml = game->first_node("cloneof");
|
||||
rapidxml::xml_node<> *crcXml = game->first_node("crc");
|
||||
rapidxml::xml_node<> *manufacturerXml = game->first_node("manufacturer");
|
||||
rapidxml::xml_node<> *yearXml = game->first_node("year");
|
||||
rapidxml::xml_node<> *genreXml = game->first_node("genre");
|
||||
rapidxml::xml_node<> *ratingXml = game->first_node("rating");
|
||||
rapidxml::xml_node<> *enabledXml = game->first_node("enabled");
|
||||
std::string name = (nameXml) ? nameXml->value() : "";
|
||||
std::string description = (descriptionXml) ? descriptionXml->value() : "";
|
||||
std::string crc = (crcXml) ? crcXml->value() : "";
|
||||
std::string cloneOf = (cloneofXml) ? cloneofXml->value() : "";
|
||||
std::string manufacturer = (manufacturerXml) ? manufacturerXml->value() : "";
|
||||
std::string year = (yearXml) ? yearXml->value() : "";
|
||||
std::string genre = (genreXml) ? genreXml->value() : "";
|
||||
std::string rating = (ratingXml) ? ratingXml->value() : "";
|
||||
std::string enabled = (enabledXml) ? enabledXml->value() : "";
|
||||
|
||||
if(name.length() > 0 && list.find(name) == list.end())
|
||||
{
|
||||
Item *i = new Item();
|
||||
i->SetFullTitle(name);
|
||||
i->SetYear(year);
|
||||
i->SetManufacturer(manufacturer);
|
||||
i->SetCloneOf(cloneOf);
|
||||
list[name] = i;
|
||||
Logger::Write(Logger::ZONE_DEBUG, "Database", "Including \"" + name + "\" (if file exists)");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(rapidxml::parse_error &e)
|
||||
{
|
||||
std::string what = e.what();
|
||||
long line = static_cast<long>(std::count(&buffer.front(), e.where<char>(), char('\n')) + 1);
|
||||
std::stringstream ss;
|
||||
ss << "Could not parse layout file. [Line: " << line << "] Reason: " << e.what();
|
||||
|
||||
Logger::Write(Logger::ZONE_ERROR, "Layout", ss.str());
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
std::string what = e.what();
|
||||
Logger::Write(Logger::ZONE_ERROR, "Layout", "Could not parse layout file. Reason: " + what);
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/*
|
||||
bool CollectionDatabase::ImportMetadata(CollectionInfo *info)
|
||||
{
|
||||
bool retVal = true;
|
||||
std::string type = info->GetMetadataType();
|
||||
|
||||
if(type.compare("mamelist") == 0)
|
||||
{
|
||||
MamelistMetadata meta;
|
||||
//todo: pass in collectionName
|
||||
retVal = meta.Import(info->GetMetadataPath(), "arcade");
|
||||
}
|
||||
else if(!type.empty())
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unsupported metadata type \"" << type << "\" for " << info->GetFullTitle() << " (collections." << info->GetName() << ".metadata.type)" << std::endl;
|
||||
Log::Write(Log::ERROR, "Database", ss.str());
|
||||
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
*/
|
||||
|
||||
bool CollectionDatabase::GetCollection(std::string collectionName, std::vector<Item *> &list)
|
||||
{
|
||||
bool retVal = true;
|
||||
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
int rc;
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
bool showParenthesis = true;
|
||||
bool showSquareBrackets = true;
|
||||
|
||||
(void)Config.GetProperty("showParenthesis", showParenthesis);
|
||||
(void)Config.GetProperty("showSquareBrackets", showSquareBrackets);
|
||||
|
||||
//todo: program crashes if this query fails
|
||||
sqlite3_prepare_v2(handle,
|
||||
"SELECT DISTINCT CollectionItems.filePath, CollectionItems.name, Meta.title, Meta.year, Meta.manufacturer, Meta.players, Meta.buttons, Meta.cloneOf "
|
||||
"FROM CollectionItems, Meta WHERE CollectionItems.collectionName=? AND Meta.collectionName=? AND CollectionItems.name=Meta.name AND CollectionItems.hidden=0 ORDER BY title ASC;",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_text(stmt, 1, collectionName.c_str(), -1, SQLITE_TRANSIENT);
|
||||
sqlite3_bind_text(stmt, 2, collectionName.c_str(), -1, SQLITE_TRANSIENT);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
while(rc == SQLITE_ROW)
|
||||
{
|
||||
std::string filePath = (char *)sqlite3_column_text(stmt, 0);
|
||||
std::string name = (char *)sqlite3_column_text(stmt, 1);
|
||||
std::string fullTitle = (char *)sqlite3_column_text(stmt, 2);
|
||||
std::string year = (char *)sqlite3_column_text(stmt, 3);
|
||||
std::string manufacturer = (char *)sqlite3_column_text(stmt, 4);
|
||||
int numberPlayers = (int)sqlite3_column_int(stmt, 5);
|
||||
int numberButtons = (int)sqlite3_column_int(stmt, 6);
|
||||
std::string cloneOf = (char *)sqlite3_column_text(stmt, 7);
|
||||
std::string launcher;
|
||||
std::string title = fullTitle;
|
||||
|
||||
if(!showParenthesis)
|
||||
{
|
||||
std::string::size_type firstPos = title.find_first_of("(");
|
||||
std::string::size_type secondPos = title.find_first_of(")", firstPos);
|
||||
|
||||
while(firstPos != std::string::npos && secondPos != std::string::npos)
|
||||
{
|
||||
firstPos = title.find_first_of("(");
|
||||
secondPos = title.find_first_of(")", firstPos);
|
||||
|
||||
if (firstPos != std::string::npos)
|
||||
{
|
||||
title.erase(firstPos, (secondPos - firstPos) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!showSquareBrackets)
|
||||
{
|
||||
std::string::size_type firstPos = title.find_first_of("[");
|
||||
std::string::size_type secondPos = title.find_first_of("]", firstPos);
|
||||
|
||||
while(firstPos != std::string::npos && secondPos != std::string::npos)
|
||||
{
|
||||
firstPos = title.find_first_of("[");
|
||||
secondPos = title.find_first_of("]", firstPos);
|
||||
|
||||
if (firstPos != std::string::npos && secondPos != std::string::npos)
|
||||
{
|
||||
title.erase(firstPos, (secondPos - firstPos) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item *item = new Item();
|
||||
item->SetFilePath(filePath);
|
||||
item->SetName(name);
|
||||
item->SetTitle(title);
|
||||
item->SetFullTitle(fullTitle);
|
||||
item->SetYear(year);
|
||||
item->SetManufacturer(manufacturer);
|
||||
item->SetNumberPlayers(numberPlayers);
|
||||
item->SetNumberButtons(numberButtons);
|
||||
item->SetCloneOf(cloneOf);
|
||||
|
||||
//std::cout << "loading " << title << std::endl;
|
||||
if(Config.GetProperty("collections." + collectionName + ".launcher", launcher))
|
||||
{
|
||||
item->SetLauncher(launcher);
|
||||
}
|
||||
|
||||
list.push_back(item);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
}
|
||||
|
||||
//todo: query the metadata table to populate each item
|
||||
|
||||
return retVal;
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
/* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.txt', which is part of this source code package.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class DB;
|
||||
class Configuration;
|
||||
class CollectionInfo;
|
||||
class Item;
|
||||
|
||||
class CollectionDatabase
|
||||
{
|
||||
public:
|
||||
CollectionDatabase(DB &db, Configuration &c);
|
||||
virtual ~CollectionDatabase();
|
||||
bool Initialize();
|
||||
bool Import();
|
||||
bool ResetDatabase();
|
||||
|
||||
|
||||
bool GetCollection(std::string collectionName, std::vector<Item *> &list);
|
||||
bool SetHidden(std::string collectionName, Item *item, bool hidden);
|
||||
|
||||
private:
|
||||
unsigned long CalculateCollectionCrc32(CollectionInfo *info);
|
||||
bool CollectionChanged(CollectionInfo *info, unsigned long crc32);
|
||||
unsigned long CrcFile(std::string file, unsigned long crc);
|
||||
|
||||
// bool ImportMetadata(CollectionInfo *info);
|
||||
bool ImportDirectory(CollectionInfo *info, unsigned long crc32);
|
||||
bool ImportBasicList(CollectionInfo *info,
|
||||
std::string file,
|
||||
std::map<std::string, Item *> &list);
|
||||
bool ImportHyperList(CollectionInfo *info,
|
||||
std::string file,
|
||||
std::map<std::string, Item *> &list);
|
||||
std::map<std::string, Item *> *ImportHyperList(CollectionInfo *info);
|
||||
Configuration &Config;
|
||||
DB &DBInstance;
|
||||
};
|
||||
198
RetroFE/Source/Database/MetadataDatabase.cpp
Normal file
198
RetroFE/Source/Database/MetadataDatabase.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
/* This file is part of RetroFE.
|
||||
*
|
||||
* RetroFE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* RetroFE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with RetroFE. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "MetadataDatabase.h"
|
||||
#include "../Collection/CollectionInfo.h"
|
||||
#include "../Collection/Item.h"
|
||||
#include "../Utility/Log.h"
|
||||
#include "../Utility/Utils.h"
|
||||
#include "MamelistMetadata.h"
|
||||
#include "Configuration.h"
|
||||
#include "DB.h"
|
||||
#include <algorithm>
|
||||
#include <dirent.h>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <rapidxml.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <sys/types.h>
|
||||
#include <sqlite3.h>
|
||||
#include <zlib.h>
|
||||
#include <exception>
|
||||
|
||||
MetadataDatabase::MetadataDatabase(DB &db, Configuration &c)
|
||||
: Config(c)
|
||||
, DBInstance(db)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MetadataDatabase::~MetadataDatabase()
|
||||
{
|
||||
}
|
||||
|
||||
bool MetadataDatabase::ResetDatabase()
|
||||
{
|
||||
bool retVal = true;
|
||||
int rc;
|
||||
char *error = NULL;
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
|
||||
Logger::Write(Logger::ZONE_INFO, "Database", "Erasing");
|
||||
|
||||
std::string sql;
|
||||
sql.append("DROP TABLE IF EXISTS Meta;");
|
||||
|
||||
rc = sqlite3_exec(handle, sql.c_str(), NULL, 0, &error);
|
||||
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unable to create Metadata table. Error: " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
retVal = false;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool MetadataDatabase::Initialize()
|
||||
{
|
||||
int rc;
|
||||
char *error = NULL;
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
|
||||
std::string sql;
|
||||
sql.append("CREATE TABLE IF NOT EXISTS Meta(");
|
||||
sql.append("collectionName TEXT KEY,");
|
||||
sql.append("name TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("title TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("year TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("manufacturer TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("cloneOf TEXT NOT NULL DEFAULT '',");
|
||||
sql.append("players INTEGER,");
|
||||
sql.append("buttons INTEGER);");
|
||||
sql.append("CREATE UNIQUE INDEX IF NOT EXISTS MetaUniqueId ON Meta(collectionName, name);");
|
||||
|
||||
rc = sqlite3_exec(handle, sql.c_str(), NULL, 0, &error);
|
||||
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unable to create Metadata table. Error: " << error;
|
||||
Logger::Write(Logger::ZONE_ERROR, "Database", ss.str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetadataDatabase::UpdateMetadata(CollectionInfo *collection)
|
||||
{
|
||||
sqlite3 *handle = DBInstance.GetHandle();
|
||||
int rc;
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
bool showParenthesis = true;
|
||||
bool showSquareBrackets = true;
|
||||
|
||||
(void)Config.GetProperty("showParenthesis", showParenthesis);
|
||||
(void)Config.GetProperty("showSquareBrackets", showSquareBrackets);
|
||||
|
||||
|
||||
// items into a hash to make it easily searchable
|
||||
std::vector<Item *> *items = collection->GetItems();
|
||||
std::map<std::string, Item *> itemMap;
|
||||
|
||||
for(std::vector<Item *>::iterator it = items->begin(); it != items->end(); it++)
|
||||
{
|
||||
itemMap[(*it)->GetName()] = *it;
|
||||
}
|
||||
|
||||
//todo: program crashes if this query fails
|
||||
sqlite3_prepare_v2(handle,
|
||||
"SELECT DISTINCT Meta.name, Meta.title, Meta.year, Meta.manufacturer, Meta.players, Meta.buttons, Meta.cloneOf "
|
||||
"FROM Meta WHERE collectionName=? ORDER BY title ASC;",
|
||||
-1, &stmt, 0);
|
||||
|
||||
sqlite3_bind_text(stmt, 1, collection->GetName().c_str(), -1, SQLITE_TRANSIENT);
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
while(rc == SQLITE_ROW)
|
||||
{
|
||||
std::string name = (char *)sqlite3_column_text(stmt, 0);
|
||||
std::string fullTitle = (char *)sqlite3_column_text(stmt, 1);
|
||||
std::string year = (char *)sqlite3_column_text(stmt, 2);
|
||||
std::string manufacturer = (char *)sqlite3_column_text(stmt, 3);
|
||||
int numberPlayers = (int)sqlite3_column_int(stmt, 4);
|
||||
int numberButtons = (int)sqlite3_column_int(stmt, 5);
|
||||
std::string cloneOf = (char *)sqlite3_column_text(stmt, 6);
|
||||
std::string launcher;
|
||||
std::string title = fullTitle;
|
||||
|
||||
//todo: this should be a helper method, peformed both in CollectionInfoBuilder
|
||||
if(!showParenthesis)
|
||||
{
|
||||
std::string::size_type firstPos = title.find_first_of("(");
|
||||
std::string::size_type secondPos = title.find_first_of(")", firstPos);
|
||||
|
||||
while(firstPos != std::string::npos && secondPos != std::string::npos)
|
||||
{
|
||||
firstPos = title.find_first_of("(");
|
||||
secondPos = title.find_first_of(")", firstPos);
|
||||
|
||||
if (firstPos != std::string::npos)
|
||||
{
|
||||
title.erase(firstPos, (secondPos - firstPos) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!showSquareBrackets)
|
||||
{
|
||||
std::string::size_type firstPos = title.find_first_of("[");
|
||||
std::string::size_type secondPos = title.find_first_of("]", firstPos);
|
||||
|
||||
while(firstPos != std::string::npos && secondPos != std::string::npos)
|
||||
{
|
||||
firstPos = title.find_first_of("[");
|
||||
secondPos = title.find_first_of("]", firstPos);
|
||||
|
||||
if (firstPos != std::string::npos && secondPos != std::string::npos)
|
||||
{
|
||||
title.erase(firstPos, (secondPos - firstPos) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, Item *>::iterator it = itemMap.find(name);
|
||||
|
||||
if(it != itemMap.end())
|
||||
{
|
||||
Item *item = it->second;
|
||||
item->SetTitle(title);
|
||||
item->SetFullTitle(fullTitle);
|
||||
item->SetYear(year);
|
||||
item->SetManufacturer(manufacturer);
|
||||
item->SetNumberPlayers(numberPlayers);
|
||||
item->SetNumberButtons(numberButtons);
|
||||
item->SetCloneOf(cloneOf);
|
||||
}
|
||||
rc = sqlite3_step(stmt);
|
||||
}
|
||||
}
|
||||
28
RetroFE/Source/Database/MetadataDatabase.h
Normal file
28
RetroFE/Source/Database/MetadataDatabase.h
Normal file
@ -0,0 +1,28 @@
|
||||
/* This file is subject to the terms and conditions defined in
|
||||
* file 'LICENSE.txt', which is part of this source code package.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class DB;
|
||||
class Configuration;
|
||||
class CollectionInfo;
|
||||
class Item;
|
||||
|
||||
class MetadataDatabase
|
||||
{
|
||||
public:
|
||||
MetadataDatabase(DB &db, Configuration &c);
|
||||
virtual ~MetadataDatabase();
|
||||
bool Initialize();
|
||||
bool ResetDatabase();
|
||||
|
||||
void UpdateMetadata(CollectionInfo *collection);
|
||||
|
||||
private:
|
||||
Configuration &Config;
|
||||
DB &DBInstance;
|
||||
};
|
||||
@ -15,8 +15,8 @@
|
||||
*/
|
||||
#include "ComponentItemBindingBuilder.h"
|
||||
#include "ComponentItemBinding.h"
|
||||
#include "../Database/CollectionDatabase.h"
|
||||
#include "../Collection/Item.h"
|
||||
#include "../Utility/Log.h"
|
||||
|
||||
ComponentItemBindingBuilder::ComponentItemBindingBuilder()
|
||||
{
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "Page.h"
|
||||
#include "ComponentItemBinding.h"
|
||||
#include "Component/Component.h"
|
||||
#include "../Collection/CollectionInfo.h"
|
||||
#include "Component/Text.h"
|
||||
#include "../Utility/Log.h"
|
||||
#include "Component/ScrollingList.h"
|
||||
@ -298,9 +299,9 @@ void Page::PageScroll(ScrollDirection direction)
|
||||
}
|
||||
|
||||
|
||||
void Page::SetItems(std::vector<Item *> *items)
|
||||
void Page::SetCollection(CollectionInfo *collection)
|
||||
{
|
||||
std::vector<ComponentItemBinding *> *sprites = ComponentItemBindingBuilder::BuildCollectionItems(items);
|
||||
std::vector<ComponentItemBinding *> *sprites = ComponentItemBindingBuilder::BuildCollectionItems(collection->GetItems());
|
||||
if(Menu)
|
||||
{
|
||||
Menu->SetItems(sprites);
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class CollectionInfo;
|
||||
class Component;
|
||||
class Configuration;
|
||||
class ScrollingList;
|
||||
@ -29,7 +30,7 @@ public:
|
||||
Page(std::string collectionName, Configuration &c);
|
||||
virtual ~Page();
|
||||
virtual void OnNewItemSelected(Item *);
|
||||
void SetItems(std::vector<Item *> *items);
|
||||
void SetCollection(CollectionInfo *collection);
|
||||
void SetMenu(ScrollingList *s);
|
||||
void SetLoadSound(Sound *chunk)
|
||||
{
|
||||
|
||||
@ -16,8 +16,6 @@
|
||||
|
||||
#include "Database/Configuration.h"
|
||||
#include "Database/CollectionDatabase.h"
|
||||
#include "Collection/CollectionInfoBuilder.h"
|
||||
#include "Collection/CollectionInfo.h"
|
||||
#include "Database/MamelistMetadata.h"
|
||||
#include "Execute/Launcher.h"
|
||||
#include "Utility/Log.h"
|
||||
@ -30,7 +28,6 @@
|
||||
|
||||
static bool ImportConfiguration(Configuration *c);
|
||||
static bool StartLogging();
|
||||
CollectionDatabase *InitializeCollectionDatabase(DB &db, Configuration &config);
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
*/
|
||||
|
||||
#include "RetroFE.h"
|
||||
#include "Database/CollectionDatabase.h"
|
||||
#include "Collection/CollectionInfoBuilder.h"
|
||||
#include "Collection/CollectionInfo.h"
|
||||
#include "Database/Configuration.h"
|
||||
#include "Collection/Item.h"
|
||||
#include "Execute/Launcher.h"
|
||||
@ -41,7 +42,6 @@ RetroFE::RetroFE(Configuration &c)
|
||||
, InitializeThread(NULL)
|
||||
, Config(c)
|
||||
, Db(NULL)
|
||||
, CollectionDB(NULL)
|
||||
, Input(Config)
|
||||
, KeyInputDisable(0)
|
||||
, CurrentTime(0)
|
||||
@ -53,30 +53,6 @@ RetroFE::~RetroFE()
|
||||
DeInitialize();
|
||||
}
|
||||
|
||||
CollectionDatabase *RetroFE::InitializeCollectionDatabase(DB &db, Configuration &config)
|
||||
{
|
||||
CollectionDatabase *cdb = NULL;
|
||||
|
||||
config.SetStatus("Initializing database");
|
||||
std::string dbFile = (Configuration::GetAbsolutePath() + "/cache.db");
|
||||
std::ifstream infile(dbFile.c_str());
|
||||
|
||||
cdb = new CollectionDatabase(db, config);
|
||||
|
||||
if(!cdb->Initialize())
|
||||
{
|
||||
delete cdb;
|
||||
cdb = NULL;
|
||||
}
|
||||
else if(!cdb->Import())
|
||||
{
|
||||
delete cdb;
|
||||
cdb = NULL;
|
||||
}
|
||||
|
||||
return cdb;
|
||||
}
|
||||
|
||||
void RetroFE::Render()
|
||||
{
|
||||
SDL_LockMutex(SDL::GetMutex());
|
||||
@ -115,16 +91,6 @@ int RetroFE::Initialize(void *context)
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
instance->CollectionDB = instance->InitializeCollectionDatabase(*instance->Db, instance->Config);
|
||||
|
||||
if(!instance->CollectionDB)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_ERROR, "RetroFE", "Could not initialize CollectionDB!");
|
||||
delete instance->Db;
|
||||
return -1;
|
||||
}
|
||||
|
||||
instance->Config.GetProperty("videoEnable", videoEnable);
|
||||
instance->Config.GetProperty("videoLoop", videoLoop);
|
||||
|
||||
@ -204,11 +170,6 @@ bool RetroFE::DeInitialize()
|
||||
Db = NULL;
|
||||
}
|
||||
|
||||
if(CollectionDB)
|
||||
{
|
||||
delete CollectionDB;
|
||||
Db = NULL;
|
||||
}
|
||||
Initialized = false;
|
||||
//todo: handle video deallocation
|
||||
return retVal;
|
||||
@ -492,7 +453,8 @@ Page *RetroFE::LoadPage(std::string collectionName)
|
||||
|
||||
Page *page = NULL;
|
||||
|
||||
std::vector<Item *> *collection = GetCollection(collectionName);
|
||||
Config.SetCurrentCollection(collectionName);
|
||||
CollectionInfo *collection = GetCollection(collectionName);
|
||||
std::string layoutName = GetLayout(collectionName);
|
||||
|
||||
if(PageChain.size() > 0)
|
||||
@ -510,8 +472,7 @@ Page *RetroFE::LoadPage(std::string collectionName)
|
||||
}
|
||||
else
|
||||
{
|
||||
Config.SetCurrentCollection(collectionName);
|
||||
page->SetItems(collection);
|
||||
page->SetCollection(collection);
|
||||
page->Start();
|
||||
|
||||
PageChain.push_back(page);
|
||||
@ -522,11 +483,10 @@ Page *RetroFE::LoadPage(std::string collectionName)
|
||||
Page *RetroFE::LoadSplashPage()
|
||||
{
|
||||
PageBuilder pb("Splash", "", Config, &FC);
|
||||
std::vector<Item *> *coll = new std::vector<Item *>();
|
||||
Page * page = pb.BuildPage();
|
||||
// page->SetStatusText("foobar");
|
||||
Config.SetCurrentCollection("");
|
||||
page->SetItems(coll);
|
||||
page->SetCollection(new CollectionInfo("", "", "", "", ""));
|
||||
page->Start();
|
||||
PageChain.push_back(page);
|
||||
|
||||
@ -534,16 +494,16 @@ Page *RetroFE::LoadSplashPage()
|
||||
}
|
||||
|
||||
|
||||
std::vector<Item *> *RetroFE::GetCollection(std::string collectionName)
|
||||
CollectionInfo *RetroFE::GetCollection(std::string collectionName)
|
||||
{
|
||||
// the page will deallocate this once its done
|
||||
std::vector<Item *> *collection = new std::vector<Item *>();
|
||||
MenuParser mp;
|
||||
|
||||
mp.GetMenuItems(CollectionDB, collectionName, *collection);
|
||||
CollectionDB->GetCollection(collectionName, *collection);
|
||||
CollectionInfoBuilder cib(Config, *Db);
|
||||
CollectionInfo *collection = cib.BuildCollection(collectionName);
|
||||
mp.GetMenuItems(collection);
|
||||
|
||||
if(collection->size() == 0)
|
||||
if(collection->GetItems()->size() == 0)
|
||||
{
|
||||
Logger::Write(Logger::ZONE_WARNING, "RetroFE", "No list items found for collection " + collectionName);
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
class CollectionDatabase;
|
||||
class CollectionInfo;
|
||||
class Configuration;
|
||||
class Page;
|
||||
|
||||
@ -47,7 +47,6 @@ private:
|
||||
};
|
||||
|
||||
void Render();
|
||||
CollectionDatabase *InitializeCollectionDatabase(DB &db, Configuration &config);
|
||||
bool Back(bool &exit);
|
||||
void Quit();
|
||||
void WaitToInitialize();
|
||||
@ -56,10 +55,9 @@ private:
|
||||
RETROFE_STATE ProcessUserInput(Page *page);
|
||||
void Update(float dt, bool scrollActive);
|
||||
std::string GetLayout(std::string collectionName);
|
||||
std::vector<Item *> *GetCollection(std::string collectionName);
|
||||
CollectionInfo *GetCollection(std::string collectionName);
|
||||
Configuration &Config;
|
||||
DB *Db;
|
||||
CollectionDatabase *CollectionDB;
|
||||
UserInput Input;
|
||||
std::list<Page *> PageChain;
|
||||
float KeyInputDisable;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user