mirror of
https://github.com/KarnerF/SMPlusGui.git
synced 2026-10-17 08:01:46 +02:00
gamelist: always use /api/v1/images (always has data); enrich with title_name from games API
This commit is contained in:
1 parent
b250018f99
commit
871fac5bf4
2 files changed
+64
-47
No files matched your search
@@ -1777,7 +1777,6 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
"fetch('/api/sm/gamelist').then(function(r){return r.text();})"
|
||||
".then(function(html){"
|
||||
"if(gl)gl.innerHTML=html;"
|
||||
"var noData=gl&&gl.querySelector('p')&&gl.textContent.indexOf('Keine')<0&&gl.textContent.indexOf('nicht')<0;"
|
||||
"if(_gpR===0&&gl&&!gl.querySelector('button')){"
|
||||
"setTimeout(function(){if(_gpR===0)loadGamePanel(1);},8000);}"
|
||||
"}).catch(function(){"
|
||||
@@ -2899,53 +2898,59 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
#undef SM_MANUAL
|
||||
}
|
||||
else if(mg_match(hm->uri,mg_str("/api/sm/gamelist"),NULL)){
|
||||
/* server-side game list HTML using /api/v1/games (has title_name) */
|
||||
char *games_json=sm_api_req("POST","/api/v1/games","{}");
|
||||
char *out=malloc(131072); if(!out){mg_http_reply(c,500,"","");if(games_json)free(games_json);return;}
|
||||
/* images always available; enrich with title_name from games API when mounted */
|
||||
char *img_json=sm_api_req("POST","/api/v1/images","{}");
|
||||
char *gam_json=sm_api_req("POST","/api/v1/games","{}");
|
||||
char *out=malloc(131072); if(!out){if(img_json)free(img_json);if(gam_json)free(gam_json);mg_http_reply(c,500,"","");return;}
|
||||
int pos=0;
|
||||
#define GL(fmt,...) pos+=snprintf(out+pos,131071-pos,fmt,##__VA_ARGS__)
|
||||
if(!games_json){
|
||||
if(!img_json){
|
||||
GL("<p class=\"hint\" style=\"color:#f87171;\">SM API nicht erreichbar</p>");
|
||||
} else {
|
||||
const char *gstart=strstr(games_json,"\"games\"");
|
||||
if(!gstart||!strstr(gstart,"\"title_id\"")){GL("<p class=\"hint\">Keine Spiele gefunden</p>");}
|
||||
const char *istart=strstr(img_json,"\"images\"");
|
||||
if(!istart){GL("<p class=\"hint\">Keine Spiele gefunden</p>");}
|
||||
else {
|
||||
int n=0;
|
||||
const char *cur=gstart;
|
||||
const char *cur=istart;
|
||||
while((cur=strstr(cur,"{\"path\""))!=NULL&&pos<120000){
|
||||
char tid[16]={0},name[128]={0},imgpath[512]={0};
|
||||
int mounted=0;
|
||||
/* extract title_id */
|
||||
const char *tv=strstr(cur,"\"title_id\":\"");
|
||||
if(tv){tv+=12;const char *te=strchr(tv,'"');if(te&&te-tv<(int)sizeof(tid)){memcpy(tid,tv,te-tv);}}
|
||||
/* extract title_name */
|
||||
const char *nv=strstr(cur,"\"title_name\":\"");
|
||||
if(nv){nv+=14;const char *ne=strchr(nv,'"');if(ne&&ne-nv<(int)sizeof(name)){memcpy(name,nv,ne-nv);}}
|
||||
/* extract image_path for size */
|
||||
const char *iv=strstr(cur,"\"image_path\":\"");
|
||||
if(iv){iv+=14;const char *ie=strchr(iv,'"');if(ie&&ie-iv<(int)sizeof(imgpath)){memcpy(imgpath,iv,ie-iv);}}
|
||||
/* extract mounted */
|
||||
const char *mv=strstr(cur,"\"mounted\":");
|
||||
if(mv){mv+=10;mounted=(mv[0]=='t');}
|
||||
/* get file size from image_path */
|
||||
char szbuf[32]="?";
|
||||
if(imgpath[0]){
|
||||
/* unescape JSON \/ */
|
||||
char rp[512]={0};int ri=0;
|
||||
for(int i=0;imgpath[i]&&ri<(int)sizeof(rp)-1;i++){
|
||||
if(imgpath[i]=='\\'&&imgpath[i+1]=='/'){rp[ri++]='/';i++;}
|
||||
else rp[ri++]=imgpath[i];}
|
||||
struct stat ist; if(stat(rp,&ist)==0){
|
||||
if(ist.st_size>1073741824) snprintf(szbuf,sizeof(szbuf),"%.2f GB",(double)ist.st_size/1073741824.0);
|
||||
else snprintf(szbuf,sizeof(szbuf),"%lld MB",(long long)ist.st_size/1048576);}
|
||||
char imgpath[512]={0},mp[256]={0};
|
||||
long long sz=0; int mounted=0;
|
||||
/* extract path, mount_point, size, mounted from images API */
|
||||
const char *pv=strstr(cur,"\"path\":\""); if(pv){pv+=8;const char *pe=strchr(pv,'"');if(pe&&pe-pv<(int)sizeof(imgpath)-1)memcpy(imgpath,pv,pe-pv);}
|
||||
const char *mv2=strstr(cur,"\"mount_point\":\""); if(mv2){mv2+=15;const char *me=strchr(mv2,'"');if(me&&me-mv2<(int)sizeof(mp)-1)memcpy(mp,mv2,me-mv2);}
|
||||
const char *sv=strstr(cur,"\"size\":"); if(sv){sv+=7;sz=strtoll(sv,NULL,10);}
|
||||
const char *mv=strstr(cur,"\"mounted\":"); if(mv){mv+=10;mounted=(mv[0]=='t');}
|
||||
/* extract title_id from mount_point: /mnt/shadowmnt/PPSA20515_name_hash */
|
||||
char tid[16]={0};
|
||||
const char *bn=strrchr(mp,'/'); bn=bn?bn+1:mp;
|
||||
char tmp[64]={0}; strncpy(tmp,bn,sizeof(tmp)-1);
|
||||
char *us=strchr(tmp,'_'); if(us)*us=0;
|
||||
if(strlen(tmp)==9){int ok=1;
|
||||
for(int i=0;i<4;i++) if(!isalpha((unsigned char)tmp[i])){ok=0;break;}
|
||||
for(int i=4;i<9;i++) if(!isdigit((unsigned char)tmp[i])){ok=0;break;}
|
||||
if(ok) strncpy(tid,tmp,sizeof(tid)-1);}
|
||||
/* look up title_name from games JSON if available */
|
||||
char dname[128]={0};
|
||||
if(tid[0]&&gam_json){
|
||||
char srch[32]; snprintf(srch,sizeof(srch),"\"title_id\":\"%s\"",tid);
|
||||
const char *gp=strstr(gam_json,srch);
|
||||
if(gp){const char *nn=strstr(gp,"\"title_name\":\"");
|
||||
if(nn){nn+=14;const char *ne=strchr(nn,'"');if(ne&&ne-nn<(int)sizeof(dname)-1)memcpy(dname,nn,ne-nn);}}
|
||||
}
|
||||
/* unescape name */
|
||||
char dname[128]={0};{int di=0;
|
||||
for(int i=0;name[i]&&di<(int)sizeof(dname)-1;i++){
|
||||
if(name[i]=='\\'&&name[i+1]=='/'){ dname[di++]='/';i++;}
|
||||
else dname[di++]=name[i];}
|
||||
}
|
||||
if(!dname[0]) strncpy(dname,tid,sizeof(dname)-1);
|
||||
if(!dname[0]&&tid[0]) strncpy(dname,tid,sizeof(dname)-1);
|
||||
/* unescape path for stat */
|
||||
char rp[512]={0};{int ri=0;for(int i=0;imgpath[i]&&ri<(int)sizeof(rp)-1;i++){
|
||||
if(imgpath[i]=='\\'&&imgpath[i+1]=='/'){rp[ri++]='/';i++;}else rp[ri++]=imgpath[i];}}
|
||||
/* size from stat (more reliable than JSON size field) */
|
||||
char szbuf[32]={0};
|
||||
{struct stat ist;
|
||||
if(rp[0]&&stat(rp,&ist)==0){
|
||||
if(ist.st_size>1073741824) snprintf(szbuf,sizeof(szbuf),"%.2f GB",(double)ist.st_size/1073741824.0);
|
||||
else snprintf(szbuf,sizeof(szbuf),"%lld MB",(long long)ist.st_size/1048576);}
|
||||
else if(sz>0){
|
||||
if(sz>1073741824) snprintf(szbuf,sizeof(szbuf),"%.2f GB",(double)sz/1073741824.0);
|
||||
else snprintf(szbuf,sizeof(szbuf),"%lld MB",sz/1048576);}
|
||||
else strncpy(szbuf,"?",sizeof(szbuf)-1);}
|
||||
const char *mntcol=mounted?"color:#10b981":"color:var(--dim)";
|
||||
const char *mnttxt=mounted?"● aktiv":"○ inaktiv";
|
||||
const char *bs_um="background:transparent;border:1px solid #f87171;border-radius:4px;color:#f87171;padding:3px 10px;cursor:pointer;font-size:.75rem;";
|
||||
@@ -2953,23 +2958,24 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
GL("<div style=\"display:flex;align-items:center;gap:10px;padding:8px 4px;border-bottom:1px solid var(--border);\">");
|
||||
GL("<div style=\"position:relative;width:48px;height:48px;border-radius:8px;overflow:hidden;background:#1e2a42;flex-shrink:0;\">"
|
||||
"<img src=\"/api/icon/%s\" style=\"position:absolute;top:0;left:0;width:100%%;height:100%%;object-fit:cover;\" onerror=\"this.remove()\">"
|
||||
"<span style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:.55rem;color:#64748b;font-family:monospace;\">%.4s</span></div>",tid,tid);
|
||||
"<span style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:.55rem;color:#64748b;font-family:monospace;\">%.4s</span></div>",tid,tid[0]?tid:"?");
|
||||
GL("<div style=\"flex:1;min-width:0;\">"
|
||||
"<div style=\"font-size:.83rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;\">%s</div>"
|
||||
"<div style=\"font-family:var(--mono);font-size:.7rem;color:var(--dim);\">%s — %s</div></div>",
|
||||
dname, tid, szbuf);
|
||||
dname[0]?dname:rp, tid[0]?tid:"–", szbuf);
|
||||
GL("<div style=\"display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex-shrink:0;\">"
|
||||
"<span style=\"font-size:.75rem;%s;\">%s</span>"
|
||||
"<button type=\"button\" data-tid=\"%s\" data-path=\"%s\" onclick=\"%s(this.dataset.tid,this.dataset.path)\" style=\"%s\">%s</button>"
|
||||
"</div></div>",
|
||||
mntcol,mnttxt, tid,imgpath, mounted?"_smUnmount":"_smMount",
|
||||
mntcol,mnttxt, tid,rp, mounted?"_smUnmount":"_smMount",
|
||||
mounted?bs_um:bs_mt, mounted?"Unmount":"Mount");
|
||||
n++; cur++;
|
||||
}
|
||||
if(n==0) GL("<p class=\"hint\">Keine Spiele gefunden</p>");
|
||||
}
|
||||
free(games_json);
|
||||
free(img_json);
|
||||
}
|
||||
if(gam_json) free(gam_json);
|
||||
out[pos]=0;
|
||||
#undef GL
|
||||
mg_http_reply(c,200,"Content-Type: text/html; charset=utf-8\r\nCache-Control: no-store\r\n","%s",out);
|
||||
@@ -3009,6 +3015,16 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if(stat(src,&sst)!=0) src[0]=0;}
|
||||
closedir(sd);}
|
||||
}
|
||||
/* fallback: use GC's icon cache (already resized PNGs from Game Compressor) */
|
||||
if(!src[0]){
|
||||
DIR *gcd=opendir("/data/GameCompressor/icon-cache");
|
||||
if(gcd){struct dirent *de;size_t tl=strlen(tid);
|
||||
while(!src[0]&&(de=readdir(gcd))!=NULL)
|
||||
if(strncmp(de->d_name,tid,tl)==0&&de->d_name[tl]=='-'){
|
||||
snprintf(src,sizeof(src),"/data/GameCompressor/icon-cache/%s",de->d_name);
|
||||
if(stat(src,&sst)!=0) src[0]=0;}
|
||||
closedir(gcd);}
|
||||
}
|
||||
if(src[0]){
|
||||
/* copy source to cache using cp via raw read/write */
|
||||
FILE *sf=fopen(src,"rb");
|
||||
|
||||
+4
-3
@@ -3501,9 +3501,10 @@ static void *p_open(const char *path, int flags) {
|
||||
MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
|
||||
return (void *) _wfopen(b1, b2);
|
||||
#else
|
||||
const char *mode = flags == MG_FS_READ ? "rbe"
|
||||
: (flags & MG_FS_EXCL) ? "wxbe"
|
||||
: "a+be"; // e for CLOSEXEC
|
||||
/* PS5/FreeBSD: 'e' flag (O_CLOEXEC) may not be supported; use plain modes */
|
||||
const char *mode = flags == MG_FS_READ ? "rb"
|
||||
: (flags & MG_FS_EXCL) ? "wxb"
|
||||
: "a+b";
|
||||
return (void *) fopen(path, mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user