A TempleOS distro for heretics
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

463 lines
11 KiB

// vim: set ft=cpp:
#include "::/Adam/Net/Url"
#define PKG_EURL (-20001)
#define PKG_EMOUNT (-20002)
#define PKG_EMANIFEST (-20003)
#define PKG_EVERSION (-20004)
#define PKG_EOSVERSION (-20005)
#define PKG_EUNSUITABLE (-20006)
#define PKG_VERSION 11
static U8* PKG_BASE_URL = "http://shrine.lanceward.net/packages";
static U8* PKG_LOCAL_REPO = "::/Misc/Packages";
static U8* PKG_TMP_DIR = "::/Tmp/PkgTmp";
class CPkgInfo {
U8* package_name;
I32 pkgmin;
I32 release;
I32 osmin;
I32 osmax;
I64 size;
U8* version;
U8* installdir;
U8* iso_c;
U8* post_install_doc;
};
// TODO: Is there a built-in for this?
static U8* StripDir(U8* file_path) {
U8* slash = StrLastOcc(file_path, "/");
if (slash)
return slash + 1;
else
return file_path;
}
U0 PkgInfoInit(CPkgInfo* pinf) {
pinf->package_name = 0;
pinf->pkgmin = 0x7fffffff;
pinf->release = 0;
pinf->osmin = 0;
pinf->osmax = 0x7fffffff;
pinf->size = 0;
pinf->version = 0;
pinf->installdir = 0;
pinf->iso_c = 0;
pinf->post_install_doc = 0;
}
U0 PkgInfoFree(CPkgInfo* pinf) {
Free(pinf->package_name);
Free(pinf->version);
Free(pinf->installdir);
Free(pinf->iso_c);
Free(pinf->post_install_doc);
PkgInfoInit(pinf);
}
// Returns 0 or error code
I64 PkgParseManifest(CPkgInfo* pinf, U8* manifest) {
U8* key = manifest;
while (*key) {
//"?%s", key;
U8* end = StrFirstOcc(key, "\n");
if (end) {
*end = 0;
end++;
}
else
end = key + StrLen(key);
U8* value = StrFirstOcc(key, "\t");
if (!value)
return PKG_EMANIFEST;
*value = 0;
value++;
//"%s=%s;\n", key, value;
if (0) {}
else if (!StrCmp(key, "name")) { Free(pinf->package_name); pinf->package_name = StrNew(value); }
else if (!StrCmp(key, "pkgmin")) { pinf->pkgmin = Str2I64(value); }
else if (!StrCmp(key, "release")) { pinf->release = Str2I64(value); }
else if (!StrCmp(key, "osmin")) { pinf->osmin = Str2I64(value); }
else if (!StrCmp(key, "osmax")) { pinf->osmax = Str2I64(value); }
else if (!StrCmp(key, "size")) { pinf->size = Str2I64(value); }
else if (!StrCmp(key, "version")) { Free(pinf->version); pinf->version = StrNew(value); }
else if (!StrCmp(key, "installdir")) { Free(pinf->installdir); pinf->installdir = StrNew(value); }
else if (!StrCmp(key, "iso.c")) { Free(pinf->iso_c); pinf->iso_c = StrNew(value); }
else if (!StrCmp(key, "post-install-doc")) { Free(pinf->post_install_doc); pinf->post_install_doc = StrNew(value); }
else { /* unrecognized keys are simply ignored */ }
key = end;
}
return 0;
}
I64 PkgWriteManifest(CPkgInfo* pinf, U8* path) {
// TODO: implement
no_warn pinf;
FileWrite(path, "", 0);
return 0;
}
// Downloads a package info from the repository.
// Returns 0 or error code
I64 PkgFetchManifest(CPkgInfo* pinf, U8* package_name) {
// Old packages didn't have to specify a name, so we'll keep this for now
pinf->package_name = StrNew(package_name);
U8* url = MStrPrint("%s/%s", PKG_BASE_URL, package_name);
U8* manifest = 0;
I64 size = 0;
I64 error = UrlGet(url, &manifest, &size);
if (error == 0)
error = PkgParseManifest(pinf, manifest);
Free(manifest);
Free(url);
return error;
}
// Get the URL of the package's ISO.C download.
// Returns NULL if N/A, otherwise must be Free()d.
U8* PkgAllocISOCUrl(CPkgInfo* pinf) {
if (!pinf->iso_c)
return NULL;
// A bit hacky, but will probably always work
if (StrFind("//", pinf->iso_c))
return StrNew(pinf->iso_c);
else
return MStrPrint("%s/%s", PKG_BASE_URL, pinf->iso_c);
}
// Check if the package metadata makes it viable for installation.
// You still need to do PkgCheckCompatibility, dependency resolution,
// and check for a suitable installable format.
Bool PkgIsInstallable(CPkgInfo* pinf) {
return pinf->package_name != NULL && pinf->version != NULL && pinf->installdir != NULL;
}
// Check if the package is compatible with this OS & Pkg version
I64 PkgCheckCompatibility(CPkgInfo* pinf) {
if (pinf->pkgmin > PKG_VERSION) {
"$FG,6$This package requires a more recent version of $FG,5$Pkg\n";
"$FG$Please update.\n";
return PKG_EVERSION;
}
I64 osver = ToI64(sys_os_version * 100);
if (osver < pinf->osmin) {
"$FG,6$This package requires a more recent system version.\n";
"$FG$Please update. (need %d, have %d)\n", pinf->osmin, osver;
return PKG_EOSVERSION;
}
if (osver > pinf->osmax) {
"$FG,6$This package is not compatible with your system version.\n";
"$FG$Last supported version is %d, you have %d.\n", pinf->osmax, osver;
return PKG_EOSVERSION;
}
return 0;
}
I64 PkgRegister(CPkgInfo* pinf) {
// TODO: this is very preliminary
if (pinf->package_name == NULL)
return PKG_EUNSUITABLE;
U8* path = MStrPrint("%s/%s", PKG_LOCAL_REPO, pinf->package_name);
PkgWriteManifest(pinf, path);
return 0;
}
// Install a package, using the provided ISO.C file.
// This will also register the package as installed.
I64 PkgInstallISOC(CPkgInfo* pinf, U8* iso_c) {
if (pinf->package_name == NULL || pinf->installdir == NULL)
return PKG_EUNSUITABLE;
I64 error = 0;
"Installing %s\n$FG,7$", pinf->package_name;
I64 letter = MountFile(iso_c);
if (letter) {
U8 src_path[8];
StrPrint(src_path, "%c:/", letter);
// StrLen check is a temporary hack to not complain about MkDir("::/");
if (StrLen(pinf->installdir) > 3)
DirMk(pinf->installdir);
CopyTree(src_path, pinf->installdir);
// Register package as installed
error = PkgRegister(pinf);
// Display post-install doc
if (pinf->post_install_doc) {
Ed(pinf->post_install_doc);
}
}
else
error = PKG_EMOUNT;
Unmount(letter);
"$FG$";
return error;
}
// Verify, download & install a single package
// All dependencies must have been installed at this point.
I64 PkgDownloadAndInstall(CPkgInfo* pinf) {
I64 error = PkgCheckCompatibility(pinf);
if (error) { return error; }
U8* iso_c_url = PkgAllocISOCUrl(pinf);
if (iso_c_url) {
U8* iso_data = 0;
I64 iso_size = 0;
"Downloading %s...\n", pinf->package_name;
error = UrlGetWithProgress(iso_c_url, &iso_data, &iso_size);
if (error == 0) {
U8* tmppath = "::/Tmp/Package.ISO.C";
FileWrite(tmppath, iso_data, iso_size);
error = PkgInstallISOC(pinf, tmppath);
}
Free(iso_data);
Free(iso_c_url);
}
else {
"$FG,6$No suitable download address. Package broken?\n";
error = PKG_EUNSUITABLE;
}
return error;
}
// Expected max length: 5 ("1023k")
static U8* FormatSize(I64 size) {
static U8 buf[16];
if (size > 0x40000000)
StrPrint(buf, "%dG", (size + 0x3fffffff) / 0x40000000);
else if (size > 0x100000)
StrPrint(buf, "%dM", (size + 0xfffff) / 0x100000);
else if (size > 0x400)
StrPrint(buf, "%dk", (size + 0x3ff) / 0x400);
else
StrPrint(buf, "%d", size);
return buf;
}
// Install a package using a local manifest file
public I64 PkgInstallFromFile(U8* manifest_path) {
DirMk(PKG_LOCAL_REPO);
CPkgInfo pinf;
PkgInfoInit(&pinf);
// Parse manifest
I64 manifest_size;
U8* manifest_file = FileRead(manifest_path, &manifest_size);
// This relies on FileRead returning a 0-terminated buffer.
// As of v502, this happens for all file systems
I64 error = PkgParseManifest(&pinf, manifest_file);
if (error == 0) {
error = PkgCheckCompatibility(&pinf);
if (!error) {
if (pinf.iso_c) {
PkgInstallISOC(&pinf, pinf.iso_c);
}
else {
"$FG,6$No suitable installable file. Package broken?\n";
error = PKG_EUNSUITABLE;
}
}
else {
"$FG,4$PkgCheckCompatibility error: %d\n$FG$", error;
}
}
else {
"$FG,4$PkgParseManifest error: %d\n$FG$", error;
}
PkgInfoFree(&pinf);
return error;
}
// Install a package from the repository
public I64 PkgInstall(U8* package_name) {
SocketInit();
DirMk(PKG_LOCAL_REPO);
CPkgInfo pinf;
PkgInfoInit(&pinf);
I64 error = PkgFetchManifest(&pinf, package_name);
if (error == 0) {
if (PkgIsInstallable(&pinf)) {
"$FG,8$ Package Ver \n"
"$FG,8$ Dir Size \n"
"$FG,8$============================\n"
"$FG,2$+ %-20s %-6s\n", package_name, pinf.version;
"$FG,2$ %-20s %-6s\n", pinf.installdir, FormatSize(pinf.size);
"\n"
"$FG$Is this ok? (y/n) ";
I64 ok = GetKey(NULL, TRUE);
"\n";
// TODO: verify all packages before we start downloading
if (ok == 'y') {
error = PkgDownloadAndInstall(&pinf);
if (error == 0) {
"$FG,2$Installed 1 package(s)\n";
}
else {
"$FG,4$PkgDownloadAndInstall error: %d\n$FG$", error;
}
}
}
else {
"$FG,4$PkgInstall: %s is not installable\n$FG$", package_name;
error = PKG_EUNSUITABLE;
}
}
else {
"$FG,4$PkgFetchManifest error: %d\n$FG$", error;
}
PkgInfoFree(&pinf);
return error;
}
// List packages available in the repository
public I64 PkgList() {
SocketInit();
U8* url = MStrPrint("%s/packages.list", PKG_BASE_URL);
U8* list = 0;
I64 size = 0;
I64 error = UrlGet(url, &list, &size);
if (error == 0) {
"$FG,2$%s\n", list;
/*U8* entry = list;
while (*entry) {
U8* end = StrFirstOcc(entry, "\n");
if (end) {
*end = 0;
end++;
}
else
end = value + StrLen(value);
"$FG,2$%s\n", entry;
entry = end;
}*/
}
else {
"$FG,4$UrlGet error: %d\n$FG$", error;
}
Free(list);
Free(url);
return error;
}
// Build a package from directory contents
public I64 PkgMakeFromDir(U8* manifest_path, U8* src_dir) {
CPkgInfo pinf;
PkgInfoInit(&pinf);
// Parse manifest
I64 manifest_size;
U8* manifest_file = FileRead(manifest_path, &manifest_size);
// This relies on FileRead returning a 0-terminated buffer.
// As of v502, this happens for all file systems
I64 error = PkgParseManifest(&pinf, manifest_file);
if (error == 0) {
// Build RedSea ISO
if (pinf.iso_c) {
U8* iso_path = pinf.iso_c;
// RedSeaISO doesn't return a proper error code
RedSeaISO(iso_path, src_dir);
// TODO: update & save manifest
/*CDirEntry* de;
if (FileFind(iso_path, &de)) {
pinf.size = de.size;
// Save updated manifest
PkgWriteManifest(&pinf, manifest_path);
Free(de->full_name);
}
else {
"$FG,6$Something went wrong, can't stat %s.\n", iso_path;
error = PKG_EMOUNT;
}*/
}
else {
"$FG,6$No output file defined.\n";
error = PKG_EUNSUITABLE;
}
}
else {
"$FG,4$PkgParseManifest error: %d\n$FG$", error;
}
PkgInfoFree(&pinf);
return error;
}
// Build a package using a single file
I64 PkgMakeFromFile(U8* manifest_path, U8* file_path) {
DelTree(PKG_TMP_DIR);
DirMk(PKG_TMP_DIR);
U8* tmppath = MStrPrint("%s/%s", PKG_TMP_DIR, StripDir(file_path));
Copy(file_path, tmppath);
I64 error = PkgMakeFromDir(manifest_path, PKG_TMP_DIR);
Free(tmppath);
return error;
}