Created
May 27, 2024 22:23
-
-
Save leonkasovan/74dd356cfafd2c0f0030950c1f038013 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ROMs Downloader using ImGui Library | |
// Dhani Novan 25 Mei 2024 Cempaka Putih Jakarta | |
// TODO: | |
// 1. (done) Check target path if exists confirm to continue download or cancel download | |
// Note: now if target path if exists, it won't displayed in result | |
// 2. (done) Clipping for result | |
// 3. Scrape | |
// 4. Build database using lua script | |
#include "imgui.h" | |
#include "imgui_impl_sdl2.h" | |
#include "imgui_impl_sdlrenderer2.h" | |
#include <stdio.h> | |
#include <dirent.h> | |
#include <string> | |
#include <vector> | |
#include <thread> | |
#include <mutex> | |
#include <atomic> | |
#include <iomanip> | |
#include <sstream> | |
#include <fstream> | |
#include <iostream> | |
#include <queue> | |
#include <map> | |
#include <curl/curl.h> | |
#include <SDL.h> | |
#define STB_IMAGE_IMPLEMENTATION | |
#include "stb_image.h" | |
#include "system_scrape_id.h" | |
#if !SDL_VERSION_ATLEAST(2,0,17) | |
#error This backend requires SDL 2.0.17+ because of SDL_RenderGeometry() function | |
#endif | |
#define MAX_LINE 2048 | |
bool LoadTextureFromFile(const char* filename, SDL_Texture** texture_ptr, int& width, int& height, SDL_Renderer* renderer) { | |
int channels; | |
unsigned char* data = stbi_load(filename, &width, &height, &channels, 0); | |
if (data == nullptr) { | |
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason()); | |
return false; | |
} | |
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)data, width, height, channels * 8, channels * width, | |
0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); | |
if (surface == nullptr) { | |
fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError()); | |
return false; | |
} | |
*texture_ptr = SDL_CreateTextureFromSurface(renderer, surface); | |
if ((*texture_ptr) == nullptr) { | |
fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError()); | |
} | |
SDL_FreeSurface(surface); | |
stbi_image_free(data); | |
return true; | |
} | |
// Function to check if a file exists | |
int isFileExists(const char *filename) { | |
FILE *file = fopen(filename, "r"); | |
if (file != NULL) { | |
fclose(file); | |
return 1; // File exists | |
} | |
return 0; // File does not exist | |
} | |
std::string Format(const char* _string, ...) { | |
va_list args; | |
va_list copy; | |
va_start(args, _string); | |
va_copy(copy, args); | |
const int length = vsnprintf(nullptr, 0, _string, copy); | |
va_end(copy); | |
std::string result; | |
result.resize(length); | |
va_copy(copy, args); | |
vsnprintf((char*)result.c_str(), (size_t)length + 1, _string, copy); | |
va_end(copy); | |
va_end(args); | |
return result; | |
} | |
// Split and allocate memory | |
// After using the result, free it with 'free_word' | |
char **split_word(const char *keyword) { | |
char *internal_keyword, *word = NULL, **p, **lword; | |
const char *start; | |
int nword = 0; | |
// Skip space in keyword | |
start = keyword; | |
while (*start == ' ') | |
start++; | |
// Count word | |
internal_keyword = strdup(start); | |
word = strtok(internal_keyword, " "); | |
while (word != NULL) { | |
nword++; | |
word = strtok(NULL, " "); // Subsequent calls to strtok() use NULL as the input, which tells the function to continue splitting from where it left off | |
} | |
free(internal_keyword); | |
// Allocate list of word | |
internal_keyword = strdup(start); | |
lword = (char **)malloc(sizeof(char *) * (nword + 1)); | |
p = lword; | |
word = strtok(internal_keyword, " "); | |
while (word != NULL) { | |
*p = word; | |
word = strtok(NULL, " "); // Subsequent calls to strtok() use NULL as the input, which tells the function to continue splitting from where it left off | |
p++; | |
} | |
*p = NULL; | |
return lword; | |
} | |
// Free memory used by internal_keyword and list of word | |
void free_word(char **lword) { | |
free(*lword); // free internal_keyword (1st entry) | |
free(lword); // free list of word | |
} | |
// Find keyword(char **) in string line | |
int find_keyword2(char *line, char **lword) { | |
char **p = lword; | |
char *word, *in_line; | |
int found = 1; | |
in_line = strdup(line); | |
SDL_strlwr(in_line); // make 'input line' lower | |
while (*p) { | |
word = *p; | |
if (*word == '-') { | |
word++; | |
if (strstr(in_line, word)) { | |
found = found & 0; | |
break; | |
}else{ | |
found = found & 1; | |
} | |
}else{ | |
if (strstr(in_line, word)) { | |
found = found & 1; | |
}else{ | |
found = found & 0; | |
break; | |
} | |
} | |
p++; | |
} | |
free(in_line); | |
return found; | |
} | |
char *my_strtok(char *str, char delimiter) { | |
static char *token; // Static variable to keep track of the current token | |
if (str != NULL) { | |
token = str; // Initialize or reset the token if str is not NULL | |
} else if (token == NULL || *token == '\0') { | |
return NULL; // No more tokens | |
} | |
// Find the next occurrence of the delimiter in the current token | |
char *delimiterPtr = strchr(token, delimiter); | |
if (delimiterPtr != NULL) { | |
*delimiterPtr = '\0'; // Replace delimiter with '\0' to terminate the current substring | |
char *result = token; | |
token = delimiterPtr + 1; // Move to the next character after the delimiter | |
return result; | |
} else { | |
// If no more delimiters are found in the current token | |
char *result = token; | |
token = NULL; // Signal the end of tokens | |
return result; | |
} | |
} | |
#define ROMS_PATH "/home/deck/Emulation/roms" | |
#define DATA_PATH "/home/deck/Projects/imgui/examples/roms_downloader" | |
struct URLSystem { | |
std::string url; | |
std::string system; | |
// Constructor | |
URLSystem(const std::string& u, const std::string& s) : url(u), system(s) {} | |
}; | |
class tSearchResult { | |
public: | |
tSearchResult(const std::string& system, const std::string& title, const std::string& url, const std::string& desc): system(system), title(title), url(url), desc(desc) {} | |
std::string system; | |
std::string title; | |
std::string url; | |
std::string desc; | |
}; | |
int SearchCSV(std::vector<tSearchResult>& result, const char *csv_fname, char **lword, unsigned int start_no) { | |
FILE *f; | |
char line[MAX_LINE]; | |
char target[MAX_LINE]; | |
char *category = NULL, *base_url = NULL, *p; | |
char *a_name, *a_title, *a_desc; | |
f = fopen(csv_fname, "r"); | |
if (!f) { | |
return start_no; | |
} | |
fgets(line, MAX_LINE, f); // Line 1: #category=snes\r\n | |
if ( (p = strchr(line, '=') )) { | |
category = strdup(p+1); | |
p = strrchr(category, '\r'); if (p) *p = '\0'; // replace \r to \0 | |
p = strrchr(category, '\n'); if (p) *p = '\0'; // replace \n to \0 | |
} | |
fgets(line, MAX_LINE, f); // Line 2: #url=https://archive.org/download/cylums or #url=https://archive.org/download/cylums_collection.zip/ | |
if ((p = strchr(line, '='))) { | |
base_url = strdup(p + 1); | |
p = strrchr(base_url, '\r'); // windows end of line | |
if (p) { | |
*p = '\0'; // replace \r to \0 | |
if (*(p-1) == '/') *(p-1) = '\0'; // remove trailing / | |
}else{ | |
p = strrchr(base_url, '\n'); | |
*p = '\0'; // replace \n to \0 | |
if (*(p-1) == '/') *(p-1) = '\0'; // remove trailing / | |
} | |
} | |
if (!base_url) { | |
fclose(f); | |
if (category) free(category); | |
return start_no; | |
} | |
while (fgets(line, MAX_LINE, f)) { // Process next line: the real csv data | |
a_name = my_strtok(line, '|'); | |
a_title = my_strtok(NULL, '|'); | |
a_desc = my_strtok(NULL, '|'); | |
// Use snprintf to safely concatenate str1 and str2 into target | |
snprintf(target, MAX_LINE, "%s/%s/%s", ROMS_PATH, category, a_name); | |
// Ensure null-termination and truncate if overflow | |
target[MAX_LINE - 1] = '\0'; | |
// add to list if keyword is found in title and rom file doesn't exists | |
// if (find_keyword2(a_title, lword) && !isFileExists(target)) { | |
if (find_keyword2(a_title, lword)) { | |
start_no++; | |
// remove trailing end-of-line in a_desc | |
p = strrchr(a_desc, '\r'); // windows end of line | |
if (p) { | |
*p = '\0'; // replace \r to \0 | |
}else{ | |
p = strrchr(a_desc, '\n'); | |
if (p) *p = '\0'; // replace \n to \0 | |
} | |
snprintf(target, MAX_LINE, "%s/%s", base_url, a_name); | |
result.emplace_back(category, a_title, target, a_desc); | |
} | |
} | |
fclose(f); | |
if (category) free(category); | |
if (base_url) free(base_url); | |
return start_no; | |
} | |
// Global Variable | |
std::mutex downloadMutex_1; | |
bool downloadDone_1 = true; | |
float downloadProgress_1 = 0.0f; | |
curl_off_t downloadTotalSize = 0; | |
curl_off_t downloadedSize = 0; | |
std::string downloadFilename; | |
CURL* g_curl = NULL; | |
struct curl_slist *headers = NULL; | |
std::string decodeUrl(const std::string& encodedUrl) { | |
std::ostringstream decoded; | |
std::size_t length = encodedUrl.length(); | |
for (std::size_t i = 0; i < length; ++i) { | |
if (encodedUrl[i] == '%' && i + 2 < length) { | |
std::istringstream hexStream(encodedUrl.substr(i + 1, 2)); | |
int hexValue; | |
if (hexStream >> std::hex >> hexValue) { | |
decoded << static_cast<char>(hexValue); | |
i += 2; | |
} else { | |
decoded << '%'; | |
} | |
} else if (encodedUrl[i] == '+') { | |
decoded << ' '; | |
} else { | |
decoded << encodedUrl[i]; | |
} | |
} | |
return decoded.str(); | |
} | |
std::string getFileName(const std::string& url) { | |
// Find the last slash in the URL | |
std::size_t lastSlashPos = url.find_last_of('/'); | |
// Extract the substring after the last slash | |
if (lastSlashPos != std::string::npos) { | |
return url.substr(lastSlashPos + 1); | |
} | |
// If no slash is found, return the whole URL (unlikely case for a valid URL) | |
return url; | |
} | |
// Write callback to write received data to a file | |
size_t WriteFileCallback(void* ptr, size_t size, size_t nmemb, void* stream) { | |
std::ofstream* out = static_cast<std::ofstream*>(stream); | |
size_t totalSize = size * nmemb; | |
out->write(static_cast<const char*>(ptr), totalSize); | |
return totalSize; | |
} | |
// Callback function to write data to a std::string | |
size_t WriteStringCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { | |
size_t totalSize = size * nmemb; | |
userp->append((char*)contents, totalSize); | |
return totalSize; | |
} | |
// Progress callback to display progress | |
int ProgressCallback(void* ptr, curl_off_t totalToDownload, curl_off_t nowDownloaded, curl_off_t, curl_off_t) { | |
if (totalToDownload > 0) { | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadProgress_1 = ((float)nowDownloaded / (float)totalToDownload); | |
downloadTotalSize = totalToDownload; | |
downloadedSize = nowDownloaded; | |
} | |
return 0; | |
} | |
// Function that request http from url and return the content as string | |
std::string httpRequestAsString(CURL* curl, const std::string& source_url) { | |
std::string content = ""; | |
// CURLcode res; | |
curl_easy_setopt(curl, CURLOPT_URL, source_url.c_str()); | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); | |
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); | |
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1); | |
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Opera/9.80 (J2ME/MIDP; Opera Mini/7.1.32052/29.3417; U; en) Presto/2.8.119 Version/11.10"); | |
curl_easy_setopt(curl, CURLOPT_HTTPGET, (long)1); | |
// Set write callback | |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback); | |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &content); | |
// Perform the request | |
curl_easy_perform(curl); | |
return content; | |
} | |
// Callback function to process headers | |
size_t header_callback(char *buffer, size_t size, size_t nitems, void *userdata) { | |
size_t numbytes = size * nitems; | |
curl_off_t *file_size = (curl_off_t *)userdata; | |
// Check if the header contains "Content-Length" | |
printf(buffer); | |
if (strncasecmp(buffer, "Content-Length:", 15) == 0) { | |
// Parse the file size from the header | |
*file_size = strtoll(buffer + 15, NULL, 10); | |
} | |
return numbytes; | |
} | |
curl_off_t urlFilesize(CURL *curl, const char *url) { | |
CURLcode res; | |
curl_off_t file_size = -1; | |
// Set the URL | |
curl_easy_setopt(curl, CURLOPT_URL, url); | |
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); | |
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1); | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); | |
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Opera/9.80 (J2ME/MIDP; Opera Mini/7.1.32052/29.3417; U; en) Presto/2.8.119 Version/11.10"); | |
curl_easy_setopt(curl, CURLOPT_HTTPGET, (long)1); | |
// Set the header callback function | |
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); | |
// Pass the file_size variable to the callback function | |
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &file_size); | |
// Only fetch the headers | |
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); | |
// Perform the request | |
res = curl_easy_perform(curl); | |
if(res != CURLE_OK) { | |
return -1; | |
} else { | |
return file_size; | |
} | |
} | |
// Thread function that scrape based on url | |
void scrapeRequest(const std::string& source_url,const std::string& system){ | |
CURL* curl; | |
// CURLcode res; | |
curl_off_t file_size; | |
std::string file_name; | |
std::string scrape_url = "https://api.screenscraper.fr/api2/jeuInfos.php?output=json&devid=recalbox&devpassword=C3KbyjX8PKsUgm2tu53y&softname=Emulationstation-Recalbox-9.1&ssid=test&sspassword=test&romtype=rom"; | |
// &systemeid=75&romnom=ladykill.zip&romtaille=1413431 | |
curl_global_init(CURL_GLOBAL_DEFAULT); | |
curl = curl_easy_init(); | |
if (!curl) return; | |
file_size = urlFilesize(curl, source_url.c_str()); | |
file_name = getFileName(source_url); | |
scrape_url.append("&romtaille="); | |
scrape_url.append(std::to_string(file_size)); | |
scrape_url.append("&romnom="); | |
scrape_url.append(file_name); | |
scrape_url.append("&systemeid="); | |
scrape_url.append(std::to_string(scrapeId[system])); | |
curl_easy_cleanup(curl); | |
curl_global_cleanup(); | |
} | |
// Thread function that download from url and save to target_path | |
void httpRequest(const std::string& source_url,const std::string& target_path) { | |
CURL* curl; | |
CURLcode res; | |
std::ofstream outFile; | |
// std::string dest_path; | |
std::int64_t fileSize; | |
printf("source_url: %s\n",source_url.c_str()); | |
// dest_path = std::string(ROMS_PATH) + "/" + system + "/" + getFileName(decodeUrl(source_url)); | |
printf("target_path: %s\n", target_path.c_str()); | |
// Check file size | |
std::ifstream inFile(target_path, std::ios::binary | std::ios::ate); | |
if (!inFile.is_open()) { | |
fileSize = -1; | |
}else{ | |
fileSize = static_cast<std::int64_t>(inFile.tellg()); | |
inFile.close(); | |
} | |
if (fileSize == -1){ | |
// Open new file for writing | |
outFile.open(target_path, std::ios::binary); | |
printf("Create new target_path\n"); | |
}else{ | |
// Open a file for append | |
outFile.open(target_path, std::ios::binary | std::ios::app); | |
printf("Open target_path and continue from %ld\n", fileSize); | |
} | |
if (!outFile) { | |
std::cerr << "Error: Unable to open file " << target_path << std::endl; | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadDone_1 = true; | |
downloadProgress_1 = 0.0f; | |
downloadTotalSize = 0; | |
downloadedSize = 0; | |
} | |
return; | |
} | |
curl_global_init(CURL_GLOBAL_DEFAULT); | |
curl = curl_easy_init(); | |
if (curl) { | |
// Set URL | |
curl_easy_setopt(curl, CURLOPT_URL, source_url.c_str()); | |
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); | |
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); | |
curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1); | |
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Opera/9.80 (J2ME/MIDP; Opera Mini/7.1.32052/29.3417; U; en) Presto/2.8.119 Version/11.10"); | |
curl_easy_setopt(curl, CURLOPT_HTTPGET, (long)1); | |
// Set write callback | |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteFileCallback); | |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &outFile); | |
if (fileSize == -1){ | |
curl_easy_setopt(curl, CURLOPT_RANGE, NULL); | |
// curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)0); | |
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)0); | |
}else{ | |
// curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)fileSize); | |
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, (curl_off_t)fileSize); | |
} | |
// Set progress callback | |
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback); | |
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, NULL); | |
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); | |
// Perform the request | |
res = curl_easy_perform(curl); | |
// Check for errors | |
if (res != CURLE_OK) { | |
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; | |
curl_easy_cleanup(curl); | |
curl_global_cleanup(); | |
outFile.close(); | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadDone_1 = true; | |
downloadProgress_1 = 0.0f; | |
downloadTotalSize = 0; | |
downloadedSize = 0; | |
} | |
return; | |
} | |
// Cleanup | |
curl_easy_cleanup(curl); | |
} | |
curl_global_cleanup(); | |
outFile.close(); | |
// Download completed | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadDone_1 = true; | |
downloadProgress_1 = 0.0f; | |
downloadTotalSize = 0; | |
downloadedSize = 0; | |
} | |
return; | |
} | |
// Main code | |
int main(int, char**) | |
{ | |
// std::map<std::string, int> scrapeId{{"megadrive",1},{"snes",4}}; | |
// scrapeId[genesis]=1; | |
// scrapeId["megadrive"]=1; | |
// scrapeId["snes"]=4; | |
// scrapeId["psx"] = 57; | |
// scrapeId["ps2"]=58; | |
// scrapeId["atomiswave"]=53; | |
// Setup SDL | |
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) | |
{ | |
printf("Error: %s\n", SDL_GetError()); | |
return -1; | |
} | |
// From 2.0.18: Enable native IME. | |
#ifdef SDL_HINT_IME_SHOW_UI | |
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); | |
#endif | |
// Create window with SDL_Renderer graphics context | |
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); | |
SDL_Window* window = SDL_CreateWindow("ROMs Downloader", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, window_flags); | |
if (window == nullptr) | |
{ | |
printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); | |
return -1; | |
} | |
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); | |
if (renderer == nullptr) | |
{ | |
SDL_Log("Error creating SDL_Renderer!"); | |
return 0; | |
} | |
//SDL_RendererInfo info; | |
//SDL_GetRendererInfo(renderer, &info); | |
//SDL_Log("Current SDL_Renderer: %s", info.name); | |
SDL_Texture* my_texture; | |
int my_image_width, my_image_height; | |
bool ret = LoadTextureFromFile("media/MyImage01.jpg", &my_texture, my_image_width, my_image_height, renderer); | |
if (!ret){ | |
printf("Error: LoadTextureFromFile\n"); | |
return -1; | |
} | |
// Setup Dear ImGui context | |
IMGUI_CHECKVERSION(); | |
ImGui::CreateContext(); | |
ImGuiIO& io = ImGui::GetIO(); (void)io; | |
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls | |
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls | |
// Setup Dear ImGui style | |
// ImGui::StyleColorsDark(); | |
ImGui::StyleColorsLight(); | |
// Setup Platform/Renderer backends | |
ImGui_ImplSDL2_InitForSDLRenderer(window, renderer); | |
ImGui_ImplSDLRenderer2_Init(renderer); | |
// Load Fonts | |
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. | |
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. | |
// - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). | |
// - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. | |
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. | |
// - Read 'docs/FONTS.md' for more instructions and details. | |
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! | |
//io.Fonts->AddFontDefault(); | |
//io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); | |
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); | |
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f); | |
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); | |
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); | |
//IM_ASSERT(font != nullptr); | |
// Our state | |
// bool show_demo_window = true; | |
// bool show_another_window = false; | |
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); | |
curl_global_init(CURL_GLOBAL_DEFAULT); | |
g_curl = curl_easy_init(); | |
if (g_curl){ | |
curl_easy_setopt(g_curl, CURLOPT_TCP_KEEPALIVE, 1L); | |
// curl_easy_setopt(g_curl, CURLOPT_TCP_KEEPIDLE, 120L); | |
// curl_easy_setopt(g_curl, CURLOPT_TCP_KEEPINTVL, 60L); | |
headers = curl_slist_append(headers, "Connection: keep-alive"); | |
curl_easy_setopt(g_curl, CURLOPT_HTTPHEADER, headers); | |
} | |
// Main loop | |
bool done = false; | |
while (!done) | |
{ | |
// Poll and handle events (inputs, window resize, etc.) | |
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. | |
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. | |
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. | |
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. | |
SDL_Event event; | |
while (SDL_PollEvent(&event)) | |
{ | |
ImGui_ImplSDL2_ProcessEvent(&event); | |
if (event.type == SDL_QUIT) | |
done = true; | |
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) | |
done = true; | |
} | |
// Start the Dear ImGui frame | |
ImGui_ImplSDLRenderer2_NewFrame(); | |
ImGui_ImplSDL2_NewFrame(); | |
ImGui::NewFrame(); | |
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. | |
{ | |
static bool show_app = true; | |
static char rom_name[1024] = "speed"; | |
const char* systems[] = { "all", "psx", "ps2", "ps3", "psvita", "snes", "nds", "3ds", "wii", "gba", "arcade", "fbneo" }; | |
static int system_current = 0; | |
static char query[1030] = ""; | |
static std::vector<tSearchResult> result; | |
static std::vector<URLSystem> downloadQueue; | |
char **lword; | |
static unsigned int n_found = 0; | |
static bool use_work_area = true; | |
const ImGuiViewport* viewport = ImGui::GetMainViewport(); | |
ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos); | |
ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size); | |
ImGui::Begin("Main", &show_app, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); // Create a window and append into it. | |
ImGui::Text("Query for searching rom:"); // Display some text (you can use a format strings too) | |
ImGui::InputText("Rom's Name", rom_name, IM_ARRAYSIZE(rom_name)); | |
ImGui::Combo("System", &system_current, systems, IM_ARRAYSIZE(systems)); | |
if (ImGui::Button("Search")) { | |
DIR *dir1; | |
struct dirent *entry; | |
char fullpath[MAX_LINE]; | |
result.clear(); | |
n_found = 0; | |
if (system_current){ | |
snprintf(query, 1030, "%s: %s", systems[system_current], rom_name); | |
}else{ | |
strcpy(query, rom_name); | |
} | |
dir1 = opendir(DATA_PATH); | |
if (dir1){ | |
lword = split_word(query); | |
entry = readdir(dir1); | |
do { | |
if (strstr(entry->d_name, ".csv")) { | |
sprintf(fullpath, DATA_PATH"/%s", entry->d_name); | |
n_found = SearchCSV(result, fullpath, lword, n_found); | |
// n_found = SearchCSV(result, "/home/deck/Projects/imgui/examples/roms_downloader/cylums_snes_rom_collection.csv", lword, n_found); | |
} | |
} while ((entry = readdir(dir1)) != NULL); | |
free_word(lword); | |
closedir(dir1); | |
} | |
} | |
if (query[0]) { | |
ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable; | |
ImVec2 outer_size = ImVec2(0.0f, 0.0f); | |
ImGui::Text("Found = %d", n_found); | |
if (n_found > 15) { | |
flags = flags | ImGuiTableFlags_ScrollY; | |
outer_size = ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 15); | |
} | |
if (ImGui::BeginTable("Result_Table", 3, flags, outer_size)) { | |
int row = 0; | |
static std::string dest_path; | |
// Set up columns | |
ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible | |
ImGui::TableSetupColumn("[System]Title", ImGuiTableColumnFlags_WidthStretch); | |
ImGui::TableSetupColumn("Description (Size/Genre/Publisher)", ImGuiTableColumnFlags_WidthStretch); | |
ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_WidthFixed, 40.0f); | |
// Add the headers row | |
ImGui::TableHeadersRow(); | |
for (const auto& res_item : result) { | |
ImGui::TableNextRow(); | |
ImGui::TableNextColumn(); | |
ImGui::Text("%d.[%s] %s", row+1, res_item.system.c_str(), res_item.title.c_str()); | |
ImGui::TableNextColumn(); | |
ImGui::Text(res_item.desc.c_str()); | |
ImGui::TableNextColumn(); | |
ImGui::PushID(row); // Push a unique identifier for each button based on row index | |
if (ImGui::SmallButton("Get")) { | |
// auto filesize = urlFilesize(g_curl, res_item.url.c_str()); | |
// printf("URL filesize = %ld\n", filesize); | |
dest_path = std::string(ROMS_PATH) + "/" + res_item.system + "/" + getFileName(decodeUrl(res_item.url)); | |
if (isFileExists(dest_path.c_str())){ | |
printf("%s already exists.\n", dest_path.c_str()); | |
ImGui::OpenPopup("Resume_download"); | |
}else{ | |
if (downloadDone_1){ // No Queue | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadDone_1 = false; | |
downloadProgress_1 = 0.0f; | |
downloadTotalSize = 0; | |
downloadedSize = 0; | |
downloadFilename = getFileName(decodeUrl(res_item.url)); | |
} | |
std::thread thread_1(httpRequest, res_item.url, dest_path); | |
thread_1.detach(); | |
}else{ // Queue download | |
downloadQueue.emplace(downloadQueue.begin(), res_item.url, dest_path); | |
} | |
} | |
} | |
ImGui::SameLine(); | |
if (ImGui::SmallButton("...")) { | |
// std::cout << httpRequestAsString(g_curl, res_item.url.c_str()); | |
std::cout << httpRequestAsString(g_curl, "https://api.screenscraper.fr/api2/ssuserInfos.php?devid=xxx&devpassword=yyy&softname=zzz&output=xml&ssid=leonkasovan&sspassword=rikadanR1"); | |
} | |
if (ImGui::BeginPopupModal("Resume_download", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { | |
ImGui::Text("%s already exists.\nResume download?", dest_path.c_str()); | |
ImGui::Separator(); | |
if (ImGui::Button("Yes")) { | |
ImGui::CloseCurrentPopup(); | |
if (downloadDone_1){ // No Queue | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadDone_1 = false; | |
downloadProgress_1 = 0.0f; | |
downloadTotalSize = 0; | |
downloadedSize = 0; | |
downloadFilename = getFileName(decodeUrl(res_item.url)); | |
} | |
std::thread thread_1(httpRequest, res_item.url, dest_path); | |
thread_1.detach(); | |
}else{ // Queue download | |
downloadQueue.emplace(downloadQueue.begin(), res_item.url, dest_path); | |
} | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("No")) { | |
ImGui::CloseCurrentPopup(); | |
} | |
ImGui::EndPopup(); | |
} | |
ImGui::PopID(); // Pop the unique identifier | |
row++; | |
} | |
ImGui::EndTable(); | |
} | |
} | |
if (!downloadQueue.empty()){ | |
ImGui::Text("Download Queue: %ld", downloadQueue.size()); | |
if (ImGui::BeginTable("Download_Queue", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { | |
// Set up columns | |
ImGui::TableSetupColumn("System", ImGuiTableColumnFlags_WidthStretch); | |
ImGui::TableSetupColumn("ROM", ImGuiTableColumnFlags_WidthStretch); | |
ImGui::TableHeadersRow(); | |
for (const auto& item : downloadQueue) { | |
ImGui::TableNextRow(); | |
ImGui::TableNextColumn(); | |
ImGui::Text(item.system.c_str()); | |
ImGui::TableNextColumn(); | |
ImGui::Text(getFileName(decodeUrl(item.url)).c_str()); | |
} | |
} | |
ImGui::EndTable(); | |
if (downloadDone_1){ // there is no downloading = downloadDone then request in the download queue | |
auto item = downloadQueue.back(); | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
downloadDone_1 = false; | |
downloadProgress_1 = 0.0f; | |
downloadTotalSize = 0; | |
downloadedSize = 0; | |
downloadQueue.pop_back(); | |
} | |
std::thread thread_1(httpRequest, item.url, item.system); | |
thread_1.detach(); | |
} | |
} | |
if (!downloadDone_1){ | |
// ImVec2 size = ImVec2(-1, 0); | |
{ | |
std::lock_guard<std::mutex> lock(downloadMutex_1); | |
ImGui::Text("Filename: %s", downloadFilename.c_str()); | |
ImGui::SameLine(); | |
ImGui::Text("Filesize: %ld byte", downloadTotalSize); | |
ImGui::SameLine(); | |
ImGui::Text("Downloaded: %ld byte", downloadedSize); | |
ImGui::ProgressBar(downloadProgress_1); // Render the progress bar | |
} | |
} | |
// ImGui::Image((void*) my_texture, ImVec2(my_image_width, my_image_height)); | |
// ImGui::Image((void*) my_texture, ImVec2(100, 50)); | |
ImGui::End(); | |
} | |
// Rendering | |
ImGui::Render(); | |
SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); | |
SDL_SetRenderDrawColor(renderer, (Uint8)(clear_color.x * 255), (Uint8)(clear_color.y * 255), (Uint8)(clear_color.z * 255), (Uint8)(clear_color.w * 255)); | |
SDL_RenderClear(renderer); | |
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer); | |
SDL_RenderPresent(renderer); | |
} | |
// Cleanup | |
curl_slist_free_all(headers); | |
if (g_curl) curl_easy_cleanup(g_curl); | |
curl_global_cleanup(); | |
ImGui_ImplSDLRenderer2_Shutdown(); | |
ImGui_ImplSDL2_Shutdown(); | |
ImGui::DestroyContext(); | |
SDL_DestroyTexture(my_texture); | |
SDL_DestroyRenderer(renderer); | |
SDL_DestroyWindow(window); | |
SDL_Quit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment