Files
vitaImmich/src/main.c
T
ninoandClaude Sonnet 4.6 07890191cf Add albums support, API layer, and Docker improvements
- Add albums browsing and navigation (src/albums.c, src/app.c)
- Extract API calls into dedicated src/api.c module
- Improve display and UI components
- Update CMakeLists.txt to include new source files
- Refactor Dockerfile for better build process

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-23 14:11:00 -06:00

165 lines
7.0 KiB
C

/*
* vitaImmich — minimal proof-of-concept Immich client for the PS Vita.
*
* Reads server URL + API key from ux0:data/vitaimmich/config.txt and shows
* the library as a scrollable chronological grid (newest first).
* X opens a photo full screen, O goes back. Videos are downloaded to the
* memory card and played with SceAvPlayer (hardware MP4/H.264 decoding).
*
* It also scans the Vita's own camera media (ux0:picture, ux0:video),
* hashes it (SHA1) on a background sync thread, asks the server which files
* are already backed up (POST /api/assets/bulk-upload-check) and can upload
* the rest (POST /api/assets, multipart). Local files are merged into the
* same date-sorted grid with a per-cell status badge (cloud-only / local-
* only / backed-up). SELECT opens a sync overview where uploads are started.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <setjmp.h>
#include <malloc.h>
#include <psp2/appmgr.h>
#include <psp2/audioout.h>
#include <psp2/avplayer.h>
#include <psp2/ctrl.h>
#include <psp2/display.h>
#include <psp2/touch.h>
#include <psp2/gxm.h>
#include <psp2/kernel/sysmem.h>
#include <psp2/io/fcntl.h>
#include <psp2/io/stat.h>
#include <psp2/io/dirent.h>
#include <psp2/rtc.h>
#include <psp2/kernel/processmgr.h>
#include <psp2/kernel/threadmgr.h>
#include <psp2/net/net.h>
#include <psp2/net/netctl.h>
#include <psp2/sysmodule.h>
#include <psp2/ime_dialog.h>
#include <psp2/common_dialog.h>
#include <vita2d.h>
#include <curl/curl.h>
#include <jpeglib.h>
#include <openssl/crypto.h>
#include <openssl/sha.h>
#define JSMN_STATIC
#define JSMN_PARENT_LINKS
#include "jsmn.h"
#define SCREEN_W 960
#define SCREEN_H 544
#define COLS 5 /* rough items per row, for prefetch distances only */
#define ROW_H 200 /* justified-grid row height; widths follow aspect */
#define CELL_PAD 3
#define HEADER_H 40 /* month/year band height in the grid */
#define SEARCH_H 56 /* pinned Immich-style search bar at the top */
#define THUMB_MAX 256 /* decode grid thumbs down to <= this dimension */
#define FULL_MAX 4096 /* GXM texture size limit */
#define PAGE_SIZE 100
#define DATELEN 20 /* "YYYY-MM-DDTHH:MM:SS" + NUL, sortable */
#define APP_VERSION "1.0" /* keep in step with CMakeLists VITA_VERSION */
#define CONFIG_DIR "ux0:data/vitaimmich"
#define CONFIG_PATH CONFIG_DIR "/config.txt"
#define LOG_PATH CONFIG_DIR "/log.txt"
#define AUTOBK_PATH CONFIG_DIR "/autobackup.txt"
#define VIDEO_TMP_PATH CONFIG_DIR "/video.mp4"
/* give curl/jpeg decoding plenty of heap */
int _newlib_heap_size_user = 192 * 1024 * 1024;
static char g_server[512];
static char g_apikey[256];
static char g_serverip[64];
/* email/password login: an empty apikey + these triggers POST /api/auth/login
* at startup; the returned access token is sent as Authorization: Bearer */
static char g_email[128];
static char g_password[128];
static char g_token[256];
/* optional DNS pin ("host:port:ip") for routers without NAT loopback */
static struct curl_slist *g_resolve_list;
/* auto-backup: when set, newly-found local-only photos are queued for upload
* automatically (toggled on the cloud page, persisted to AUTOBK_PATH) */
static int g_autobackup;
/* folders scanned for camera media (config syncdir=, with defaults), and
* a size cap so a movie collection in ux0:video isn't hashed/synced */
static char g_syncdirs[8][192];
static int g_syncdir_count;
static long g_syncmax_mb = 512;
/* vitasdk's freetype is built with bzip2 support but the SDK ships no
* libbz2; we never load bzip2-compressed fonts, so failing stubs satisfy
* the linker */
int BZ2_bzDecompressInit(void *strm, int verbosity, int small);
int BZ2_bzDecompress(void *strm);
int BZ2_bzDecompressEnd(void *strm);
int BZ2_bzDecompressInit(void *strm, int verbosity, int small) { return -1; }
int BZ2_bzDecompress(void *strm) { return -1; }
int BZ2_bzDecompressEnd(void *strm) { return -1; }
static vita2d_pgf *g_font; /* system font, fallback */
static vita2d_font *g_ttf; /* bundled Overpass (Immich's face) */
static vita2d_texture *g_logo; /* bundled Immich logo (app0:logo.png) */
/* cloud status icons (white, from the Immich SVGs): on server / on device /
* on both */
static vita2d_texture *g_ic_server;
static vita2d_texture *g_ic_device;
static vita2d_texture *g_ic_both;
/* server-asset arrays. dynamically grown (doubling) as pages are fetched, so
* the library is bounded only by memory, not a fixed cap. grown on the main
* thread; the thumb worker never indexes these (it reads dedicated request
* buffers), so a realloc here can't dangle a pointer under it. */
static char (*g_asset_ids)[40];
static char (*g_asset_dates)[DATELEN]; /* YYYY-MM-DDTHH:MM:SS */
static float *g_asset_ratio; /* display w/h from exif, 0 = unknown */
static unsigned char *g_asset_rot; /* raw exif orientation (0 = none) */
static unsigned char *g_asset_is_video;
/* set during display rebuild: a backed-up local file matches this server
* asset, so its grid cell shows the green "backed up" badge */
static unsigned char *g_asset_local_backed;
/* 1 = appended only to back a map-cluster gallery (its id/thumb are needed but
* it isn't part of the paged library): shown in the gallery, hidden elsewhere */
static unsigned char *g_asset_hidden;
static int g_asset_count;
static int g_asset_cap;
static int g_next_page = 1; /* 0 = no more pages */
static vita2d_texture **g_thumb;
static int *g_thumb_failed;
static char (*g_tex_err)[160];
/* ------------------------------------------------------------------ */
/* unity build: the rest of the program lives in section files, */
/* #included here in dependency order so they form one translation */
/* unit and share all the file-scope state declared above. There are */
/* no headers/extern decls — each part is just the next slice of what */
/* was one main.c. Only this file is compiled (see CMakeLists.txt). */
/* ------------------------------------------------------------------ */
#include "state.c" /* local media + sync state, array-grow helpers */
#include "draw.c" /* text/logo/error drawing helpers */
#include "config.c" /* config.txt load/save */
#include "net.c" /* curl/openssl, auth headers, HTTP helpers */
#include "jpeg.c" /* libjpeg decode to packed pixels */
#include "api.c" /* immich REST endpoints */
#include "thumbs.c" /* background thumbnail loader + texture pool */
#include "display.c" /* merged server+local timeline + grid layout */
#include "ui.c" /* grid/detail/search/login/cloud screens */
#include "map.c" /* geotag map: markers, clustering, projection */
#include "albums.c" /* album list page + album open/fetch threads */
#include "video.c" /* video download + SceAvPlayer playback */
#include "sync.c" /* local scan/hash/check/upload + sync overview */
#include "app.c" /* main(): init, event loop, threads */