Files
ShadowMountPlus/src/sm_install_queue.c
T

467 lines
14 KiB
C

#include "sm_platform.h"
#include "sm_install_queue.h"
#include "sm_types.h"
#include "sm_appdb.h"
#include "sm_config_mount.h"
#include "sm_game_cache.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_manual.h"
#include "sm_runtime.h"
#include "sm_time.h"
#include "sm_title_state.h"
#define INSTALL_WAIT_TIMEOUT_US (300ull * 1000000ull)
#define INSTALL_POLL_INTERVAL_US (2ull * 1000000ull)
#define INSTALL_SUBMIT_RETRY_US (2ull * 1000000ull)
typedef enum {
INSTALL_TRACK_NONE = 0,
INSTALL_TRACK_QUEUED,
INSTALL_TRACK_SUBMITTED,
} install_track_state_t;
typedef struct {
char title_id[MAX_TITLE_ID];
char title_name[MAX_TITLE_NAME];
char source_path[MAX_PATH];
char manual_source_path[MAX_PATH];
uint64_t requested_at_us;
bool has_src_snd0;
bool manual;
install_track_state_t state;
} pending_install_entry_t;
static pending_install_entry_t g_pending_installs[MAX_PENDING];
static uint64_t g_pending_install_poll_due_us = 0;
static uint64_t g_queued_install_submit_due_us = 0;
static int g_tracked_install_count = 0;
static int g_submitted_install_count = 0;
static bool g_queued_install_batch_announced = false;
static bool g_queued_install_submit_failure_notified = false;
static int count_queued_installs(void);
static uint8_t note_pending_install_failure(const pending_install_entry_t *entry);
static void drop_queued_install_entry(pending_install_entry_t *entry);
static bool install_queue_active(void) {
return runtime_config()->app_install_all_enabled || g_tracked_install_count > 0;
}
static pending_install_entry_t *find_pending_install_entry(
const char *title_id) {
if (!title_id || title_id[0] == '\0')
return NULL;
for (int i = 0; i < MAX_PENDING; i++) {
if (g_pending_installs[i].state == INSTALL_TRACK_NONE)
continue;
if (strcmp(g_pending_installs[i].title_id, title_id) == 0)
return &g_pending_installs[i];
}
return NULL;
}
static pending_install_entry_t *reserve_pending_install_entry(
const char *title_id) {
pending_install_entry_t *entry = find_pending_install_entry(title_id);
if (entry)
return entry;
for (int i = 0; i < MAX_PENDING; i++) {
if (g_pending_installs[i].state == INSTALL_TRACK_NONE)
return &g_pending_installs[i];
}
return NULL;
}
static uint64_t next_pending_install_timeout_us(void) {
if (g_submitted_install_count <= 0)
return 0;
uint64_t next_deadline = 0;
for (int i = 0; i < MAX_PENDING; i++) {
const pending_install_entry_t *entry = &g_pending_installs[i];
if (entry->state != INSTALL_TRACK_SUBMITTED || entry->requested_at_us == 0)
continue;
uint64_t deadline_us = entry->requested_at_us + INSTALL_WAIT_TIMEOUT_US;
if (next_deadline == 0 || deadline_us < next_deadline)
next_deadline = deadline_us;
}
return next_deadline;
}
static void schedule_pending_install_poll(uint64_t now_us) {
if (g_submitted_install_count <= 0) {
g_pending_install_poll_due_us = 0;
return;
}
g_pending_install_poll_due_us =
now_us == 0 ? 1 : now_us + INSTALL_POLL_INTERVAL_US;
}
static void schedule_queued_install_submit(uint64_t now_us) {
if (count_queued_installs() <= 0 || g_submitted_install_count > 0) {
g_queued_install_submit_due_us = 0;
return;
}
g_queued_install_submit_due_us = now_us == 0 ? 1 : now_us;
}
static void schedule_queued_install_submit_retry(uint64_t now_us) {
if (count_queued_installs() <= 0 || g_submitted_install_count > 0) {
g_queued_install_submit_due_us = 0;
return;
}
g_queued_install_submit_due_us =
now_us == 0 ? 1 : now_us + INSTALL_SUBMIT_RETRY_US;
}
static void clear_pending_install_entry(pending_install_entry_t *entry) {
if (!entry)
return;
if (entry->state == INSTALL_TRACK_SUBMITTED && g_submitted_install_count > 0)
g_submitted_install_count--;
if (entry->state != INSTALL_TRACK_NONE && g_tracked_install_count > 0)
g_tracked_install_count--;
memset(entry, 0, sizeof(*entry));
if (g_submitted_install_count <= 0)
g_pending_install_poll_due_us = 0;
if (g_tracked_install_count <= 0) {
g_queued_install_submit_due_us = 0;
g_queued_install_batch_announced = false;
g_queued_install_submit_failure_notified = false;
}
}
bool is_title_install_pending(const char *title_id) {
return find_pending_install_entry(title_id) != NULL;
}
void sm_install_forget_pending_title(const char *title_id) {
pending_install_entry_t *entry = find_pending_install_entry(title_id);
if (entry)
clear_pending_install_entry(entry);
}
static void append_batch_install_line(char *message, size_t message_size,
const char *title_name,
const char *title_id, int *shown_count) {
if (!message || message_size == 0 || !title_name || !title_id || !shown_count)
return;
size_t used = strlen(message);
if (used >= message_size)
return;
int written = snprintf(message + used, message_size - used, "\n%s (%s)",
title_name, title_id);
if (written < 0 || (size_t)written >= message_size - used)
return;
(*shown_count)++;
}
bool sm_install_queue_candidate(const scan_candidate_t *candidate,
bool has_src_snd0) {
if (!candidate || candidate->title_id[0] == '\0' ||
candidate->title_name[0] == '\0' || candidate->path[0] == '\0') {
return false;
}
if (!runtime_config()->app_install_all_enabled)
return false;
bool was_queue_empty = (count_queued_installs() <= 0);
pending_install_entry_t *entry =
reserve_pending_install_entry(candidate->title_id);
if (!entry) {
log_debug(" [REG] install queue full, cannot queue %s (%s)",
candidate->title_name, candidate->title_id);
return false;
}
install_track_state_t previous_state = entry->state;
if (previous_state == INSTALL_TRACK_SUBMITTED)
return true;
memset(entry, 0, sizeof(*entry));
(void)strlcpy(entry->title_id, candidate->title_id, sizeof(entry->title_id));
(void)strlcpy(entry->title_name, candidate->title_name,
sizeof(entry->title_name));
(void)strlcpy(entry->source_path, candidate->path, sizeof(entry->source_path));
(void)strlcpy(entry->manual_source_path, candidate->manual_source_path,
sizeof(entry->manual_source_path));
entry->manual = candidate->manual;
entry->has_src_snd0 = has_src_snd0;
entry->state = INSTALL_TRACK_QUEUED;
if (previous_state == INSTALL_TRACK_NONE)
g_tracked_install_count++;
if (was_queue_empty) {
g_queued_install_batch_announced = false;
g_queued_install_submit_failure_notified = false;
}
schedule_queued_install_submit(monotonic_time_us());
return true;
}
static void finalize_pending_install_success(pending_install_entry_t *entry) {
if (!entry)
return;
log_debug(" [REG] Installed: %s (%s)", entry->title_name, entry->title_id);
notify_game_installed_rich(entry->title_id);
if (entry->has_src_snd0) {
int snd0_updates = update_snd0info(entry->title_id);
if (snd0_updates >= 0)
log_debug(" [DB] snd0info updated rows=%d", snd0_updates);
}
cache_game_entry(entry->source_path, entry->title_id, entry->title_name);
if (entry->manual)
sm_manual_note_installed(entry->manual_source_path, entry->title_id,
entry->title_name);
clear_register_attempts(entry->title_id);
clear_failed_mount_attempts(entry->title_id);
clear_pending_install_entry(entry);
}
static void finalize_pending_install_timeout(pending_install_entry_t *entry) {
if (!entry)
return;
log_debug(" [REG] Install timeout after 5 minutes: %s (%s)",
entry->title_name, entry->title_id);
notify_system("Install failed: %s (%s)\nTimed out after 5 minutes",
entry->title_name, entry->title_id);
(void)note_pending_install_failure(entry);
clear_pending_install_entry(entry);
}
static void poll_pending_installs(void) {
if (g_submitted_install_count <= 0)
return;
invalidate_app_db_title_cache();
struct AppDbTitleList app_db_titles = {0};
bool app_db_titles_ready = get_app_db_title_list_cached(&app_db_titles);
uint64_t now_us = monotonic_time_us();
if (!app_db_titles_ready)
log_debug(" [DB] app.db title list unavailable while polling installs");
for (int i = 0; i < MAX_PENDING; i++) {
pending_install_entry_t *entry = &g_pending_installs[i];
if (entry->state != INSTALL_TRACK_SUBMITTED)
continue;
if (app_db_titles_ready &&
app_db_title_list_contains(&app_db_titles, entry->title_id)) {
finalize_pending_install_success(entry);
continue;
}
if (now_us != 0 && entry->requested_at_us != 0 &&
now_us >= entry->requested_at_us &&
now_us - entry->requested_at_us >= INSTALL_WAIT_TIMEOUT_US) {
finalize_pending_install_timeout(entry);
}
}
schedule_pending_install_poll(now_us);
if (g_submitted_install_count == 0)
schedule_queued_install_submit(now_us);
free_app_db_title_list(&app_db_titles);
}
uint64_t sm_install_next_wake_us(uint64_t now_us) {
if (runtime_sleep_mode_active())
return 0;
if (!install_queue_active())
return 0;
if (g_submitted_install_count <= 0)
return g_queued_install_submit_due_us;
uint64_t next_wake_us = next_pending_install_timeout_us();
if (g_pending_install_poll_due_us != 0 &&
(next_wake_us == 0 || g_pending_install_poll_due_us < next_wake_us)) {
next_wake_us = g_pending_install_poll_due_us;
}
if (g_queued_install_submit_due_us != 0 &&
(next_wake_us == 0 || g_queued_install_submit_due_us < next_wake_us)) {
next_wake_us = g_queued_install_submit_due_us;
}
if (next_wake_us == 0)
next_wake_us = now_us == 0 ? 1 : now_us + INSTALL_POLL_INTERVAL_US;
return next_wake_us;
}
void sm_install_poll_pending(void) {
if (runtime_sleep_mode_active())
return;
poll_pending_installs();
}
void sm_install_service_pending(void) {
if (runtime_sleep_mode_active())
return;
if (!install_queue_active())
return;
sm_install_poll_pending();
if (count_queued_installs() > 0 && !sm_install_submit_queued())
sm_install_note_submit_failure();
}
static void log_batch_submit_retry_limit(const pending_install_entry_t *entry) {
uint8_t failed_attempts = get_failed_mount_attempts(entry->title_id);
if (failed_attempts == MAX_FAILED_MOUNT_ATTEMPTS) {
log_debug(" [RETRY] limit reached (%u/%u): %s (%s)",
(unsigned)failed_attempts,
(unsigned)MAX_FAILED_MOUNT_ATTEMPTS, entry->title_name,
entry->title_id);
}
}
static int count_queued_installs(void) {
int queued_count = g_tracked_install_count - g_submitted_install_count;
return queued_count > 0 ? queued_count : 0;
}
static void log_queued_install_batch(void) {
int queued_count = count_queued_installs();
if (queued_count <= 0)
return;
log_debug(" [REG] Batch install request contains %d title(s):", queued_count);
for (int i = 0; i < MAX_PENDING; i++) {
const pending_install_entry_t *entry = &g_pending_installs[i];
if (entry->state != INSTALL_TRACK_QUEUED)
continue;
log_debug(" - %s (%s)", entry->title_name, entry->title_id);
}
}
static void notify_queued_install_batch(void) {
int queued_count = count_queued_installs();
if (queued_count <= 0 || g_queued_install_batch_announced)
return;
char message[3075];
int shown_count = 0;
snprintf(message, sizeof(message), "Batch install queued (%d):", queued_count);
for (int i = 0; i < MAX_PENDING; i++) {
const pending_install_entry_t *entry = &g_pending_installs[i];
if (entry->state != INSTALL_TRACK_QUEUED)
continue;
append_batch_install_line(message, sizeof(message), entry->title_name,
entry->title_id, &shown_count);
}
if (shown_count < queued_count) {
size_t used = strlen(message);
if (used < sizeof(message)) {
(void)snprintf(message + used, sizeof(message) - used, "\n... and %d more",
queued_count - shown_count);
}
}
notify_system_info("%s", message);
g_queued_install_batch_announced = true;
}
static uint8_t note_pending_install_failure(const pending_install_entry_t *entry) {
if (!entry)
return 0;
uint8_t failed_attempts = bump_failed_mount_attempts(entry->title_id);
log_batch_submit_retry_limit(entry);
return failed_attempts;
}
static void drop_queued_install_entry(pending_install_entry_t *entry) {
if (!entry || entry->state != INSTALL_TRACK_QUEUED)
return;
log_debug(" [REG] Dropping queued install after retry limit: %s (%s)",
entry->title_name, entry->title_id);
clear_pending_install_entry(entry);
}
bool sm_install_submit_queued(void) {
if (runtime_sleep_mode_active())
return true;
int queued_count = count_queued_installs();
if (queued_count <= 0)
return true;
if (g_submitted_install_count > 0)
return true;
uint64_t now_us = monotonic_time_us();
if (g_queued_install_submit_due_us != 0 && now_us != 0 &&
now_us < g_queued_install_submit_due_us) {
return true;
}
log_queued_install_batch();
notify_queued_install_batch();
int res = sceAppInstUtilAppInstallAll();
if (res != 0) {
log_debug(" [REG] Batch install request failed: 0x%x", res);
if (!g_queued_install_submit_failure_notified) {
notify_system("Batch install failed for %d app(s).\ncode=0x%08X",
queued_count, (uint32_t)res);
g_queued_install_submit_failure_notified = true;
}
schedule_queued_install_submit_retry(now_us);
return false;
}
g_queued_install_submit_due_us = 0;
g_queued_install_submit_failure_notified = false;
for (int i = 0; i < MAX_PENDING; i++) {
pending_install_entry_t *entry = &g_pending_installs[i];
if (entry->state != INSTALL_TRACK_QUEUED)
continue;
entry->state = INSTALL_TRACK_SUBMITTED;
entry->requested_at_us = now_us;
g_submitted_install_count++;
clear_failed_mount_attempts(entry->title_id);
}
invalidate_app_db_title_cache();
schedule_pending_install_poll(now_us);
return true;
}
void sm_install_note_submit_failure(void) {
for (int i = 0; i < MAX_PENDING; i++) {
pending_install_entry_t *entry = &g_pending_installs[i];
if (entry->state != INSTALL_TRACK_QUEUED)
continue;
if (note_pending_install_failure(entry) >= MAX_FAILED_MOUNT_ATTEMPTS)
drop_queued_install_entry(entry);
}
}