From 9090168bca2441ecdf207c8f0fd969a23126e052 Mon Sep 17 00:00:00 2001 From: Jurn Wubben Date: Sat, 27 Sep 2025 14:07:16 +0200 Subject: [PATCH] now stores pics to sd also and allows configuring endpoint from sd --- src/main.c | 144 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 113 insertions(+), 31 deletions(-) diff --git a/src/main.c b/src/main.c index 5e3fa2c..b964fe5 100644 --- a/src/main.c +++ b/src/main.c @@ -16,15 +16,15 @@ #define HEIGHT 240 #define FB_SIZE (WIDTH * HEIGHT * 3) #define STORAGE_DIR "sdmc:/printcam" -#define ENDPOINT_PATH STORAGE_DIR "/printcam/endpoint.txt" -#define API_ENDPOINT_DEFAULT "http://192.168.1.113:8000" +#define ENDPOINT_PATH STORAGE_DIR "/endpoint.txt" +#define API_ENDPOINT_DEFAULT "https://example.com/upload" #define JPEG_QUALITY 100 static volatile bool exitRequested = false; -static Thread camThread; -static Handle ready[2]; -static u8 *camBuf[2]; -static u8 *rgbBuf; +static Thread camThread = NULL; +static Handle ready[2] = {0, 0}; +static u8 *camBuf[2] = {NULL, NULL}; +static u8 *rgbBuf = NULL; static volatile u8 writeIdx = 0, dispIdx = 0; static volatile bool camera_reinit = false; static volatile int camera_index = 0; @@ -38,6 +38,23 @@ static bool ensure_storage_dir(void) { return errno == EEXIST; } +static bool ensure_default_endpoint_file(void) { + FILE *f = fopen(ENDPOINT_PATH, "r"); + if (f) { + fclose(f); + return true; + } + f = fopen(ENDPOINT_PATH, "w"); + if (!f) + return false; + if (fprintf(f, "%s\n", API_ENDPOINT_DEFAULT) < 0) { + fclose(f); + return false; + } + fclose(f); + return true; +} + static int load_endpoint_from_sd(void) { FILE *f = fopen(ENDPOINT_PATH, "r"); if (!f) @@ -157,48 +174,105 @@ Result http_post_file(const char *url, const char *filename) { fclose(f); return -1; } - long size = ftell(f); - if (size < 0) { + long fsize = ftell(f); + if (fsize < 0) { fclose(f); return -1; } rewind(f); - u8 *buf = malloc((size_t)size); - if (!buf) { + u8 *filebuf = malloc((size_t)fsize); + if (!filebuf) { fclose(f); return -1; } - if (fread(buf, 1, (size_t)size, f) != (size_t)size) { - free(buf); + if (fread(filebuf, 1, (size_t)fsize, f) != (size_t)fsize) { + free(filebuf); fclose(f); return -1; } fclose(f); + char boundary[64]; + srand((unsigned)time(NULL)); + snprintf(boundary, sizeof(boundary), "----printcam%08x", rand()); + const char *filename_only = filename; + const char *p = strrchr(filename, '/'); + if (p) + filename_only = p + 1; + char preamble[512]; + int pre_len = snprintf( + preamble, sizeof(preamble), + "--%s\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n" + "Content-Type: image/jpeg\r\n\r\n", + boundary, filename_only); + const char *epilogue_fmt = "\r\n--%s--\r\n"; + int epilogue_len = snprintf(NULL, 0, epilogue_fmt, boundary); + char *epilogue = malloc((size_t)epilogue_len + 1); + if (!epilogue) { + free(filebuf); + return -1; + } + snprintf(epilogue, (size_t)epilogue_len + 1, epilogue_fmt, boundary); + size_t multipart_size = + (size_t)pre_len + (size_t)fsize + (size_t)epilogue_len; + u8 *multipart_buf = malloc(multipart_size); + if (!multipart_buf) { + free(filebuf); + free(epilogue); + return -1; + } + memcpy(multipart_buf, preamble, (size_t)pre_len); + memcpy(multipart_buf + pre_len, filebuf, (size_t)fsize); + memcpy(multipart_buf + pre_len + fsize, epilogue, (size_t)epilogue_len); + free(filebuf); + free(epilogue); ret = httpcOpenContext(&context, HTTPC_METHOD_POST, url, 0); if (ret != 0) { - free(buf); + free(multipart_buf); return ret; } httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); httpcSetKeepAlive(&context, HTTPC_KEEPALIVE_ENABLED); + char content_type_header[128]; + snprintf(content_type_header, sizeof(content_type_header), + "multipart/form-data; boundary=%s", boundary); httpcAddRequestHeaderField(&context, "User-Agent", "printcam/1.0"); - httpcAddRequestHeaderField(&context, "Content-Type", "image/jpeg"); - ret = httpcAddPostDataRaw(&context, (u32 *)buf, (u32)size); + httpcAddRequestHeaderField(&context, "Content-Type", content_type_header); + ret = + httpcAddPostDataRaw(&context, (u32 *)multipart_buf, (u32)multipart_size); if (ret != 0) { httpcCloseContext(&context); - free(buf); + free(multipart_buf); return ret; } ret = httpcBeginRequest(&context); if (ret != 0) { httpcCloseContext(&context); - free(buf); + free(multipart_buf); return ret; } ret = httpcGetResponseStatusCode(&context, &statuscode); - (void)statuscode; + if (ret == 0) { + u32 downloaded = 0; + u32 contentsize = 0; + while (true) { + ret = httpcGetDownloadSizeState(&context, &downloaded, &contentsize); + if (ret != 0) + break; + if (contentsize == 0) + break; + if (downloaded >= contentsize) + break; + u32 remaining = contentsize - downloaded; + u32 toread = remaining > 4096 ? 4096 : remaining; + u8 tmp[4096]; + ret = httpcReceiveData(&context, tmp, toread); + if (ret != 0) + break; + } + } httpcCloseContext(&context); - free(buf); + free(multipart_buf); return ret; } @@ -257,12 +331,15 @@ int main(int argc, char *argv[]) { printf("printcam\nL/R=take X=switch-front/back Y=reload-url START=exit\n"); if (!ensure_storage_dir()) printf("Failed to create %s\n", STORAGE_DIR); + if (!ensure_default_endpoint_file()) + printf("Failed to create default endpoint file\n"); if (load_endpoint_from_sd() == 0) printf("Endpoint: %s\n", api_endpoint); camBuf[0] = (u8 *)memalign(0x40, WIDTH * HEIGHT * 2); camBuf[1] = (u8 *)memalign(0x40, WIDTH * HEIGHT * 2); rgbBuf = (u8 *)memalign(0x40, FB_SIZE); - if (!camBuf[0] || !camBuf[1] || !rgbBuf) { + u8 *linearBuf = malloc(FB_SIZE); + if (!camBuf[0] || !camBuf[1] || !rgbBuf || !linearBuf) { printf("Out of memory\n"); goto cleanup; } @@ -270,7 +347,6 @@ int main(int argc, char *argv[]) { svcCreateEvent(&ready[1], RESET_ONESHOT); gfxSetDoubleBuffering(GFX_TOP, true); camThread = threadCreate(cameraThreadFunc, NULL, 0x1000, 0x2F, -2, false); - u8 *linearBuf = malloc(FB_SIZE); while (aptMainLoop()) { hidScanInput(); u32 k = hidKeysDown(); @@ -301,8 +377,6 @@ int main(int argc, char *argv[]) { printf("Wrote %s\n", jpgpath); Result res = http_post_file(api_endpoint, jpgpath); printf("http_post_file returned: %" PRIx32 "\n", (u32)res); - if (res == 0) - remove(jpgpath); } else { printf("JPEG write failed\n"); } @@ -318,14 +392,22 @@ int main(int argc, char *argv[]) { } cleanup: exitRequested = true; - threadJoin(camThread, U64_MAX); - threadFree(camThread); - svcCloseHandle(ready[0]); - svcCloseHandle(ready[1]); - free(camBuf[0]); - free(camBuf[1]); - free(rgbBuf); - free(linearBuf); + if (camThread) + threadJoin(camThread, U64_MAX); + if (camThread) + threadFree(camThread); + if (ready[0]) + svcCloseHandle(ready[0]); + if (ready[1]) + svcCloseHandle(ready[1]); + if (camBuf[0]) + free(camBuf[0]); + if (camBuf[1]) + free(camBuf[1]); + if (rgbBuf) + free(rgbBuf); + if (linearBuf) + free(linearBuf); httpcExit(); gfxExit(); return 0;