mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-26 23:27:14 +03:00
wip full new kit install downloads flow
This commit is contained in:
parent
beeae033c9
commit
6753de511a
@ -11,6 +11,7 @@ interface downloads {
|
||||
progress(progress-update),
|
||||
size(size-update),
|
||||
get-files(option<package-id>),
|
||||
add-download(add-download-request),
|
||||
}
|
||||
|
||||
record download-request {
|
||||
@ -45,6 +46,13 @@ interface downloads {
|
||||
files: list<entry>,
|
||||
}
|
||||
|
||||
// part of new-package-request local-only flow.
|
||||
record add-download-request {
|
||||
package-id: package-id,
|
||||
version-hash: string,
|
||||
mirror: bool,
|
||||
}
|
||||
|
||||
record progress-update {
|
||||
package-id: package-id,
|
||||
version-hash: string,
|
||||
@ -115,6 +123,13 @@ interface main {
|
||||
use chain.{on-chain-metadata};
|
||||
|
||||
|
||||
// general error type used by both chain: and downloads:
|
||||
// todo: add variant for types to match on
|
||||
|
||||
record error {
|
||||
reason: string,
|
||||
}
|
||||
|
||||
variant request {
|
||||
local(local-request),
|
||||
}
|
||||
@ -138,7 +153,7 @@ interface main {
|
||||
|
||||
variant local-request {
|
||||
new-package(new-package-request),
|
||||
install(package-id),
|
||||
install(install-package-request),
|
||||
uninstall(package-id),
|
||||
apis,
|
||||
get-api(package-id),
|
||||
@ -146,10 +161,15 @@ interface main {
|
||||
|
||||
record new-package-request {
|
||||
package-id: package-id,
|
||||
metadata: on-chain-metadata,
|
||||
mirror: bool,
|
||||
}
|
||||
|
||||
record install-package-request {
|
||||
package-id: package-id,
|
||||
metadata: option<on-chain-metadata>, // if None == local sideload package.
|
||||
version-hash: string,
|
||||
}
|
||||
|
||||
variant local-response {
|
||||
new-package-response(new-package-response),
|
||||
install-response(install-response),
|
||||
|
@ -1,12 +1,14 @@
|
||||
use crate::{
|
||||
kinode::process::downloads::{DownloadRequest, DownloadResponse},
|
||||
kinode::process::downloads::{DownloadRequest, DownloadResponse, Downloads},
|
||||
kinode::process::main::Error,
|
||||
state::{MirrorCheck, PackageState, State},
|
||||
Resp,
|
||||
};
|
||||
|
||||
use kinode_process_lib::{
|
||||
http::server,
|
||||
http::{self, Method, StatusCode},
|
||||
Address, LazyLoadBlob, NodeId, PackageId, Request,
|
||||
Address, LazyLoadBlob, PackageId, Request,
|
||||
};
|
||||
use kinode_process_lib::{SendError, SendErrorKind};
|
||||
use serde_json::json;
|
||||
@ -206,9 +208,15 @@ pub fn handle_http_request(
|
||||
bytes: body,
|
||||
}),
|
||||
),
|
||||
Err(_e) => (
|
||||
Err(e) => (
|
||||
server::HttpResponse::new(http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||
None,
|
||||
Some(LazyLoadBlob {
|
||||
mime: None,
|
||||
bytes: serde_json::to_vec(&Error {
|
||||
reason: e.to_string(),
|
||||
})
|
||||
.unwrap(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -222,6 +230,13 @@ fn get_package_id(url_params: &HashMap<String, String>) -> anyhow::Result<Packag
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
fn get_version_hash(url_params: &HashMap<String, String>) -> anyhow::Result<String> {
|
||||
let Some(version_hash) = url_params.get("version_hash") else {
|
||||
return Err(anyhow::anyhow!("Missing version_hash"));
|
||||
};
|
||||
Ok(version_hash.to_string())
|
||||
}
|
||||
|
||||
fn gen_package_info(id: &PackageId, state: &PackageState) -> serde_json::Value {
|
||||
// installed package.
|
||||
// on-chain info combined somewhere else.
|
||||
@ -265,20 +280,50 @@ fn serve_paths(
|
||||
return Ok((StatusCode::OK, None, serde_json::to_vec(&all)?));
|
||||
}
|
||||
"/downloads" => {
|
||||
// TODO
|
||||
Ok((StatusCode::OK, None, vec![]))
|
||||
// get all local downloads!
|
||||
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||
let resp = Request::new()
|
||||
.target(downloads)
|
||||
.body(serde_json::to_vec(&Downloads::GetFiles(None))?)
|
||||
.send_and_await_response(5)??;
|
||||
|
||||
let msg = serde_json::from_slice::<Resp>(resp.body())?;
|
||||
println!("downlaods response: {:?}", msg);
|
||||
// shouldn't really return status code
|
||||
Ok((StatusCode::OK, None, resp.body().to_vec()))
|
||||
}
|
||||
"/installed" => {
|
||||
// TODO
|
||||
Ok((StatusCode::OK, None, vec![]))
|
||||
// TODO format json for UI
|
||||
println!("/installed response: {:?}", state.packages);
|
||||
|
||||
let body = serde_json::to_vec(&state.packages)?;
|
||||
Ok((StatusCode::OK, None, body))
|
||||
}
|
||||
"/ourapps" => {
|
||||
// TODO
|
||||
// TODO, fetch from state
|
||||
Ok((StatusCode::OK, None, vec![]))
|
||||
}
|
||||
"/downloads/:id" => {
|
||||
// TODO
|
||||
Ok((StatusCode::OK, None, vec![]))
|
||||
// get downloads for a specific app
|
||||
let Ok(package_id) = get_package_id(url_params) else {
|
||||
return Ok((
|
||||
StatusCode::BAD_REQUEST,
|
||||
None,
|
||||
format!("Missing id").into_bytes(),
|
||||
));
|
||||
};
|
||||
// fix
|
||||
let package_id = crate::kinode::process::main::PackageId::from_process_lib(package_id);
|
||||
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||
let resp = Request::new()
|
||||
.target(downloads)
|
||||
.body(serde_json::to_vec(&Downloads::GetFiles(Some(package_id)))?)
|
||||
.send_and_await_response(5)??;
|
||||
|
||||
let msg = serde_json::from_slice::<Resp>(resp.body())?;
|
||||
println!("downlaods response: {:?}", msg);
|
||||
// shouldn't really return status code
|
||||
Ok((StatusCode::OK, None, resp.body().to_vec()))
|
||||
}
|
||||
// GET detail about a specific app
|
||||
// update a downloaded app: PUT
|
||||
@ -367,7 +412,8 @@ fn serve_paths(
|
||||
.body(serde_json::to_vec(&download_request).unwrap())
|
||||
.send_and_await_response(5)??;
|
||||
|
||||
let response: DownloadResponse = serde_json::from_slice(&response.body())?;
|
||||
let response: Resp = serde_json::from_slice(&response.body())?;
|
||||
println!("got download response: {:?}", response);
|
||||
Ok((StatusCode::OK, None, serde_json::to_vec(&response)?))
|
||||
}
|
||||
// POST /apps/:id/update
|
||||
@ -383,7 +429,7 @@ fn serve_paths(
|
||||
|
||||
// TODO send to downloads:
|
||||
// // actually just install something, we go get it from vfs or ask for it from downloads?
|
||||
|
||||
// yeah this should be obsolete
|
||||
match method {
|
||||
_ => Ok((
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
@ -403,7 +449,24 @@ fn serve_paths(
|
||||
));
|
||||
};
|
||||
|
||||
match crate::utils::install(&package_id, &our.to_string()) {
|
||||
let Ok(version_hash) = get_version_hash(url_params) else {
|
||||
return Ok((
|
||||
StatusCode::BAD_REQUEST,
|
||||
None,
|
||||
format!("Missing version_hash").into_bytes(),
|
||||
));
|
||||
};
|
||||
|
||||
let process_package_id =
|
||||
crate::kinode::process::main::PackageId::from_process_lib(package_id);
|
||||
|
||||
match crate::utils::install(
|
||||
&process_package_id,
|
||||
None,
|
||||
&version_hash,
|
||||
state,
|
||||
&our.to_string(),
|
||||
) {
|
||||
Ok(_) => Ok((StatusCode::CREATED, None, vec![])),
|
||||
Err(e) => Ok((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
|
@ -18,8 +18,8 @@
|
||||
//! - set to automatically update if a new version is available
|
||||
use crate::kinode::process::downloads::{DownloadResponse, ProgressUpdate};
|
||||
use crate::kinode::process::main::{
|
||||
ApisResponse, GetApiResponse, InstallResponse, LocalRequest, LocalResponse, NewPackageRequest,
|
||||
NewPackageResponse, UninstallResponse,
|
||||
ApisResponse, Error, GetApiResponse, InstallPackageRequest, InstallResponse, LocalRequest,
|
||||
LocalResponse, NewPackageRequest, NewPackageResponse, UninstallResponse,
|
||||
};
|
||||
use kinode_process_lib::{
|
||||
await_message, call_init, get_blob, http, println, vfs, Address, LazyLoadBlob, Message,
|
||||
@ -56,6 +56,7 @@ pub enum Req {
|
||||
pub enum Resp {
|
||||
LocalResponse(LocalResponse),
|
||||
Download(DownloadResponse),
|
||||
Err(Error),
|
||||
}
|
||||
|
||||
call_init!(init);
|
||||
@ -118,6 +119,31 @@ fn handle_message(
|
||||
},
|
||||
);
|
||||
}
|
||||
Req::Progress(progress) => {
|
||||
if !message.is_local(&our) {
|
||||
return Err(anyhow::anyhow!("http_server from non-local node"));
|
||||
}
|
||||
println!("progress: {:?}", progress);
|
||||
http_server.ws_push_all_channels(
|
||||
"/",
|
||||
http::server::WsMessageType::Text,
|
||||
LazyLoadBlob {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({
|
||||
"kind": "progress",
|
||||
"data": {
|
||||
"package_id": progress.package_id,
|
||||
"version_hash": progress.version_hash,
|
||||
"downloaded": progress.downloaded,
|
||||
"total": progress.total,
|
||||
}
|
||||
})
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
@ -140,11 +166,8 @@ fn handle_local_request(
|
||||
request: LocalRequest,
|
||||
) -> (LocalResponse, Option<LazyLoadBlob>) {
|
||||
match request {
|
||||
LocalRequest::NewPackage(NewPackageRequest {
|
||||
package_id,
|
||||
metadata,
|
||||
mirror,
|
||||
}) => {
|
||||
LocalRequest::NewPackage(NewPackageRequest { package_id, mirror }) => {
|
||||
// note, use metadata and mirror?
|
||||
let Some(blob) = get_blob() else {
|
||||
return (
|
||||
LocalResponse::NewPackageResponse(NewPackageResponse::NoBlob),
|
||||
@ -152,16 +175,29 @@ fn handle_local_request(
|
||||
);
|
||||
};
|
||||
(
|
||||
match utils::new_package(&package_id.to_process_lib(), state, blob.bytes) {
|
||||
match utils::new_package(package_id, mirror, blob.bytes) {
|
||||
Ok(()) => LocalResponse::NewPackageResponse(NewPackageResponse::Success),
|
||||
Err(_) => LocalResponse::NewPackageResponse(NewPackageResponse::InstallFailed),
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
LocalRequest::Install(package_id) => (
|
||||
match utils::install(&package_id.to_process_lib(), &our.to_string()) {
|
||||
Ok(()) => LocalResponse::InstallResponse(InstallResponse::Success),
|
||||
LocalRequest::Install(InstallPackageRequest {
|
||||
package_id,
|
||||
metadata,
|
||||
version_hash,
|
||||
}) => (
|
||||
match utils::install(
|
||||
&package_id,
|
||||
metadata,
|
||||
&version_hash,
|
||||
state,
|
||||
&our.to_string(),
|
||||
) {
|
||||
Ok(()) => {
|
||||
println!("successfully installed package:");
|
||||
LocalResponse::InstallResponse(InstallResponse::Success)
|
||||
}
|
||||
Err(e) => {
|
||||
println!("error installing package: {e}");
|
||||
LocalResponse::InstallResponse(InstallResponse::Failure)
|
||||
|
@ -77,31 +77,6 @@ impl State {
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// if package_bytes is None, we already have the package downloaded
|
||||
/// in VFS and this is being called to rebuild our process state
|
||||
pub fn add_downloaded_package(
|
||||
&mut self,
|
||||
package_id: &PackageId,
|
||||
mut package_state: PackageState,
|
||||
package_zip_bytes: Option<Vec<u8>>,
|
||||
) -> anyhow::Result<()> {
|
||||
let Some(listing) = self.packages.get_mut(package_id) else {
|
||||
return Err(anyhow::anyhow!("package not found"));
|
||||
};
|
||||
// if passed zip bytes, make drive
|
||||
if let Some(package_bytes) = package_zip_bytes {
|
||||
let manifest_hash = utils::create_package_drive(package_id, package_bytes)?;
|
||||
package_state.manifest_hash = Some(manifest_hash);
|
||||
}
|
||||
|
||||
if let Ok(extracted) = utils::extract_api(package_id) {
|
||||
if extracted {
|
||||
self.installed_apis.insert(package_id.to_owned());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// /// returns True if the package was found and updated, False otherwise
|
||||
// pub fn update_downloaded_package(
|
||||
// &mut self,
|
||||
|
@ -1,11 +1,17 @@
|
||||
use {
|
||||
crate::state::{PackageState, State},
|
||||
crate::VFS_TIMEOUT,
|
||||
crate::{
|
||||
kinode::process::{
|
||||
chain::{Chains, GetAppResponse, OnChainMetadata},
|
||||
downloads::{AddDownloadRequest, DownloadResponse, Downloads},
|
||||
},
|
||||
state::{PackageState, State},
|
||||
VFS_TIMEOUT,
|
||||
},
|
||||
kinode_process_lib::{
|
||||
get_blob, kernel_types as kt, println, vfs, Address, LazyLoadBlob, PackageId, ProcessId,
|
||||
Request,
|
||||
},
|
||||
std::collections::HashSet,
|
||||
std::{collections::HashSet, str::FromStr},
|
||||
};
|
||||
|
||||
// quite annoyingly, we must convert from our gen'd version of PackageId
|
||||
@ -67,38 +73,66 @@ pub fn fetch_package_manifest(
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn fetch_package_metadata(package_id: &PackageId) -> anyhow::Result<kt::Erc721Metadata> {
|
||||
vfs_request(
|
||||
format!("/{package_id}/pkg/metadata.json"), // OK BIG todo: figure out why tf this is not here...
|
||||
vfs::VfsAction::Read,
|
||||
)
|
||||
.send_and_await_response(VFS_TIMEOUT)??;
|
||||
let Some(blob) = get_blob() else {
|
||||
return Err(anyhow::anyhow!("no blob"));
|
||||
pub fn fetch_package_metadata(
|
||||
package_id: &crate::kinode::process::main::PackageId,
|
||||
) -> anyhow::Result<OnChainMetadata> {
|
||||
let chain = Address::from_str("our@chain:app_store:sys")?;
|
||||
let resp = Request::new()
|
||||
.target(chain)
|
||||
.body(serde_json::to_vec(&Chains::GetApp(package_id.clone())).unwrap())
|
||||
.send_and_await_response(5)??;
|
||||
|
||||
let resp = serde_json::from_slice::<GetAppResponse>(&resp.body())?;
|
||||
let app = match resp.app {
|
||||
Some(app) => app,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"No app data found in response from chain:app_store:sys"
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(serde_json::from_slice::<kt::Erc721Metadata>(&blob.bytes)?)
|
||||
let metadata = match app.metadata {
|
||||
Some(metadata) => metadata,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"No metadata found in response from chain:app_store:sys"
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
pub fn new_package(
|
||||
package_id: &PackageId,
|
||||
state: &mut State,
|
||||
package_id: crate::kinode::process::main::PackageId,
|
||||
mirror: bool,
|
||||
bytes: Vec<u8>,
|
||||
) -> anyhow::Result<()> {
|
||||
// add to listings
|
||||
|
||||
// TODO: add? to uhh listings at all?
|
||||
// state.insert_package(package_id, metadata);
|
||||
|
||||
// set the version hash for this new local package
|
||||
let our_version_hash = sha_256_hash(&bytes);
|
||||
let version_hash = sha_256_hash(&bytes);
|
||||
|
||||
let package_state = PackageState {
|
||||
our_version_hash,
|
||||
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against
|
||||
caps_approved: true, // TODO see if we want to auto-approve local installs
|
||||
manifest_hash: None, // generated in the add fn
|
||||
let downloads = Address::from_str("our@downloads:app_store:sys")?;
|
||||
let resp = Request::new()
|
||||
.target(downloads)
|
||||
.body(serde_json::to_vec(&Downloads::AddDownload(
|
||||
AddDownloadRequest {
|
||||
package_id: package_id.clone(),
|
||||
version_hash: version_hash.clone(),
|
||||
mirror,
|
||||
},
|
||||
))?)
|
||||
.blob_bytes(bytes)
|
||||
.send_and_await_response(5)??;
|
||||
|
||||
let download_resp = serde_json::from_slice::<DownloadResponse>(&resp.body())?;
|
||||
println!("got download resp: {:?}", download_resp);
|
||||
if !download_resp.success {
|
||||
return Err(anyhow::anyhow!(
|
||||
"failed to add download: {:?}",
|
||||
download_resp.error
|
||||
));
|
||||
};
|
||||
state.add_downloaded_package(&package_id, package_state, Some(bytes))
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// create a new package drive in VFS and add the package zip to it.
|
||||
@ -138,6 +172,7 @@ pub fn create_package_drive(
|
||||
));
|
||||
};
|
||||
|
||||
// be careful, this is technically a duplicate.. but..
|
||||
// save the zip file itself in VFS for sharing with other nodes
|
||||
// call it <package_id>.zip
|
||||
let zip_path = format!("{}/{}.zip", drive_name, package_id);
|
||||
@ -174,7 +209,8 @@ pub fn extract_api(package_id: &PackageId) -> anyhow::Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// given a `PackageId`, interact with VFS and kernel to get manifest,
|
||||
/// given a `PackageId`, interact with VFS and kernel to get {package_hash}.zip,
|
||||
/// unzip the manifest and pkg,
|
||||
/// grant the capabilities in manifest, then initialize and start
|
||||
/// the processes in manifest.
|
||||
///
|
||||
@ -182,13 +218,49 @@ pub fn extract_api(package_id: &PackageId) -> anyhow::Result<bool> {
|
||||
/// which we can only do if we were the process to create that drive.
|
||||
/// note also that each capability will only be granted if we, the process
|
||||
/// using this function, own that capability ourselves.
|
||||
pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
// get the package manifest
|
||||
let drive_path = format!("/{package_id}/pkg");
|
||||
let manifest = fetch_package_manifest(package_id)?;
|
||||
pub fn install(
|
||||
package_id: &crate::kinode::process::main::PackageId,
|
||||
metadata: Option<OnChainMetadata>,
|
||||
version_hash: &str,
|
||||
state: &mut State,
|
||||
our_node: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let process_package_id = package_id.clone().to_process_lib();
|
||||
let file = vfs::open_file(
|
||||
&format!("/app_store:sys/downloads/{process_package_id}/{version_hash}.zip"),
|
||||
false,
|
||||
Some(VFS_TIMEOUT),
|
||||
)?;
|
||||
let bytes = file.read()?;
|
||||
let manifest_hash = create_package_drive(&process_package_id, bytes)?;
|
||||
|
||||
let package_state = PackageState {
|
||||
our_version_hash: version_hash.to_string(),
|
||||
verified: true, // sideloaded apps are implicitly verified because there is no "source" to verify against
|
||||
caps_approved: true, // TODO see if we want to auto-approve local installs
|
||||
manifest_hash: Some(manifest_hash),
|
||||
};
|
||||
|
||||
if let Ok(extracted) = extract_api(&process_package_id) {
|
||||
if extracted {
|
||||
state.installed_apis.insert(process_package_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.packages
|
||||
.insert(process_package_id.clone(), package_state);
|
||||
|
||||
// get the package manifest
|
||||
let drive_path = format!("/{process_package_id}/pkg");
|
||||
let manifest = fetch_package_manifest(&process_package_id)?;
|
||||
// get wit version from metadata if local or chain if remote.
|
||||
let metadata = if let Some(metadata) = metadata {
|
||||
metadata
|
||||
} else {
|
||||
fetch_package_metadata(&package_id)?
|
||||
};
|
||||
|
||||
// get wit version from metadata:
|
||||
let metadata = fetch_package_metadata(package_id)?;
|
||||
let wit_version = metadata.properties.wit_version;
|
||||
|
||||
// first, for each process in manifest, initialize it
|
||||
@ -201,11 +273,11 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
format!("/{}", entry.process_wasm_path)
|
||||
};
|
||||
let wasm_path = format!("{}{}", drive_path, wasm_path);
|
||||
|
||||
println!("wasm path: {wasm_path}");
|
||||
let process_id = ProcessId::new(
|
||||
Some(&entry.process_name),
|
||||
package_id.package(),
|
||||
package_id.publisher(),
|
||||
process_package_id.package(),
|
||||
process_package_id.publisher(),
|
||||
);
|
||||
|
||||
// kill process if it already exists
|
||||
@ -236,10 +308,11 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("failed to initialize process"));
|
||||
};
|
||||
// println!("kernel process gave us something back.");
|
||||
|
||||
// build initial caps from manifest
|
||||
// // build initial caps from manifest
|
||||
let mut requested_capabilities = parse_capabilities(our_node, &entry.request_capabilities);
|
||||
|
||||
println!("parsed caps.");
|
||||
if entry.request_networking {
|
||||
requested_capabilities.push(kt::Capability {
|
||||
issuer: Address::new(our_node, ("kernel", "distro", "sys")),
|
||||
@ -247,6 +320,8 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
});
|
||||
}
|
||||
|
||||
println!("requested caps: {:?}", requested_capabilities);
|
||||
|
||||
// always grant read/write to their drive, which we created for them
|
||||
requested_capabilities.push(kt::Capability {
|
||||
issuer: Address::new(our_node, ("vfs", "distro", "sys")),
|
||||
@ -265,6 +340,7 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
.to_string(),
|
||||
});
|
||||
|
||||
// NOTE.. this crashes...
|
||||
kernel_request(kt::KernelCommand::GrantCapabilities {
|
||||
target: process_id.clone(),
|
||||
capabilities: requested_capabilities,
|
||||
@ -278,8 +354,8 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
for entry in &manifest {
|
||||
let process_id = ProcessId::new(
|
||||
Some(&entry.process_name),
|
||||
package_id.package(),
|
||||
package_id.publisher(),
|
||||
process_package_id.package(),
|
||||
process_package_id.publisher(),
|
||||
);
|
||||
|
||||
for value in &entry.grant_capabilities {
|
||||
@ -340,6 +416,7 @@ pub fn install(package_id: &PackageId, our_node: &str) -> anyhow::Result<()> {
|
||||
) else {
|
||||
return Err(anyhow::anyhow!("failed to start process"));
|
||||
};
|
||||
println!("started the process!");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use std::{collections::HashMap, str::FromStr};
|
||||
use crate::kinode::process::chain::{
|
||||
Chains, GetAppResponse, OnChainApp, OnChainMetadata, OnChainProperties,
|
||||
};
|
||||
use crate::kinode::process::main::Error;
|
||||
use alloy_primitives::keccak256;
|
||||
use alloy_sol_types::SolEvent;
|
||||
use kinode_process_lib::{
|
||||
@ -93,6 +94,14 @@ fn init(our: Address) {
|
||||
Ok(message) => {
|
||||
if let Err(e) = handle_message(&our, &mut state, &message) {
|
||||
println!("error handling message: {:?}", e);
|
||||
let _ = Response::new()
|
||||
.body(
|
||||
serde_json::to_vec(&Error {
|
||||
reason: e.to_string(),
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,19 +3,21 @@
|
||||
//! manages downloading and sharing of versioned packages.
|
||||
//!
|
||||
use crate::kinode::process::downloads::{
|
||||
AvailableFiles, DownloadRequest, DownloadResponse, Downloads, Entry, ProgressUpdate,
|
||||
AddDownloadRequest, AvailableFiles, DownloadRequest, DownloadResponse, Downloads, Entry,
|
||||
ProgressUpdate,
|
||||
};
|
||||
use crate::kinode::process::main::Error;
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use ft_worker_lib::{spawn_receive_transfer, spawn_send_transfer};
|
||||
use kinode::process::standard::print_to_terminal;
|
||||
use kinode_process_lib::get_blob;
|
||||
use kinode_process_lib::{
|
||||
await_message, call_init,
|
||||
http::{
|
||||
self,
|
||||
client::{HttpClientError, HttpClientResponse},
|
||||
},
|
||||
println,
|
||||
print_to_terminal, println,
|
||||
vfs::{self, DirEntry, Directory, File, SeekFrom},
|
||||
Address, Message, PackageId, ProcessId, Request, Response,
|
||||
};
|
||||
@ -56,23 +58,28 @@ fn init(our: Address) {
|
||||
mirroring: HashSet::new(),
|
||||
};
|
||||
|
||||
// should the files be actively in state? probably not due to updates?
|
||||
// just install the downloads directory.
|
||||
// /log/.log file for debugs
|
||||
vfs::create_drive(our.package_id(), "log", None).expect("could not create /log drive");
|
||||
// /downloads/ for downloads
|
||||
vfs::create_drive(our.package_id(), "downloads", None)
|
||||
.expect("could not create /downloads drive");
|
||||
|
||||
let mut logfile =
|
||||
vfs::open_file("/app_store:sys/.log", true, Some(5)).expect("could not open logfile");
|
||||
vfs::open_file("/app_store:sys/log/.log", true, Some(5)).expect("could not open logfile");
|
||||
logfile
|
||||
.seek(SeekFrom::End(0))
|
||||
.expect("could not seek to end of logfile");
|
||||
|
||||
// FIX this api... first creates (fails if already exists. second shouldn't fail like this... like files.)
|
||||
let _ = vfs::open_dir("/app_store:sys/downloads", true, None);
|
||||
let mut downloads =
|
||||
vfs::open_dir("/app_store:sys/downloads", true, None).expect("could not open downloads");
|
||||
let mut tmp = vfs::open_dir("/tmp", true, None).expect("could not open tmp");
|
||||
vfs::open_dir("/app_store:sys/downloads", false, None).expect("could not open downloads");
|
||||
let _ = vfs::open_dir("/app_store:sys/downloads/tmp", true, None);
|
||||
let mut tmp =
|
||||
vfs::open_dir("/app_store:sys/downloads/tmp", false, None).expect("could not open tmp");
|
||||
|
||||
loop {
|
||||
match await_message() {
|
||||
Err(send_error) => {
|
||||
// TODO handle these based on what they are triggered by
|
||||
println!("got network error: {send_error}");
|
||||
}
|
||||
Ok(message) => {
|
||||
@ -85,6 +92,14 @@ fn init(our: Address) {
|
||||
&mut tmp,
|
||||
) {
|
||||
println!("error handling message: {:?}", e);
|
||||
let _ = Response::new()
|
||||
.body(
|
||||
serde_json::to_vec(&Error {
|
||||
reason: e.to_string(),
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,25 +165,6 @@ fn handle_message(
|
||||
.unwrap(),
|
||||
)
|
||||
.send();
|
||||
|
||||
// http_server.ws_push_all_channels(
|
||||
// "/",
|
||||
// http::server::WsMessageType::Text,
|
||||
// LazyLoadBlob {
|
||||
// mime: Some("application/json".to_string()),
|
||||
// bytes: serde_json::json!({
|
||||
// "kind": "progress",
|
||||
// "data": {
|
||||
// "file_name": file_name,
|
||||
// "chunks_received": chunks_received,
|
||||
// "total_chunks": total_chunks,
|
||||
// }
|
||||
// })
|
||||
// .to_string()
|
||||
// .as_bytes()
|
||||
// .to_vec(),
|
||||
// },
|
||||
// );
|
||||
}
|
||||
Downloads::GetFiles(maybe_id) => {
|
||||
// if not local, throw to the boonies. (could also implement and discovery protocol here..)
|
||||
@ -194,6 +190,35 @@ fn handle_message(
|
||||
.body(serde_json::to_vec(&AvailableFiles { files })?)
|
||||
.send()?;
|
||||
}
|
||||
Downloads::AddDownload(add_req) => {
|
||||
if !message.is_local(our) {
|
||||
// todo figure out full error throwing for http pathways.
|
||||
return Err(anyhow::anyhow!("not local"));
|
||||
}
|
||||
let Some(blob) = get_blob() else {
|
||||
return Err(anyhow::anyhow!("could not get blob"));
|
||||
};
|
||||
let bytes = blob.bytes;
|
||||
|
||||
let package_dir = format!(
|
||||
"{}/{}",
|
||||
downloads.path,
|
||||
add_req.package_id.to_process_lib().to_string()
|
||||
);
|
||||
let _ = vfs::open_dir(&package_dir, true, None);
|
||||
let file = vfs::create_file(
|
||||
&format!("{}/{}.zip", package_dir, add_req.version_hash),
|
||||
None,
|
||||
)?;
|
||||
let _ = file.write(bytes.as_slice())?;
|
||||
|
||||
Response::new()
|
||||
.body(serde_json::to_vec(&Resp::Download(DownloadResponse {
|
||||
success: true,
|
||||
error: None,
|
||||
}))?)
|
||||
.send()?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
|
@ -34,13 +34,13 @@ fn init(our: Address) {
|
||||
let Ok(Ok(Message::Response { body, .. })) =
|
||||
Request::to((our.node(), ("main", "app_store", "sys")))
|
||||
.body(
|
||||
serde_json::to_vec(&LocalRequest::Install(
|
||||
crate::kinode::process::main::PackageId {
|
||||
package_name: package_id.package_name.clone(),
|
||||
publisher_node: package_id.publisher_node.clone(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
b"temp", // serde_json::to_vec(&LocalRequest::Install(
|
||||
// crate::kinode::process::main::PackageId {
|
||||
// package_name: package_id.package_name.clone(),
|
||||
// publisher_node: package_id.publisher_node.clone(),
|
||||
// },
|
||||
// ))
|
||||
// .unwrap(),
|
||||
)
|
||||
.send_and_await_response(5)
|
||||
else {
|
||||
|
@ -7,6 +7,8 @@
|
||||
"request_capabilities": [
|
||||
"http_client:distro:sys",
|
||||
"http_server:distro:sys",
|
||||
"main:app_store:sys",
|
||||
"chain:app_store:sys",
|
||||
"vfs:distro:sys",
|
||||
{
|
||||
"process": "vfs:distro:sys",
|
||||
@ -22,6 +24,35 @@
|
||||
],
|
||||
"public": false
|
||||
},
|
||||
{
|
||||
"process_name": "chain",
|
||||
"process_wasm_path": "/chain.wasm",
|
||||
"on_exit": "Restart",
|
||||
"request_networking": true,
|
||||
"request_capabilities": [
|
||||
"main:app_store:sys",
|
||||
"downloads:app_store:sys",
|
||||
"vfs:distro:sys",
|
||||
"kns_indexer:kns_indexer:sys",
|
||||
"eth:distro:sys",
|
||||
"http_server:distro:sys",
|
||||
"http_client:distro:sys",
|
||||
{
|
||||
"process": "vfs:distro:sys",
|
||||
"params": {
|
||||
"root": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"grant_capabilities": [
|
||||
"http_server:distro:sys",
|
||||
"kns_indexer:kns_indexer:sys",
|
||||
"vfs:distro:sys",
|
||||
"http_client:distro:sys",
|
||||
"eth:distro:sys"
|
||||
],
|
||||
"public": false
|
||||
},
|
||||
{
|
||||
"process_name": "main",
|
||||
"process_wasm_path": "/app_store.wasm",
|
||||
@ -34,6 +65,8 @@
|
||||
"http_server:distro:sys",
|
||||
"http_client:distro:sys",
|
||||
"net:distro:sys",
|
||||
"downloads:app_store:sys",
|
||||
"chain:app_store:sys",
|
||||
"vfs:distro:sys",
|
||||
"kernel:distro:sys",
|
||||
"eth:distro:sys",
|
||||
@ -59,6 +92,7 @@
|
||||
"net:distro:sys",
|
||||
"http_client:distro:sys",
|
||||
"http_server:distro:sys",
|
||||
"kernel:distro:sys",
|
||||
"kns_indexer:kns_indexer:sys",
|
||||
"terminal:terminal:sys",
|
||||
"vfs:distro:sys"
|
||||
|
@ -1,11 +1,13 @@
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||
|
||||
import Header from "./components/Header";
|
||||
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||
|
||||
import StorePage from "./pages/StorePage";
|
||||
import AppPage from "./pages/AppPage";
|
||||
import PublishPage from "./pages/PublishPage";
|
||||
import Header from "./components/Header";
|
||||
import { APP_DETAILS_PATH, PUBLISH_PATH, STORE_PATH } from "./constants/path";
|
||||
import Testing from "./pages/Testing";
|
||||
|
||||
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
@ -18,6 +20,7 @@ function App() {
|
||||
<Router basename={BASE_URL}>
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route path="/testing" element={<Testing />} />
|
||||
<Route path={STORE_PATH} element={<StorePage />} />
|
||||
<Route path={`${APP_DETAILS_PATH}/:id`} element={<AppPage />} />
|
||||
<Route path={PUBLISH_PATH} element={<PublishPage />} />
|
||||
|
42
kinode/packages/app_store/ui/src/pages/Testing.tsx
Normal file
42
kinode/packages/app_store/ui/src/pages/Testing.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { useState } from 'react'
|
||||
import useAppsStore from '../store'
|
||||
|
||||
const Testing: React.FC = () => {
|
||||
const { getDownloads, getInstalledApps, getOurApps, getDownloadsForApp } = useAppsStore()
|
||||
const [result, setResult] = useState<any>(null)
|
||||
const [appId, setAppId] = useState('')
|
||||
|
||||
const handleAction = async (action: () => Promise<any>) => {
|
||||
try {
|
||||
const data = await action()
|
||||
setResult(JSON.stringify(data, null, 2))
|
||||
} catch (error) {
|
||||
setResult(`Error: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Testing Page</h1>
|
||||
<div>
|
||||
<button onClick={() => handleAction(getDownloads)}>Get Downloads</button>
|
||||
<button onClick={() => handleAction(getInstalledApps)}>Get Installed Apps</button>
|
||||
<button onClick={() => handleAction(getOurApps)}>Get Our Apps</button>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={appId}
|
||||
onChange={(e) => setAppId(e.target.value)}
|
||||
placeholder="Enter App ID"
|
||||
/>
|
||||
<button onClick={() => handleAction(() => getDownloadsForApp(appId))}>
|
||||
Get Downloads for App
|
||||
</button>
|
||||
</div>
|
||||
<pre>{result}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Testing
|
@ -24,6 +24,12 @@ interface AppsStore {
|
||||
setMirroring: (app: AppInfo, mirroring: boolean) => Promise<void>
|
||||
setAutoUpdate: (app: AppInfo, autoUpdate: boolean) => Promise<void>
|
||||
rebuildIndex: () => Promise<void>
|
||||
// new.
|
||||
getDownloads: () => Promise<any>
|
||||
getInstalledApps: () => Promise<any>
|
||||
getOurApps: () => Promise<any>
|
||||
getDownloadsForApp: (id: string) => Promise<any>
|
||||
|
||||
}
|
||||
|
||||
const useAppsStore = create<AppsStore>()(
|
||||
@ -166,6 +172,62 @@ const useAppsStore = create<AppsStore>()(
|
||||
throw new Error('Failed to rebuild index')
|
||||
}
|
||||
},
|
||||
|
||||
getDownloads: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads`)
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
console.error('Error fetching downloads:', data)
|
||||
}
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Error fetching downloads:', error)
|
||||
return { error: 'Failed to get downloads' }
|
||||
}
|
||||
},
|
||||
|
||||
getInstalledApps: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/installed`)
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
console.error('Error fetching installed apps:', data)
|
||||
}
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Error fetching installed apps:', error)
|
||||
return { error: 'Failed to get installed apps' }
|
||||
}
|
||||
},
|
||||
|
||||
getOurApps: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/ourapps`)
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
console.error('Error fetching our apps:', data)
|
||||
}
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Error fetching our apps:', error)
|
||||
return { error: 'Failed to get our apps' }
|
||||
}
|
||||
},
|
||||
|
||||
getDownloadsForApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}`)
|
||||
const data = await res.json()
|
||||
if (!res.ok) {
|
||||
console.error(`Error fetching downloads for app ${id}:`, data)
|
||||
}
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error(`Error fetching downloads for app ${id}:`, error)
|
||||
return { error: `Failed to get downloads for app: ${id}` }
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'app_store',
|
||||
|
Loading…
Reference in New Issue
Block a user