Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,16 @@ pub struct ImportArgs {
///
/// 3. App ID in the catalog (for example, `sys.launcher`).
///
/// 4. The word "launcher" to install the latest version of the default launcher.
#[arg()]
/// 4. Author ID starting with "@".
pub path: String,

/// Any additional apps to install.
pub paths: Option<Vec<String>>,
}

#[derive(Debug, Parser)]
pub struct NewArgs {
/// The directory name to create, the new project root and name.
#[arg()]
pub name: String,

/// The programming language to use for the project.
Expand Down Expand Up @@ -320,13 +321,11 @@ pub struct CheatArgs {
/// The command to pass into the app.
///
/// Either an integer or a command listed in firefly.toml.
#[arg()]
pub command: String,

/// The value to pass into the app.
///
/// Either an integer, boolean, or a character.
#[arg()]
pub value: String,

/// Path to the project root.
Expand Down Expand Up @@ -398,7 +397,6 @@ pub enum RuntimeCommands {
#[derive(Debug, Parser)]
pub struct LaunchArgs {
/// The app ID to launch. For example, "lux.snek".
#[arg()]
pub id: String,
}

Expand All @@ -417,6 +415,5 @@ pub struct CatalogListArgs {
#[derive(Debug, Parser)]
pub struct CatalogShowArgs {
/// The app/author ID to get info for. For example, "lux.snek".
#[arg()]
pub id: String,
}
11 changes: 6 additions & 5 deletions src/commands/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,34 @@ const BASE_URL: &str = "https://catalog.fireflyzero.com/";
const LIST_URL: &str = "https://catalog.fireflyzero.com/apps.json";

#[derive(Deserialize)]
struct ShortApp {
id: String,
pub struct ShortApp {
pub id: String,
name: String,
author: String,
short: String,
added: String,
}

#[derive(Deserialize)]
struct App {
pub struct App {
name: String,
author: Author,
short: String,
added: String,
download: String,
pub download: String,
desc: String,
links: Option<HashMap<String, String>>,
categories: Vec<String>,
}

#[derive(Deserialize)]
struct Author {
pub struct Author {
name: String,
pronouns: Option<String>,
links: HashMap<String, String>,
short: String,
about: Option<String>,
pub apps: Option<Vec<ShortApp>>,
}

pub fn cmd_catalog_list(_args: &CatalogListArgs) -> Result<()> {
Expand Down
44 changes: 33 additions & 11 deletions src/commands/import.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,54 @@
use crate::args::ImportArgs;
use crate::commands::catalog::{App, Author};
use crate::crypto::hash_dir;
use crate::file_names::{HASH, META, STATS};
use crate::vfs::init_vfs;
use anyhow::{Context, Result, bail};
use chrono::Datelike;
use firefly_types::{Encode, Meta, validate_id};
use serde::Deserialize;
use std::env::temp_dir;
use std::fs::{self, File, create_dir_all};
use std::io::Read;
use std::path::{Path, PathBuf};
use zip::ZipArchive;

/// API response from the firefly catalog.
///
/// Example: <https://catalog.fireflyzero.com/sys.launcher.json>
#[derive(Deserialize)]
struct CatalogApp {
download: String,
// `ff import`: Install apps from catalog, file, or URL.
pub fn cmd_import(vfs: &Path, args: &ImportArgs) -> Result<()> {
// If an author ID is given, download all system apps.
if args.path.starts_with('@') {
let url = format!("https://catalog.fireflyzero.com/{}.json", args.path);
let resp = ureq::get(&url).call().context("send HTTP request")?;
let mut body = resp.into_body().into_reader();
let author: Author = serde_json::from_reader(&mut body).context("parse JSON")?;
println!("⏳️ installing all apps of the author...");
if let Some(apps) = author.apps {
for app in apps {
import_app(vfs, &app.id).context("download a system app")?;
}
}
println!("✅ all apps of the author have been installed");
return Ok(());
}

// Download explicitly listed apps.
import_app(vfs, &args.path)?;
if let Some(paths) = &args.paths {
for path in paths {
import_app(vfs, path)?;
}
}
Ok(())
}

pub fn cmd_import(vfs: &Path, args: &ImportArgs) -> Result<()> {
let path = fetch_archive(&args.path).context("download ROM archive")?;
/// Download the given app. The path can be local file, URL, or app ID.
fn import_app(vfs: &Path, raw_path: &str) -> Result<()> {
let path = fetch_archive(raw_path).context("download ROM archive")?;
let file = File::open(path).context("open archive file")?;
let mut archive = ZipArchive::new(file).context("open archive")?;

let meta_raw = read_meta_raw(&mut archive)?;
let meta = Meta::decode(&meta_raw).context("parse meta")?;
if !id_matches(&args.path, &meta) {
if !id_matches(raw_path, &meta) {
bail!(
"app ID ({}.{}) doesn't match the expected ID",
meta.author_id,
Expand Down Expand Up @@ -97,7 +118,7 @@ fn fetch_archive(path: &str) -> Result<PathBuf> {
let url = format!("https://catalog.fireflyzero.com/{path}.json");
let resp = ureq::get(&url).call().context("send HTTP request")?;
let mut body = resp.into_body().into_reader();
let app: CatalogApp = serde_json::from_reader(&mut body).context("parse JSON")?;
let app: App = serde_json::from_reader(&mut body).context("parse JSON")?;
// TODO(@orsinium): the download link might be a download page,
// not the actual ROM file.
path = app.download;
Expand Down Expand Up @@ -319,6 +340,7 @@ mod tests {
let path_str = archive_path.to_str().unwrap();
let args = ImportArgs {
path: path_str.to_string(),
paths: None,
};
cmd_import(&vfs2, &args).unwrap();

Expand Down
Loading