mirror of https://github.com/LIJI32/SameBoy.git
Implement the "plumbing" around thumbnail generation
Only the actual thumbnail generation is left!
This commit is contained in:
parent
e4ceb3d93b
commit
b3cecf2413
|
@ -7,4 +7,5 @@ HexFiend/* linguist-vendored
|
|||
Core/*.h linguist-language=C
|
||||
SDL/*.h linguist-language=C
|
||||
Windows/*.h linguist-language=C
|
||||
XdgThumbnailer/*.h linguist-language=C
|
||||
Cocoa/*.h linguist-language=Objective-C
|
||||
|
|
|
@ -549,7 +549,7 @@ int GB_load_isx(GB_gameboy_t *gb, const char *path)
|
|||
bank = byte;
|
||||
if (byte >= 0x80) {
|
||||
READ(byte);
|
||||
/* TODO: This is just a guess, the docs don't elaborator on how banks > 0xFF are saved,
|
||||
/* TODO: This is just a guess, the docs don't elaborate on how banks > 0xFF are saved,
|
||||
other than the fact that banks >= 80 requires two bytes to store them, and I haven't
|
||||
encountered an ISX file for a ROM larger than 4MBs yet. */
|
||||
bank += byte << 7;
|
||||
|
|
13
Makefile
13
Makefile
|
@ -338,7 +338,7 @@ endif
|
|||
|
||||
cocoa: $(BIN)/SameBoy.app
|
||||
quicklook: $(BIN)/SameBoy.qlgenerator
|
||||
xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer
|
||||
xdg-thumbnailer: $(BIN)/XdgThumbnailer/sameboy-thumbnailer $(BIN)/SDL/cgb_boot_fast.bin
|
||||
sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/mgb_boot.bin $(BIN)/SDL/cgb0_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders $(BIN)/SDL/Palettes
|
||||
bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/mgb_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin
|
||||
tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin
|
||||
|
@ -423,21 +423,21 @@ $(OBJ)/SDL/%.c.o: SDL/%.c
|
|||
|
||||
$(OBJ)/XdgThumbnailer/%.c.o: XdgThumbnailer/%.c
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(GIO_CFLAGS) -c $< -o $@
|
||||
$(CC) $(CFLAGS) $(GIO_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -c $< -o $@
|
||||
# Make sure not to attempt compiling this before generating the interface code.
|
||||
$(OBJ)/XdgThumbnailer/main.c.o: $(OBJ)/XdgThumbnailer/interface.h
|
||||
# Silence warnings for this. It is code generated not by us, so we do not want `-Werror` to break
|
||||
# compilation with some version of the generator and/or compiler.
|
||||
$(OBJ)/XdgThumbnailer/interface.c.o: $(OBJ)/XdgThumbnailer/interface.c
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(GIO_CFLAGS) -w -c $< -o $@
|
||||
$(CC) $(CFLAGS) $(GIO_CFLAGS) -DG_LOG_DOMAIN='"sameboy-thumbnailer"' -w -c $< -o $@
|
||||
$(OBJ)/XdgThumbnailer/interface.c $(OBJ)/XdgThumbnailer/interface.h: XdgThumbnailer/interface.xml
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
gdbus-codegen --c-generate-autocleanup none --c-namespace Thumbnailer --interface-prefix org.freedesktop.thumbnails. --generate-c-code $(OBJ)/XdgThumbnailer/interface $<
|
||||
|
||||
$(OBJ)/OpenDialog/%.c.o: OpenDialog/%.c
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@
|
||||
$(CC) $(CFLAGS) $(SDL_CFLAGS) $(GL_CFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
$(OBJ)/%.c.o: %.c
|
||||
|
@ -688,7 +688,8 @@ ifneq ($(FREEDESKTOP),)
|
|||
ICON_NAMES := apps/sameboy mimetypes/x-gameboy-rom mimetypes/x-gameboy-color-rom
|
||||
ICON_SIZES := 16x16 32x32 64x64 128x128 256x256 512x512
|
||||
ICONS := $(foreach name,$(ICON_NAMES), $(foreach size,$(ICON_SIZES),$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(size)/$(name).png))
|
||||
install: sdl $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop
|
||||
# TODO: install the thumbnailer as well
|
||||
install: sdl xdg-thumbnailer $(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml $(ICONS) FreeDesktop/sameboy.desktop
|
||||
-@$(MKDIR) -p $(dir $(DESTDIR)$(PREFIX))
|
||||
mkdir -p $(DESTDIR)$(DATA_DIR)/ $(DESTDIR)$(PREFIX)/bin/
|
||||
cp -rf $(BIN)/SDL/* $(DESTDIR)$(DATA_DIR)/
|
||||
|
@ -725,7 +726,7 @@ endif
|
|||
|
||||
ios:
|
||||
@$(MAKE) _ios
|
||||
|
||||
|
||||
$(BIN)/SameBoy-iOS.ipa: ios iOS/sideload.entitlements
|
||||
$(MKDIR) -p $(OBJ)/Payload
|
||||
cp -rf $(BIN)/SameBoy-iOS.app $(OBJ)/Payload/SameBoy-iOS.app
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#define G_LOG_DOMAIN "sameboy-thumbnailer"
|
||||
#include "main.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib-object.h>
|
||||
|
@ -8,45 +8,46 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tasks.h"
|
||||
#include "thumbnail.h"
|
||||
|
||||
// Auto-generated via `gdbus-codegen` from `interface.xml`.
|
||||
#include "build/obj/XdgThumbnailer/interface.h"
|
||||
|
||||
static char const *const name_on_bus = "com.github.liji32.sameboy.XdgThumbnailer";
|
||||
static char const *const object_path = "/com/github/liji32/sameboy/XdgThumbnailer";
|
||||
|
||||
/* --- The main work being performed here --- */
|
||||
ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface = NULL;
|
||||
static unsigned max_nb_worker_threads;
|
||||
|
||||
static GThreadPool *thread_pool;
|
||||
|
||||
// The function called by the threads in `thread_pool`.
|
||||
static void generate_thumbnail(void *data, void *user_data)
|
||||
static gboolean handle_queue(void *instance, GDBusMethodInvocation *invocation, char const *uri,
|
||||
char const *mime_type, char const *flavor, gboolean urgent,
|
||||
void *user_data)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
ThumbnailerSpecializedThumbnailer1 *skeleton = instance;
|
||||
g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri,
|
||||
mime_type, flavor, urgent ? "true" : "false");
|
||||
g_assert(skeleton == thumbnailer_interface);
|
||||
|
||||
static gboolean handle_queue(ThumbnailerSpecializedThumbnailer1 *object,
|
||||
GDBusMethodInvocation *invocation, char const *uri, char const *mime_type,
|
||||
char const *flavor, gboolean urgent)
|
||||
{
|
||||
g_info("Received Queue(uri=\"%s\", mime_type=\"%s\", flavor=\"%s\", urgent=%s) request", uri, mime_type, flavor, urgent ? "true" : "false");
|
||||
|
||||
// TODO
|
||||
struct NewTaskInfo task_info = new_task(urgent);
|
||||
start_thumbnailing(task_info.handle, task_info.cancellable, urgent, uri, mime_type);
|
||||
|
||||
thumbnailer_specialized_thumbnailer1_complete_queue(skeleton, invocation, task_info.handle);
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
static gboolean handle_dequeue(ThumbnailerSpecializedThumbnailer1 *object,
|
||||
GDBusMethodInvocation *invocation, unsigned handle)
|
||||
static gboolean handle_dequeue(void *instance, GDBusMethodInvocation *invocation, unsigned handle,
|
||||
void *user_data)
|
||||
{
|
||||
ThumbnailerSpecializedThumbnailer1 *skeleton = instance;
|
||||
g_info("Received Dequeue(handle=%u) request", handle);
|
||||
g_assert(skeleton == thumbnailer_interface);
|
||||
|
||||
// TODO
|
||||
cancel_task(handle);
|
||||
|
||||
return G_DBUS_METHOD_INVOCATION_HANDLED;
|
||||
}
|
||||
|
||||
/* --- "Glue"; or, how the above is orchestrated / wired up --- */
|
||||
|
||||
static GMainLoop *main_loop;
|
||||
|
||||
static void on_bus_acquired(GDBusConnection *connection, const char *name, void *user_data)
|
||||
|
@ -55,16 +56,13 @@ static void on_bus_acquired(GDBusConnection *connection, const char *name, void
|
|||
(void)user_data;
|
||||
g_info("Acquired bus");
|
||||
|
||||
GError *error;
|
||||
|
||||
// Create the interface, and hook up callbacks for when its methods are called.
|
||||
ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface =
|
||||
thumbnailer_specialized_thumbnailer1_skeleton_new();
|
||||
thumbnailer_interface = thumbnailer_specialized_thumbnailer1_skeleton_new();
|
||||
g_signal_connect(thumbnailer_interface, "handle-queue", G_CALLBACK(handle_queue), NULL);
|
||||
g_signal_connect(thumbnailer_interface, "handle-dequeue", G_CALLBACK(handle_dequeue), NULL);
|
||||
|
||||
// Export the interface on the bus.
|
||||
error = NULL;
|
||||
GError *error = NULL;
|
||||
GDBusInterfaceSkeleton *interface = G_DBUS_INTERFACE_SKELETON(thumbnailer_interface);
|
||||
gboolean res = g_dbus_interface_skeleton_export(interface, connection, object_path, &error);
|
||||
g_assert(res);
|
||||
|
@ -108,14 +106,11 @@ static gboolean handle_sigterm(void *user_data)
|
|||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
GError *error;
|
||||
|
||||
// Create the thread pool *before* starting to accept tasks from D-Bus.
|
||||
// Make it non-exclusive so that the number of spawned threads grows dynamically, to consume
|
||||
// fewer system resources when no thumbnails are being generated.
|
||||
thread_pool =
|
||||
g_thread_pool_new(generate_thumbnail, NULL, g_get_num_processors(), FALSE, &error);
|
||||
g_assert_no_error(error); // Creating a non-exclusive thread pool cannot generate errors.
|
||||
max_nb_worker_threads = g_get_num_processors();
|
||||
// unsigned active_worker_threads = 0;
|
||||
// Create the task queue *before* starting to accept tasks from D-Bus.
|
||||
init_tasks();
|
||||
load_boot_roms();
|
||||
// Likewise, create the main loop before then, so it can be aborted even before entering it.
|
||||
main_loop = g_main_loop_new(NULL, FALSE);
|
||||
|
||||
|
@ -127,13 +122,19 @@ int main(int argc, char const *argv[])
|
|||
|
||||
unsigned sigterm_source_id = g_unix_signal_add(SIGTERM, handle_sigterm, NULL);
|
||||
g_main_loop_run(main_loop);
|
||||
gboolean removed =
|
||||
g_source_remove(sigterm_source_id); // This must be done before destroying the main loop.
|
||||
// This must be done before destroying the main loop.
|
||||
gboolean removed = g_source_remove(sigterm_source_id);
|
||||
g_assert(removed);
|
||||
|
||||
g_info("Waiting for outstanding tasks...");
|
||||
g_thread_pool_free(thread_pool, FALSE, TRUE);
|
||||
cleanup_tasks(); // Also waits for any remaining tasks.
|
||||
// "Pedantic" cleanup for Valgrind et al.
|
||||
unload_boot_roms();
|
||||
g_main_loop_unref(main_loop);
|
||||
g_bus_unown_name(owner_id);
|
||||
if (thumbnailer_interface) {
|
||||
g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(thumbnailer_interface));
|
||||
}
|
||||
g_object_unref(thumbnailer_interface);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
// As defined in the thumbnailer spec.
|
||||
enum ErrorCode {
|
||||
ERROR_UNKNOWN_SCHEME_OR_MIME,
|
||||
ERROR_SPECIALIZED_THUMBNAILER_CONNECTION_FAILED,
|
||||
ERROR_INVALID_DATA,
|
||||
ERROR_THUMBNAIILING_THUMBNAIL,
|
||||
ERROR_COULD_NOT_WRITE,
|
||||
ERROR_UNSUPPORTED_FLAVOR,
|
||||
};
|
||||
|
||||
struct _ThumbnailerSpecializedThumbnailer1;
|
||||
extern struct _ThumbnailerSpecializedThumbnailer1 *thumbnailer_interface;
|
|
@ -0,0 +1,102 @@
|
|||
#include "tasks.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
#define URGENT_FLAG (1u << (sizeof(unsigned) * CHAR_BIT - 1)) // The compiler should warn if this shift is out of range.
|
||||
|
||||
struct Tasks {
|
||||
// Note that the lock only applies to the whole array; individual elements may be mutated
|
||||
// in-place just fine by the readers.
|
||||
GRWLock lock;
|
||||
GArray /* of GCancellable* */ *tasks;
|
||||
};
|
||||
static struct Tasks urgent_tasks, tasks;
|
||||
|
||||
static void init_task_list(struct Tasks *task_list)
|
||||
{
|
||||
g_rw_lock_init(&task_list->lock);
|
||||
task_list->tasks = g_array_new(FALSE, FALSE, sizeof(GCancellable *));
|
||||
}
|
||||
void init_tasks(void)
|
||||
{
|
||||
init_task_list(&urgent_tasks);
|
||||
init_task_list(&tasks);
|
||||
}
|
||||
|
||||
static void cleanup_task_list(struct Tasks *task_list) {
|
||||
// TODO: wait for the remaining tasks to end?
|
||||
g_rw_lock_clear(&task_list->lock);
|
||||
g_array_unref(task_list->tasks);
|
||||
}
|
||||
void cleanup_tasks(void)
|
||||
{
|
||||
cleanup_task_list(&urgent_tasks);
|
||||
cleanup_task_list(&tasks);
|
||||
}
|
||||
|
||||
struct NewTaskInfo new_task(gboolean is_urgent)
|
||||
{
|
||||
struct Tasks *task_list = is_urgent ? &urgent_tasks : &tasks;
|
||||
GCancellable **array = (void *)task_list->tasks->data;
|
||||
|
||||
GCancellable *cancellable = g_cancellable_new();
|
||||
|
||||
// We may reallocate the array, so we need a writer lock.
|
||||
g_rw_lock_writer_lock(&task_list->lock);
|
||||
// First, look for a free slot in the array.
|
||||
unsigned index = 0;
|
||||
for (unsigned i = 0; i < task_list->tasks->len; ++i) {
|
||||
if (array[i] == NULL) {
|
||||
array[i] = cancellable;
|
||||
index = i + 1;
|
||||
goto got_slot;
|
||||
}
|
||||
}
|
||||
// We need to allocate a new slot.
|
||||
|
||||
// Each task list cannot contain 0x7FFFFFFF handles, as otherwise bit 7 cannot differentiate
|
||||
// between regular and urgent tasks.
|
||||
// Note that index 0 is invalid, since it's reserved for "no handle", so that's 1 less.
|
||||
if (task_list->tasks->len == URGENT_FLAG - 2) {
|
||||
g_object_unref(cancellable);
|
||||
return (struct NewTaskInfo){.handle = 0};
|
||||
}
|
||||
g_array_append_val(task_list->tasks, cancellable);
|
||||
index = task_list->tasks->len; // We want the new index *plus one*.
|
||||
got_slot:
|
||||
g_rw_lock_writer_unlock(&task_list->lock);
|
||||
|
||||
g_assert_cmpuint(index, !=, 0);
|
||||
g_assert_cmpuint(index, <, URGENT_FLAG);
|
||||
|
||||
return (struct NewTaskInfo){.handle = is_urgent ? (index | URGENT_FLAG) : index,
|
||||
.cancellable = cancellable};
|
||||
}
|
||||
|
||||
void cancel_task(unsigned handle)
|
||||
{
|
||||
struct Tasks *task_list = (handle & URGENT_FLAG) ? &urgent_tasks : &tasks;
|
||||
|
||||
g_rw_lock_reader_lock(&task_list->lock);
|
||||
GCancellable **slot = &((GCancellable **)task_list->tasks->data)[(handle & ~URGENT_FLAG) - 1];
|
||||
GCancellable *cancellable = *slot;
|
||||
*slot = NULL;
|
||||
g_rw_lock_reader_unlock(&task_list->lock);
|
||||
|
||||
g_cancellable_cancel(cancellable);
|
||||
g_object_unref(cancellable);
|
||||
}
|
||||
|
||||
void finished_task(unsigned handle)
|
||||
{
|
||||
struct Tasks *task_list = (handle & URGENT_FLAG) ? &urgent_tasks : &tasks;
|
||||
|
||||
g_rw_lock_reader_lock(&task_list->lock);
|
||||
GCancellable **slot = &((GCancellable **)task_list->tasks->data)[(handle & ~URGENT_FLAG) - 1];
|
||||
GCancellable *cancellable = *slot;
|
||||
*slot = NULL;
|
||||
g_rw_lock_reader_unlock(&task_list->lock);
|
||||
|
||||
g_object_unref(cancellable);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
void init_tasks(void);
|
||||
void cleanup_tasks(void);
|
||||
|
||||
struct NewTaskInfo {
|
||||
unsigned handle;
|
||||
GCancellable *cancellable;
|
||||
};
|
||||
struct NewTaskInfo new_task(gboolean is_urgent);
|
||||
void cancel_task(unsigned handle);
|
||||
void finished_task(unsigned handle);
|
|
@ -0,0 +1,166 @@
|
|||
#include "thumbnail.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Core/gb.h"
|
||||
#include "XdgThumbnailer/tasks.h"
|
||||
#include "main.h"
|
||||
|
||||
#define THUMBNAILING_ERROR_DOMAIN (g_quark_from_static_string("thumbnailing"))
|
||||
|
||||
enum FileKind {
|
||||
KIND_GB,
|
||||
KIND_GBC,
|
||||
KIND_ISX,
|
||||
};
|
||||
|
||||
#define BOOT_ROM_SIZE (0x100 + 0x800) // The two "parts" of it, which are stored contiguously.
|
||||
static char *boot_rom;
|
||||
|
||||
void load_boot_roms(void)
|
||||
{
|
||||
static char const *boot_rom_path = DATA_DIR "/cgb_boot_fast.bin";
|
||||
|
||||
size_t length;
|
||||
GError *error = NULL;
|
||||
g_file_get_contents(boot_rom_path, &boot_rom, &length, &error);
|
||||
|
||||
if (error) {
|
||||
g_error("Error loading boot ROM from \"%s\": %s", boot_rom_path, error->message);
|
||||
// NOTREACHED
|
||||
}
|
||||
else if (length != BOOT_ROM_SIZE) {
|
||||
g_error("Error loading boot ROM from \"%s\": expected to read %d bytes, got %zu",
|
||||
boot_rom_path, BOOT_ROM_SIZE, length);
|
||||
// NOTREACHED
|
||||
}
|
||||
}
|
||||
|
||||
void unload_boot_roms(void) { g_free(boot_rom); }
|
||||
|
||||
struct TaskData {
|
||||
char *contents;
|
||||
size_t length;
|
||||
enum FileKind kind;
|
||||
};
|
||||
|
||||
static void destroy_task_data(void *data)
|
||||
{
|
||||
struct TaskData *task_data = data;
|
||||
|
||||
g_free(task_data->contents);
|
||||
g_slice_free(struct TaskData, task_data);
|
||||
}
|
||||
|
||||
static void generate_thumbnail(GTask *task, void *source_object, void *data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
struct TaskData *task_data = data;
|
||||
|
||||
GB_gameboy_t gb;
|
||||
GB_init(&gb, GB_MODEL_CGB_E);
|
||||
GB_load_boot_rom_from_buffer(&gb, (unsigned char const *)boot_rom, sizeof(boot_rom));
|
||||
|
||||
if (task_data->kind == KIND_ISX) {
|
||||
g_assert_not_reached(); // TODO: implement GB_load_isx_from_buffer
|
||||
}
|
||||
else {
|
||||
GB_load_rom_from_buffer(&gb, (unsigned char const *)task_data->contents, task_data->length);
|
||||
}
|
||||
// TODO
|
||||
|
||||
GB_free(&gb);
|
||||
|
||||
g_task_return_boolean(task, TRUE);
|
||||
g_object_unref(task);
|
||||
}
|
||||
|
||||
// Callback when an async file operation completes.
|
||||
static void on_file_ready(GObject *source_object, GAsyncResult *res, void *user_data)
|
||||
{
|
||||
GFile *file = G_FILE(source_object);
|
||||
GTask *task = user_data;
|
||||
char const *uri = g_task_get_name(task);
|
||||
g_debug("File \"%s\" is done being read", uri);
|
||||
struct TaskData *task_data = g_task_get_task_data(task);
|
||||
|
||||
GError *error = NULL;
|
||||
g_file_load_contents_finish(file, res, &task_data->contents, &task_data->length, NULL, &error);
|
||||
g_object_unref(file);
|
||||
|
||||
if (error) {
|
||||
g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME,
|
||||
"Failed to load URI \"%s\": %s", uri, error->message);
|
||||
g_object_unref(task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_task_return_error_if_cancelled(task)) {
|
||||
g_object_unref(task);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: cap the max number of active threads.
|
||||
g_task_run_in_thread(task, generate_thumbnail);
|
||||
}
|
||||
|
||||
static void on_thumbnailing_end(GObject *source_object, GAsyncResult *res, void *user_data)
|
||||
{
|
||||
// TODO: start a new thread if some task is pending.
|
||||
|
||||
g_assert_null(source_object); // The object that was passed to `g_task_new`.
|
||||
GTask *task = G_TASK(res);
|
||||
g_debug("Ending thumbnailing for \"%s\"", g_task_get_name(task));
|
||||
unsigned handle = GPOINTER_TO_UINT(user_data);
|
||||
char const *uri = g_task_get_name(task);
|
||||
|
||||
GError *error = NULL;
|
||||
if (g_task_propagate_boolean(task, &error)) {
|
||||
g_signal_emit_by_name(thumbnailer_interface, "ready", handle, uri);
|
||||
}
|
||||
else if (!g_cancellable_is_cancelled(g_task_get_cancellable(task))) {
|
||||
// If the task was cancelled, do not emit an error response.
|
||||
g_signal_emit_by_name(thumbnailer_interface, "error", handle, uri, error->code,
|
||||
error->message);
|
||||
}
|
||||
g_signal_emit_by_name(thumbnailer_interface, "finished", handle);
|
||||
|
||||
finished_task(handle);
|
||||
}
|
||||
|
||||
void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent,
|
||||
char const *uri, char const *mime_type)
|
||||
{
|
||||
g_signal_emit_by_name(thumbnailer_interface, "started", handle);
|
||||
|
||||
GTask *task = g_task_new(NULL, cancellable, on_thumbnailing_end, GUINT_TO_POINTER(handle));
|
||||
g_task_set_priority(task, is_urgent ? G_PRIORITY_HIGH : G_PRIORITY_DEFAULT);
|
||||
g_task_set_name(task, uri);
|
||||
|
||||
enum FileKind kind;
|
||||
if (g_strcmp0(mime_type, "application/x-gameboy-color-rom") == 0) {
|
||||
kind = KIND_GBC;
|
||||
}
|
||||
else if (g_strcmp0(mime_type, "application/x-gameboy-rom") == 0) {
|
||||
kind = KIND_GB;
|
||||
}
|
||||
else if (g_strcmp0(mime_type, "application/x-gameboy-isx") == 0) {
|
||||
kind = KIND_ISX;
|
||||
}
|
||||
else {
|
||||
g_task_return_new_error(task, THUMBNAILING_ERROR_DOMAIN, ERROR_UNKNOWN_SCHEME_OR_MIME,
|
||||
"Unsupported MIME type %s", mime_type);
|
||||
g_object_unref(task);
|
||||
return;
|
||||
}
|
||||
|
||||
struct TaskData *task_data = g_slice_new(struct TaskData);
|
||||
task_data->contents = NULL;
|
||||
task_data->kind = kind;
|
||||
g_task_set_task_data(task, task_data, destroy_task_data);
|
||||
|
||||
GFile *file = g_file_new_for_uri(uri);
|
||||
g_file_load_contents_async(file, cancellable, on_file_ready, task);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
void load_boot_roms(void);
|
||||
void unload_boot_roms(void);
|
||||
|
||||
void start_thumbnailing(unsigned handle, GCancellable *cancellable, gboolean is_urgent,
|
||||
char const *uri, char const *mime_type);
|
Loading…
Reference in New Issue