/^ The CrossLang Package Manager Client ^/ class Tesses.CrossLang.PackageManager { private configRoot; private packageCache; /^ Work offline (defaults to false) ^/ public Offline = false; /^ Construct the Package Manager ^/ public PackageManager() { this.configRoot = Env.CrossLangConfig; this.packageCache = this.configRoot / "PackageCache"; FS.Local.CreateDirectory(this.packageCache); } /^ Parse package filename ^/ public ParseFileName(name) { var index = name.LastIndexOf('.'); if(index == -1) throw "We expect an extension"; if(name.Substring(index) != ".crvm") throw "We expect the extension .crvm"; name = name.Substring(0,index); //Name-VERSION-[dev|alpha|beta|prod] var lastIndex = name.LastIndexOf('-'); var verStage = name.Substring(lastIndex+1); var versionStr = verStage; if(verStage == "dev" || verStage == "alpha" || verStage == "beta" || verStage == "prod") { var lastIndex2 = name.LastIndexOf('-', lastIndex-1); return { Name = name.Substring(0,lastIndex2), Version = name.Substring(lastIndex2+1) }; } else { return { Name = name.Substring(0,lastIndex), Version = name.Substring(lastIndex+1) }; } } /^ Get package servers list ^/ public GetPackageServers() { var packageConfigFile = configRoot / "package_servers.json"; if(FS.Local.RegularFileExists(packageConfigFile)) { return Json.Decode(FS.ReadAllText(FS.Local, packageConfigFile)); } return ["https://cpkg.tesseslanguage.com/"]; } /^ Get a package (returns bytearray of package data or null if not found), use GetLatest to get the latest version ^/ public GetPackage(name, version) { var v = Version.Parse(version); var useCache = v.Stage != "dev"; var pkgFile = packageCache / name / v.ToString(); if(useCache && FS.Local.RegularFileExists(pkgFile)) { return FS.ReadAllBytes(FS.Local,pkgFile); } if(this.Offline) return null; each(var item : this.GetPackageServers()) { //https://cpkg.tesseslanguage.com/api/v1/download?name=MyPackage&version=1.0.0.0-prod var uri = $"{item.TrimEnd('/')}/api/v1/download?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(version)}"; Console.WriteLine($"Downloading: {name} {version} from {item}"); var req = Net.Http.MakeRequest(uri); if(req.StatusCode == 200) { var strm = new MemoryStream(true); req.CopyToStream(strm); if(useCache) { FS.Local.CreateDirectory(packageCache / name); var f = FS.Local.OpenFile(pkgFile,"wb"); strm.Seek(0,0); strm.CopyTo(f); f.Close(); } var data = strm.GetBytes(); strm.Close(); req.Close(); return data; } } return null; } /^ Get the latest version of a package ^/ public GetLatest(name) { var pkgServers = this.GetPackageServers(); if(this.Offline || pkgServers.Count == 0) { //user has declared they are offline or don't have packageServers look through packages locally var version = new Version(0,0,0,0,"dev"); var configRoot = Env.CrossLangConfig; var dir = configRoot / "PackageCache" / name; if(FS.Local.DirectoryExists(dir)) each(var f : FS.Local.EnumeratePaths(dir)) { var v = Version.Parse(f.GetFileName()); if(v >= version) { version = v; } } if(version.VersionInt == 0) return null; return version.ToString(); } else { each(var item : pkgServers) { var uri = $"{item.TrimEnd('/')}/api/v1/latest?name={Net.Http.UrlEncode(name)}"; var req = Net.Http.MakeRequest(uri); if(req.StatusCode == 200) { var res0 = req.ReadAsString(); var res = Json.Decode(res0); req.Close(); return res.version; } } } return null; } /^ Search for packages based on query Options is a {"server": "SERVER_URL", "type": "lib", "pluginHost": "yourPluginHost","offset":0,"limit": 20} ^/ public Search(query, $options) { options = options ?? {}; var urlBase = "/api/v1/search?q={Net.Http.UrlEncode(query)}"; if(TypeOf(options.type) == "String") urlBase += $"&type={Net.Http.UrlEncode(options.type)}"; if(TypeOf(options.pluginHost) == "String") urlBase += "&pluginHost={Net.Http.UrlEncode(options.pluginHost)}"; if(TypeOf(options.offset) == "Long") urlBase += $"&offset={options.offset}"; if(TypeOf(options.limit) == "Long") urlBase += $"&limit={options.limit}"; func handle(server) { var req = Net.Http.MakeRequest($"{server.TrimEnd('/')}{urlBase}"); if(req.StatusCode==200) { try { var res2=Json.Decode(req.ReadAsString()); return res2.packages; }catch(ex) { } } return null; } if(TypeOf(options.server) == "String") return handle(options.server) ?? []; else { each(var item : this.GetPackageServers()) { var r = handle(item); if(TypeOf(r) == "List") return r; } return []; } } /^ Download a plugin to directory returns true if succeeds or false if it fails ^/ public DownloadPlugin(dirFS, name, version) { var pkg = this.GetPackage(name,version.ToString()); if(pkg == null) return false; var ms = new MemoryStream(true); ms.WriteBlock(pkg,0,pkg.Count); ms.Seek(0,0); var vm = VM.LoadExecutable(ms); ms.Close(); var info = {}; try{info=Json.Decode(vm.Info);}catch(ex){} var short_name = TypeOf(info.short_name) == "String" ? info.short_name : name; dirFS.CreateDirectory(/short_name); each(var file : dirFS.EnumeratePaths(/short_name)) { if(file.GetExtension() == ".crvm" && dirFS.FileExists(file)) { dirFS.DeleteFile(file); } } FS.WriteAllBytes(dirFS,/short_name/$"{short_name}.crvm", pkg); func dlDeps(_vm) { each(var dep : _vm.Dependencies) { var path = /short_name/$"{dep.Name}-{dep.Version.ToString()}"; if(!dirFs.RegularFileExists(path)) { var pkg2 = this.GetPackage(dep.Name,dep.Version.ToString()); if(pkg2 == null) return false; FS.WriteAllBytes(dirFS,path,pkg2); var ms2 = new MemoryStream(true); ms2.WriteBlock(pkg2,0,pkg2.Count); ms2.Seek(0,0); var vm2 = VM.LoadExecutable(ms2); ms2.Close(); dlDeps(vm2); } } return true; } return dlDeps(vm); } }