diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
| commit | 4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch) | |
| tree | c296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/json_dir.c | |
| download | relic-master.tar.gz | |
Diffstat (limited to 'src/json_dir.c')
| -rw-r--r-- | src/json_dir.c | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/src/json_dir.c b/src/json_dir.c new file mode 100644 index 0000000..47526b6 --- /dev/null +++ b/src/json_dir.c @@ -0,0 +1,294 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) +** +** This program is distributed in the hope that it will be useful, +** but without any warranty; without even the implied warranty of +** merchantability or fitness for a particular purpose. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_dir.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +static cson_value * json_page_dir_list(void); +/* +** Mapping of /json/wiki/XXX commands/paths to callbacks. +*/ +#if 0 /* TODO: Not used? */ +static const JsonPageDef JsonPageDefs_Dir[] = { +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; +#endif + +#if 0 /* TODO: Not used? */ +static char const * json_dir_path_extra(void){ + static char const * zP = NULL; + if( !zP ){ + zP = g.zExtra; + while(zP && *zP && ('/'==*zP)){ + ++zP; + } + } + return zP; +} +#endif + +/* +** Impl of /json/dir. 98% of it was taken directly +** from browse.c::page_dir() +*/ +static cson_value * json_page_dir_list(void){ + cson_object * zPayload = NULL; /* return value */ + cson_array * zEntries = NULL; /* accumulated list of entries. */ + cson_object * zEntry = NULL; /* a single dir/file entry. */ + cson_array * keyStore = NULL; /* garbage collector for shared strings. */ + cson_string * zKeyName = NULL; + cson_string * zKeySize = NULL; + cson_string * zKeyIsDir = NULL; + cson_string * zKeyUuid = NULL; + cson_string * zKeyTime = NULL; + cson_string * zKeyRaw = NULL; + char * zD = NULL; + char const * zDX = NULL; + int nD; + char * zUuid = NULL; + char const * zCI = NULL; + Manifest * pM = NULL; + Stmt q = empty_Stmt; + int rid = 0; + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); + return NULL; + } + zCI = json_find_option_cstr("checkin",NULL,"ci" ); + + /* If a specific check-in is requested, fetch and parse it. If the + ** specific check-in does not exist, clear zCI. zCI==0 will cause all + ** files from all check-ins to be displayed. + */ + if( zCI && *zCI ){ + pM = manifest_get_by_name(zCI, &rid); + if( pM ){ + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + }else{ + json_set_err(FSL_JSON_E_UNRESOLVED_UUID, + "Check-in name [%s] is unresolved.", + zCI); + return NULL; + } + } + + /* Jump through some hoops to find the directory name... */ + zDX = json_find_option_cstr("name",NULL,NULL); + if(!zDX && !g.isHTTP){ + zDX = json_command_arg(g.json.dispatchDepth+1); + } + if(zDX && (!*zDX || (0==strcmp(zDX,"/")))){ + zDX = NULL; + } + zD = zDX ? fossil_strdup(zDX) : NULL; + nD = zD ? strlen(zD)+1 : 0; + while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } + + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, + pathelementFunc, 0, 0); + + /* Compute the temporary table "localfiles" containing the names + ** of all files and subdirectories in the zD[] directory. + ** + ** Subdirectory names begin with "/". This causes them to sort + ** first and it also gives us an easy way to distinguish files + ** from directories in the loop that follows. + */ + + if( zCI ){ + Stmt ins; + ManifestFile *pFile; + ManifestFile *pPrev = 0; + int nPrev = 0; + int c; + + db_multi_exec( + "CREATE TEMP TABLE json_dir_files(" + " n UNIQUE NOT NULL," /* file name */ + " fn UNIQUE NOT NULL," /* full file name */ + " u DEFAULT NULL," /* file uuid */ + " sz DEFAULT -1," /* file size */ + " mtime DEFAULT NULL" /* file mtime in unix epoch format */ + ");" + ); + + db_prepare(&ins, + "INSERT OR IGNORE INTO json_dir_files (n,fn,u,sz,mtime) " + "SELECT" + " pathelement(:path,0)," + " CASE WHEN %Q IS NULL THEN '' ELSE %Q||'/' END ||:abspath," + " a.uuid," + " a.size," + " CAST(strftime('%%s',e.mtime) AS INTEGER) " + "FROM" + " mlink m, " + " event e," + " blob a," + " blob b " + "WHERE" + " e.objid=m.mid" + " AND a.rid=m.fid"/*FILE artifact*/ + " AND b.rid=m.mid"/*CHECKIN artifact*/ + " AND a.uuid=:uuid", + zD, zD + ); + manifest_file_rewind(pM); + while( (pFile = manifest_file_next(pM,0))!=0 ){ + if( nD>0 + && ((pFile->zName[nD-1]!='/') || (0!=memcmp(pFile->zName, zD, nD-1))) + ){ + continue; + } + /*printf("zD=%s, nD=%d, pFile->zName=%s\n", zD, nD, pFile->zName);*/ + if( pPrev + && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 + && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/') + ){ + continue; + } + db_bind_text( &ins, ":path", &pFile->zName[nD] ); + db_bind_text( &ins, ":abspath", &pFile->zName[nD] ); + db_bind_text( &ins, ":uuid", pFile->zUuid ); + db_step(&ins); + db_reset(&ins); + pPrev = pFile; + for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} + if( c=='/' ) nPrev++; + } + db_finalize(&ins); + }else if( zD && *zD ){ + db_multi_exec( + "CREATE TEMP VIEW json_dir_files AS" + " SELECT DISTINCT(pathelement(name,%d)) AS n," + " %Q||'/'||name AS fn," + " NULL AS u, NULL AS sz, NULL AS mtime" + " FROM filename" + " WHERE name GLOB '%q/*'" + " GROUP BY n", + nD, zD, zD + ); + }else{ + db_multi_exec( + "CREATE TEMP VIEW json_dir_files" + " AS SELECT DISTINCT(pathelement(name,0)) AS n, NULL AS fn" + " FROM filename" + ); + } + + if(zCI){ + db_prepare( &q, "SELECT" + " n as name," + " fn as fullname," + " u as uuid," + " sz as size," + " mtime as mtime " + "FROM json_dir_files ORDER BY n"); + }else{/* UUIDs are all NULL. */ + db_prepare( &q, "SELECT n, fn FROM json_dir_files ORDER BY n"); + } + + zKeyName = cson_new_string("name",4); + zKeyUuid = cson_new_string("uuid",4); + zKeyIsDir = cson_new_string("isDir",5); + keyStore = cson_new_array(); + cson_array_append( keyStore, cson_string_value(zKeyName) ); + cson_array_append( keyStore, cson_string_value(zKeyUuid) ); + cson_array_append( keyStore, cson_string_value(zKeyIsDir) ); + + if( zCI ){ + zKeySize = cson_new_string("size",4); + cson_array_append( keyStore, cson_string_value(zKeySize) ); + zKeyTime = cson_new_string("timestamp",9); + cson_array_append( keyStore, cson_string_value(zKeyTime) ); + zKeyRaw = cson_new_string("downloadPath",12); + cson_array_append( keyStore, cson_string_value(zKeyRaw) ); + } + zPayload = cson_new_object(); + cson_object_set_s( zPayload, zKeyName, + json_new_string((zD&&*zD) ? zD : "/") ); + if( zUuid ){ + cson_object_set( zPayload, "checkin", json_new_string(zUuid) ); + } + + while( (SQLITE_ROW==db_step(&q)) ){ + cson_value * name = NULL; + char const * n = db_column_text(&q,0); + char const isDir = ('/'==*n); + zEntry = cson_new_object(); + if(!zEntries){ + zEntries = cson_new_array(); + cson_object_set( zPayload, "entries", cson_array_value(zEntries) ); + } + cson_array_append(zEntries, cson_object_value(zEntry) ); + if(isDir){ + name = json_new_string( n+1 ); + cson_object_set_s(zEntry, zKeyIsDir, cson_value_true() ); + } else{ + name = json_new_string( n ); + } + cson_object_set_s(zEntry, zKeyName, name ); + if( zCI && !isDir){ + /* Don't add the uuid/size for dir entries - that data refers to + one of the files in that directory :/. Entries with no + --checkin may refer to N versions, and therefore we cannot + associate a single size and uuid with them (and fetching all + would be overkill for most use cases). + */ + char const * fullName = db_column_text(&q,1); + char const * u = db_column_text(&q,2); + sqlite_int64 const sz = db_column_int64(&q,3); + sqlite_int64 const ts = db_column_int64(&q,4); + cson_object_set_s(zEntry, zKeyUuid, json_new_string( u ) ); + cson_object_set_s(zEntry, zKeySize, + cson_value_new_integer( (cson_int_t)sz )); + cson_object_set_s(zEntry, zKeyTime, + cson_value_new_integer( (cson_int_t)ts )); + cson_object_set_s(zEntry, zKeyRaw, + json_new_string_f("/raw/%T?name=%t", + fullName, u)); + } + } + db_finalize(&q); + if(pM){ + manifest_destroy(pM); + } + cson_free_array( keyStore ); + + free( zUuid ); + free( zD ); + return cson_object_value(zPayload); +} + +/* +** Implements the /json/dir family of pages/commands. +** +*/ +cson_value * json_page_dir(void){ +#if 1 + return json_page_dir_list(); +#else + return json_page_dispatch_helper(&JsonPageDefs_Dir[0]); +#endif +} + +#endif /* FOSSIL_ENABLE_JSON */ |
