Galaxy: Add small files container support

Makes the downloader use small files container when installing games via Galaxy API.
Instead of downloading a lot of very small files we download a file that
contains all the small files and extract it when everything is downloaded.
This commit is contained in:
Sude 2024-12-18 11:49:39 +02:00
parent 1b14b1129c
commit 70a4437c3c
3 changed files with 163 additions and 5 deletions

View File

@ -38,6 +38,10 @@ struct galaxyDepotItem
std::string md5;
std::string product_id;
bool isDependency = false;
bool isSmallFilesContainer = false;
bool isInSFC = false;
uintmax_t sfc_offset;
uintmax_t sfc_size;
};
class galaxyAPI

View File

@ -4013,13 +4013,17 @@ std::vector<galaxyDepotItem> Downloader::galaxyGetDepotItemVectorFromJson(const
}
}
// Set product id for items
// Set product id for items and add product id to small files container name
for (auto it = items.begin(); it != items.end(); ++it)
{
if (it->product_id.empty())
{
it->product_id = product_id;
}
if (it->isSmallFilesContainer)
{
it->path += "_" + it->product_id;
}
}
return items;
@ -4105,6 +4109,49 @@ void Downloader::galaxyInstallGameById(const std::string& product_id, const std:
}
}
std::vector<galaxyDepotItem> items_smallfiles;
std::vector<galaxyDepotItem> sfc_vector;
bool bUseSmallFilesContainer = true;
for (auto item : items)
{
if (item.isInSFC)
{
std::string item_install_path = install_path + "/" + item.path;
if (boost::filesystem::exists(item_install_path))
{
bUseSmallFilesContainer = false;
break;
}
}
}
if (!bUseSmallFilesContainer)
{
for (std::vector<galaxyDepotItem>::iterator it = items.begin(); it != items.end();)
{
if (it->isSmallFilesContainer)
it = items.erase(it);
else
++it;
}
}
else
{
for (std::vector<galaxyDepotItem>::iterator it = items.begin(); it != items.end();)
{
if (it->isSmallFilesContainer)
sfc_vector.push_back(*it);
if (it->isInSFC)
{
items_smallfiles.push_back(*it);
it = items.erase(it);
}
else
++it;
}
}
// Check for differences between previously installed build and new build
std::vector<galaxyDepotItem> items_old;
@ -4227,7 +4274,66 @@ void Downloader::galaxyInstallGameById(const std::string& product_id, const std:
vThreads.clear();
vDownloadInfo.clear();
if (bUseSmallFilesContainer)
{
for (auto container : sfc_vector)
{
std::string container_install_path = install_path + "/" + container.path;
if (!boost::filesystem::exists(container_install_path))
continue;
std::cout << "Extracting small files container " << container_install_path << std::endl;
for (auto item : items_smallfiles)
{
if (item.product_id != container.product_id)
continue;
std::string item_install_path = install_path + "/" + item.path;
std::cout << item_install_path << std::endl;
std::ifstream sfc(container_install_path, std::ifstream::binary);
if (sfc)
{
sfc.seekg(item.sfc_offset, sfc.beg);
char *filecontents = (char *) malloc(item.sfc_size);
sfc.read(filecontents, item.sfc_size);
sfc.close();
// Check that directory exists and create it
boost::filesystem::path path = item_install_path;
boost::filesystem::path directory = path.parent_path();
if (!boost::filesystem::exists(directory))
{
if (!boost::filesystem::create_directories(directory))
{
std::cout << "Failed to create directory: " << directory << std::endl;
free(filecontents);
continue;
}
}
std::ofstream output(item_install_path, std::ofstream::binary);
if (output)
{
output.write(filecontents, item.sfc_size);
output.close();
}
free(filecontents);
}
}
std::cout << "Deleting small files container " << container_install_path << std::endl;
if (!boost::filesystem::remove(container_install_path))
std::cerr << "Failed to delete " << container_install_path << std::endl;
}
}
std::cout << "Checking for orphaned files" << std::endl;
if (bUseSmallFilesContainer)
{
// Add small files back to items vector for ophan checking
items.insert(std::end(items), std::begin(items_smallfiles), std::end(items_smallfiles));
}
std::vector<std::string> orphans = this->galaxyGetOrphanedFiles(items, install_path);
std::cout << "\t" << orphans.size() << " orphaned files" << std::endl;
for (unsigned int i = 0; i < orphans.size(); ++i)

View File

@ -255,6 +255,45 @@ std::vector<galaxyDepotItem> galaxyAPI::getDepotItemsVector(const std::string& h
Json::Value json = this->getManifestV2(hash, is_dependency);
std::vector<galaxyDepotItem> items;
if (json["depot"].isMember("smallFilesContainer"))
{
if (json["depot"]["smallFilesContainer"]["chunks"].isArray())
{
galaxyDepotItem item;
item.totalSizeCompressed = 0;
item.totalSizeUncompressed = 0;
item.path = "galaxy_smallfilescontainer";
item.isDependency = is_dependency;
item.isSmallFilesContainer = true;
for (unsigned int i = 0; i < json["depot"]["smallFilesContainer"]["chunks"].size(); ++i)
{
Json::Value json_chunk = json["depot"]["smallFilesContainer"]["chunks"][i];
galaxyDepotItemChunk chunk;
chunk.md5_compressed = json_chunk["compressedMd5"].asString();
chunk.md5_uncompressed = json_chunk["md5"].asString();
chunk.size_compressed = json_chunk["compressedSize"].asLargestUInt();
chunk.size_uncompressed = json_chunk["size"].asLargestUInt();
chunk.offset_compressed = item.totalSizeCompressed;
chunk.offset_uncompressed = item.totalSizeUncompressed;
item.totalSizeCompressed += chunk.size_compressed;
item.totalSizeUncompressed += chunk.size_uncompressed;
item.chunks.push_back(chunk);
}
if (json["depot"]["smallFilesContainer"].isMember("md5"))
item.md5 = json["depot"]["smallFilesContainer"]["md5"].asString();
else if (json["depot"]["smallFilesContainer"]["chunks"].size() == 1)
item.md5 = json["depot"]["smallFilesContainer"]["chunks"][0]["md5"].asString();
else
item.md5 = std::string();
items.push_back(item);
}
}
for (unsigned int i = 0; i < json["depot"]["items"].size(); ++i)
{
@ -266,14 +305,23 @@ std::vector<galaxyDepotItem> galaxyAPI::getDepotItemsVector(const std::string& h
item.path = json["depot"]["items"][i]["path"].asString();
item.isDependency = is_dependency;
if (json["depot"]["items"][i].isMember("sfcRef"))
{
item.isInSFC = true;
item.sfc_offset = json["depot"]["items"][i]["sfcRef"]["offset"].asLargestUInt();
item.sfc_size = json["depot"]["items"][i]["sfcRef"]["size"].asLargestUInt();
}
while (Util::replaceString(item.path, "\\", "/"));
for (unsigned int j = 0; j < json["depot"]["items"][i]["chunks"].size(); ++j)
{
Json::Value json_chunk = json["depot"]["items"][i]["chunks"][j];
galaxyDepotItemChunk chunk;
chunk.md5_compressed = json["depot"]["items"][i]["chunks"][j]["compressedMd5"].asString();
chunk.md5_uncompressed = json["depot"]["items"][i]["chunks"][j]["md5"].asString();
chunk.size_compressed = json["depot"]["items"][i]["chunks"][j]["compressedSize"].asLargestUInt();
chunk.size_uncompressed = json["depot"]["items"][i]["chunks"][j]["size"].asLargestUInt();
chunk.md5_compressed = json_chunk["compressedMd5"].asString();
chunk.md5_uncompressed = json_chunk["md5"].asString();
chunk.size_compressed = json_chunk["compressedSize"].asLargestUInt();
chunk.size_uncompressed = json_chunk["size"].asLargestUInt();
chunk.offset_compressed = item.totalSizeCompressed;
chunk.offset_uncompressed = item.totalSizeUncompressed;