mirror of
https://github.com/storytold/craft-launcher.git
synced 2026-11-09 20:04:34 +00:00
Support Studio release assets, skip ArtCraft on FreeBSD, and list as app #01
- Support ArtCraft Studio's published MSI and DMG asset formats in parse_asset_name while preserving existing asset naming - Add real artcraft-release.json fixture and test asset selection across desktop platforms - Add runs_on to CraftApp and filter out ArtCraft Studio on FreeBSD across backend and UI - List ArtCraft Studio as app #01 in the README Craft App Suite table and xtask APPS array
This commit is contained in:
9 files changed
+230
-41
No files matched your search
@@ -15,7 +15,7 @@
|
||||
|
||||
<p align="center">
|
||||
<b>One place to install, update and open every app in the Craft App Suite, rebuilt in pure Rust.</b><br>
|
||||
PhotoCraft, LightCraft, FilmCraft, DesignCraft, VectorCraft, PdfCraft, EffectCraft, WordCraft,<br>
|
||||
ArtCraft Studio, PhotoCraft, LightCraft, FilmCraft, DesignCraft, VectorCraft, PdfCraft, EffectCraft, WordCraft,<br>
|
||||
DeckCraft, GridCraft, CADCraft, SoundCraft, FigCraft, Craft Anything, TextureCraft, MaterialCraft and the community app SolidCraft: the right build for your computer, kept up to date.
|
||||
</p>
|
||||
|
||||
@@ -264,27 +264,26 @@ ArtCraft Launcher installs the **Craft App Suite**: free, open-source creative t
|
||||
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
|
||||
stand on its own.
|
||||
|
||||
| | App | What it's for | Code | Learn more |
|
||||
|:-:|---|---|---|---|
|
||||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/figcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png" alt="" width="32" height="32"> | **FigCraft** | Interface design and prototyping | [GitHub](https://github.com/storytold/figcraft) | [Website](https://getartcraft.com/apps/figcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/wordcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.wordcraft.png" alt="" width="32" height="32"> | **WordCraft** | Word processing and documents | [GitHub](https://github.com/storytold/wordcraft) | [Website](https://getartcraft.com/apps/wordcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/soundcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.soundcraft.png" alt="" width="32" height="32"> | **SoundCraft** | Audio recording and editing | [GitHub](https://github.com/storytold/soundcraft) | [Website](https://getartcraft.com/apps/soundcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/deckcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.deckcraft.png" alt="" width="32" height="32"> | **DeckCraft** | Presentations and slide decks | [GitHub](https://github.com/storytold/deckcraft) | [Website](https://getartcraft.com/apps/deckcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/cadcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.cadcraft.png" alt="" width="32" height="32"> | **CADCraft** | Computer-aided design | [GitHub](https://github.com/storytold/cadcraft) | [Website](https://getartcraft.com/apps/cadcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/gridcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png" alt="" width="32" height="32"> | **GridCraft** | Spreadsheets and data | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/texturecraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.texturecraft.png" alt="" width="32" height="32"> | **TextureCraft** | 3D texture painting | [GitHub](https://github.com/storytold/texturecraft) | [Website](https://getartcraft.com/apps/texturecraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/materialcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.materialcraft.png" alt="" width="32" height="32"> | **MaterialCraft** | Procedural material design | [GitHub](https://github.com/storytold/materialcraft) | [Project page](https://github.com/storytold/materialcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/craft-anything/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.craft-anything.png" alt="" width="32" height="32"> | **Craft Anything** | See how any software works, then build your own | [GitHub](https://github.com/storytold/craft-anything) | [Website](https://getartcraft.com/apps/craft-anything) |
|
||||
| <img src="https://raw.githubusercontent.com/tangency-labs/solidcraft/main/assets/icons/app/linux/exports/hicolor/64x64/apps/io.tangencylabs.solidcraft.png" alt="" width="32" height="32"> | **SolidCraft** | Parametric 3D CAD by Tangency Labs (community app) | [GitHub](https://github.com/tangency-labs/solidcraft) | |
|
||||
|
||||
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
|
||||
| | App | What it's for | Code | Learn more |
|
||||
|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------|:------------------------------------------------------|:------------------------------------------------------|:-----------------------------------------------------------|
|
||||
| <img src="https://raw.githubusercontent.com/storytold/artcraft/main/crates/desktop/artcraft/icons/64x64.png" alt="" width="32" height="32"> | **ArtCraft Studio** | AI image and video creation | [GitHub](https://github.com/storytold/artcraft) | [Website](https://getartcraft.com) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/figcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png" alt="" width="32" height="32"> | **FigCraft** | Interface design and prototyping | [GitHub](https://github.com/storytold/figcraft) | [Website](https://getartcraft.com/apps/figcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/wordcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.wordcraft.png" alt="" width="32" height="32"> | **WordCraft** | Word processing and documents | [GitHub](https://github.com/storytold/wordcraft) | [Website](https://getartcraft.com/apps/wordcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/soundcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.soundcraft.png" alt="" width="32" height="32"> | **SoundCraft** | Audio recording and editing | [GitHub](https://github.com/storytold/soundcraft) | [Website](https://getartcraft.com/apps/soundcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/deckcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.deckcraft.png" alt="" width="32" height="32"> | **DeckCraft** | Presentations and slide decks | [GitHub](https://github.com/storytold/deckcraft) | [Website](https://getartcraft.com/apps/deckcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/cadcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.cadcraft.png" alt="" width="32" height="32"> | **CADCraft** | Computer-aided design | [GitHub](https://github.com/storytold/cadcraft) | [Website](https://getartcraft.com/apps/cadcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/gridcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png" alt="" width="32" height="32"> | **GridCraft** | Spreadsheets and data | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/texturecraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.texturecraft.png" alt="" width="32" height="32"> | **TextureCraft** | 3D texture painting | [GitHub](https://github.com/storytold/texturecraft) | [Website](https://getartcraft.com/apps/texturecraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/materialcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.materialcraft.png" alt="" width="32" height="32"> | **MaterialCraft** | Procedural material design | [GitHub](https://github.com/storytold/materialcraft) | [Project page](https://github.com/storytold/materialcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/craft-anything/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.craft-anything.png" alt="" width="32" height="32"> | **Craft Anything** | See how any software works, then build your own | [GitHub](https://github.com/storytold/craft-anything) | [Website](https://getartcraft.com/apps/craft-anything) |
|
||||
| <img src="https://raw.githubusercontent.com/tangency-labs/solidcraft/main/assets/icons/app/linux/exports/hicolor/64x64/apps/io.tangencylabs.solidcraft.png" alt="" width="32" height="32"> | **SolidCraft** | Parametric 3D CAD by Tangency Labs (community app) | [GitHub](https://github.com/tangency-labs/solidcraft) | |
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
@@ -75,6 +75,14 @@ impl CraftApp {
|
||||
kind != crate::PackageKind::Dmg || self.mac_team.is_some()
|
||||
}
|
||||
|
||||
/// Whether this app runs on the given operating system.
|
||||
pub fn runs_on(&self, os: crate::Os) -> bool {
|
||||
match os {
|
||||
crate::Os::FreeBsd => self.id != "artcraft",
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Bundle ids this app may be installed under, current first.
|
||||
pub fn bundle_ids(&self) -> impl Iterator<Item = String> + '_ {
|
||||
std::iter::once(self.bundle_id()).chain(self.former_bundles.iter().map(|b| b.to_string()))
|
||||
@@ -398,4 +406,16 @@ mod tests {
|
||||
assert_eq!(pdf.website(), "https://getartcraft.com/apps/pdfcraft");
|
||||
assert_eq!(CraftApp::by_id("wordcraft").unwrap().website(), "https://getartcraft.com/apps/wordcraft");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artcraft_studio_does_not_run_on_freebsd() {
|
||||
let studio = CraftApp::by_id("artcraft").unwrap();
|
||||
assert!(!studio.runs_on(crate::Os::FreeBsd));
|
||||
assert!(studio.runs_on(crate::Os::Linux));
|
||||
assert!(studio.runs_on(crate::Os::MacOs));
|
||||
assert!(studio.runs_on(crate::Os::Windows));
|
||||
|
||||
let photo = CraftApp::by_id("photocraft").unwrap();
|
||||
assert!(photo.runs_on(crate::Os::FreeBsd));
|
||||
}
|
||||
}
|
||||
@@ -205,8 +205,16 @@ impl AssetName {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads `<stem>-<version>-<os>-<arch>[-portable].<ext>`; `None` for checksums, zsync files, CLI
|
||||
/// and web bundles, or anything else.
|
||||
fn strip_stem_prefix<'a>(base: &'a str, stem: &str, sep: char) -> Option<&'a str> {
|
||||
if base.len() > stem.len() && base[..stem.len()].eq_ignore_ascii_case(stem) && base.as_bytes()[stem.len()] == sep as u8 {
|
||||
Some(&base[stem.len() + 1..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads `<stem>-<version>-<os>-<arch>[-portable].<ext>` or Studio's `<Stem>_<version>_<arch>[_<locale>].<ext>`;
|
||||
/// `None` for checksums, zsync files, CLI and web bundles, or anything else.
|
||||
pub fn parse_asset_name(name: &str, stems: &[&str]) -> Option<AssetName> {
|
||||
const KINDS: [PackageKind; 9] = [
|
||||
PackageKind::PortableZip,
|
||||
@@ -221,14 +229,35 @@ pub fn parse_asset_name(name: &str, stems: &[&str]) -> Option<AssetName> {
|
||||
];
|
||||
let (kind, base) = KINDS.iter().find_map(|k| name.strip_suffix(k.suffix()).map(|b| (*k, b)))?;
|
||||
for stem in stems {
|
||||
let Some(rest) = base.strip_prefix(stem).and_then(|r| r.strip_prefix('-')) else { continue };
|
||||
// rest = <version>-<os>-<arch>; os and arch never contain '-', versions may.
|
||||
let mut parts = rest.rsplitn(3, '-');
|
||||
let (arch, os, version) = (parts.next()?, parts.next()?, parts.next()?);
|
||||
if crate::version::parse(version).is_none() || !version.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
continue;
|
||||
if let Some(rest) = strip_stem_prefix(base, stem, '-') {
|
||||
// rest = <version>-<os>-<arch>; os and arch never contain '-', versions may.
|
||||
let mut parts = rest.rsplitn(3, '-');
|
||||
let (Some(arch), Some(os), Some(version)) = (parts.next(), parts.next(), parts.next()) else { continue };
|
||||
if crate::version::parse(version).is_none() || !version.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
continue;
|
||||
}
|
||||
return Some(AssetName { stem: (*stem).to_string(), version: version.to_string(), token: format!("{os}-{arch}"), kind });
|
||||
}
|
||||
if let Some(rest) = strip_stem_prefix(base, stem, '_') {
|
||||
// Studio / Tauri format: <version>_<arch>[_<locale>]
|
||||
let mut parts = rest.split('_');
|
||||
let (Some(version), Some(arch_token)) = (parts.next(), parts.next()) else { continue };
|
||||
if crate::version::parse(version).is_none() || !version.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
continue;
|
||||
}
|
||||
let token = match (kind, arch_token) {
|
||||
(PackageKind::Dmg, "universal") => Some("macos-universal".to_string()),
|
||||
(PackageKind::Dmg, "x86_64" | "x64") => Some("macos-x86_64".to_string()),
|
||||
(PackageKind::Dmg, "aarch64" | "arm64") => Some("macos-aarch64".to_string()),
|
||||
(PackageKind::Msi | PackageKind::PortableZip, "x86_64" | "x64") => Some("windows-x64".to_string()),
|
||||
(PackageKind::Msi | PackageKind::PortableZip, "x86") => Some("windows-x86".to_string()),
|
||||
(PackageKind::Msi | PackageKind::PortableZip, "arm64" | "aarch64") => Some("windows-arm64".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(token) = token {
|
||||
return Some(AssetName { stem: (*stem).to_string(), version: version.to_string(), token, kind });
|
||||
}
|
||||
}
|
||||
return Some(AssetName { stem: (*stem).to_string(), version: version.to_string(), token: format!("{os}-{arch}"), kind });
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -304,6 +333,27 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn artcraft_studio_selects_published_desktop_assets() {
|
||||
let release =
|
||||
crate::release::parse_github_release(include_str!("../tests/fixtures/artcraft-release.json"), "storytold/artcraft").unwrap();
|
||||
assert_eq!(release.version, Some(crate::version::Version::new(0, 41, 1)));
|
||||
for (os, arch, kind, expected) in [
|
||||
(Os::Windows, Arch::X64, PackageKind::Msi, "ArtCraft_0.41.1_x64_en-US.msi"),
|
||||
(Os::MacOs, Arch::Arm64, PackageKind::Dmg, "ArtCraft_0.41.1_universal.dmg"),
|
||||
(Os::MacOs, Arch::X64, PackageKind::Dmg, "ArtCraft_0.41.1_universal.dmg"),
|
||||
(Os::Linux, Arch::X64, PackageKind::AppImage, "artcraft-0.41.1-linux-x86_64.AppImage"),
|
||||
(Os::Linux, Arch::X64, PackageKind::Flatpak, "artcraft-0.41.1-linux-x86_64.flatpak"),
|
||||
(Os::Linux, Arch::X64, PackageKind::TarGz, "artcraft-0.41.1-linux-x86_64.tar.gz"),
|
||||
(Os::Linux, Arch::Arm64, PackageKind::TarGz, "artcraft-0.41.1-linux-aarch64.tar.gz"),
|
||||
] {
|
||||
let platform = Platform { os, arch };
|
||||
let (asset, selected) = pick_asset(&release, &["artcraft"], platform, &[kind]).unwrap();
|
||||
assert_eq!(asset.name, expected);
|
||||
assert_eq!(selected, kind);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freebsd_picks_the_tarball_for_the_processor() {
|
||||
// SoundCraft v0.10.0's GitHub metadata, trimmed to its FreeBSD tarballs.
|
||||
@@ -382,6 +432,15 @@ mod tests {
|
||||
assert!(parse_asset_name("figcraft-0.5.1-linux-x86_64.tar.gz", &["figcraft"]).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reads_artcraft_studio_asset_names() {
|
||||
let dmg = parse_asset_name("ArtCraft_0.41.1_universal.dmg", &["artcraft"]).unwrap();
|
||||
assert_eq!((dmg.version.as_str(), dmg.token.as_str(), dmg.kind), ("0.41.1", "macos-universal", PackageKind::Dmg));
|
||||
|
||||
let msi = parse_asset_name("ArtCraft_0.41.1_x64_en-US.msi", &["artcraft"]).unwrap();
|
||||
assert_eq!((msi.version.as_str(), msi.token.as_str(), msi.kind), ("0.41.1", "windows-x64", PackageKind::Msi));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picks_the_native_build_first_and_falls_back_to_emulation() {
|
||||
let r = release(PHOTO);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"tag_name": "artcraft-v0.41.1",
|
||||
"name": "ArtCraft v0.41.1",
|
||||
"html_url": "https://github.com/storytold/artcraft/releases/tag/artcraft-v0.41.1",
|
||||
"published_at": "2026-11-07T02:49:42Z",
|
||||
"prerelease": false,
|
||||
"draft": false,
|
||||
"assets": [
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-aarch64.AppImage",
|
||||
"size": 177187336,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-aarch64.AppImage",
|
||||
"digest": "sha256:31a4071758ce7989c6548e3976839aaf6010be414d0a1b5cc9a913537bbc88de"
|
||||
},
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-aarch64.flatpak",
|
||||
"size": 40561288,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-aarch64.flatpak",
|
||||
"digest": "sha256:8f899c94f0c41fb1553c66fc6b4d3ac0763f4c034bfb06fe9f0d60426c153f7b"
|
||||
},
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-aarch64.tar.gz",
|
||||
"size": 49710171,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-aarch64.tar.gz",
|
||||
"digest": "sha256:cb780e02e87234927d339c87dee03b224e1641320758be813d31d442d9a691ae"
|
||||
},
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-riscv64.tar.gz",
|
||||
"size": 48491446,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-riscv64.tar.gz",
|
||||
"digest": "sha256:d32edc5ac3320f6789154fcd89552f0650d6d396c8cb576d1197d1e11bbc0745"
|
||||
},
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-x86_64.AppImage",
|
||||
"size": 182495736,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-x86_64.AppImage",
|
||||
"digest": "sha256:bb32657b6e81e8e47da44ca4e764b1af14d77d8690b248a263a4acd1218b7d2d"
|
||||
},
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-x86_64.flatpak",
|
||||
"size": 41755576,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-x86_64.flatpak",
|
||||
"digest": "sha256:7351e1bad48006b251ab943372fa15f1829d8b92ca944adb7f312b4e5c7e9912"
|
||||
},
|
||||
{
|
||||
"name": "artcraft-0.41.1-linux-x86_64.tar.gz",
|
||||
"size": 49365185,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/artcraft-0.41.1-linux-x86_64.tar.gz",
|
||||
"digest": "sha256:149f9748ea33553177c021e1ade47a195fad25b881129d4178fe5bd922a94eb4"
|
||||
},
|
||||
{
|
||||
"name": "ArtCraft_0.41.1_universal.dmg",
|
||||
"size": 94884865,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/ArtCraft_0.41.1_universal.dmg",
|
||||
"digest": "sha256:89009b8983d88919481ac936e2ada0409dff0e0fc73f487cce3e7324699866a6"
|
||||
},
|
||||
{
|
||||
"name": "ArtCraft_0.41.1_x64_en-US.msi",
|
||||
"size": 47058944,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/ArtCraft_0.41.1_x64_en-US.msi",
|
||||
"digest": "sha256:450b15e53d8ccc9afde2d3644a547ab5f900167b837b861571ea4c833166417d"
|
||||
},
|
||||
{
|
||||
"name": "ArtCraft_universal.app.tar.gz",
|
||||
"size": 94791060,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/ArtCraft_universal.app.tar.gz",
|
||||
"digest": "sha256:d1e4e9623ebf506fbc8d7476b69b2db957125761e7908624b3eb031ce044413e"
|
||||
},
|
||||
{
|
||||
"name": "SHA256SUMS.txt",
|
||||
"size": 4108,
|
||||
"browser_download_url": "https://github.com/storytold/artcraft/releases/download/artcraft-v0.41.1/SHA256SUMS.txt",
|
||||
"digest": "sha256:5450801c2342f8ea05d9aa71cd31cb4b02462cebd9cab7abbe8884e17de6f075"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -246,7 +246,7 @@ impl NativeBackend {
|
||||
let pre = options.prereleases;
|
||||
let feed = if pre { None } else { load_feed(&self.paths) };
|
||||
let now = crate::now();
|
||||
for app in &SUITE {
|
||||
for app in SUITE.iter().filter(|a| self.platform.is_none_or(|p| a.runs_on(p.os))) {
|
||||
let answer = match feed.as_ref().map(|f| f.answer(app, now)) {
|
||||
Some(FeedAnswer::Fresh(Some(r))) => Some((LatestRelease::Found(r), Origin::ArtCraftApi)),
|
||||
Some(FeedAnswer::Fresh(None)) => Some((LatestRelease::None, Origin::ArtCraftApi)),
|
||||
@@ -268,7 +268,7 @@ impl NativeBackend {
|
||||
let paths = self.paths.clone();
|
||||
let platform = self.platform;
|
||||
std::thread::spawn(move || {
|
||||
for app in &SUITE {
|
||||
for app in SUITE.iter().filter(|a| platform.is_none_or(|p| a.runs_on(p.os))) {
|
||||
report_partial(app, &shared, &paths, platform);
|
||||
}
|
||||
});
|
||||
@@ -293,12 +293,12 @@ impl NativeBackend {
|
||||
http::clean_downloads(&paths.downloads(), Duration::from_secs(30 * 86_400), Duration::from_secs(7 * 86_400));
|
||||
let snapshot = crate::detect::Snapshot::take();
|
||||
let manifest = shared.manifest.lock().unwrap_or_else(PoisonError::into_inner).clone();
|
||||
for app in &SUITE {
|
||||
for app in SUITE.iter().filter(|a| platform.is_none_or(|p| a.runs_on(p.os))) {
|
||||
let found = crate::detect::detect(app, &manifest, &snapshot);
|
||||
publish_detected(app, found, &shared, platform);
|
||||
}
|
||||
// Downloads paused (or cut off by quitting) in an earlier run.
|
||||
for app in &SUITE {
|
||||
for app in SUITE.iter().filter(|a| platform.is_none_or(|p| a.runs_on(p.os))) {
|
||||
report_partial(app, &shared, &paths, platform);
|
||||
}
|
||||
drop(busy);
|
||||
@@ -310,7 +310,7 @@ impl NativeBackend {
|
||||
/// day old, or no answer at all), at most once an hour per app unless `force`.
|
||||
fn check(&self, force: bool) {
|
||||
let (Some(source), Some(http)) = (self.source.clone(), self.http.clone()) else {
|
||||
for app in &SUITE {
|
||||
for app in SUITE.iter().filter(|a| self.platform.is_none_or(|p| a.runs_on(p.os))) {
|
||||
self.shared.emit(Event::Latest {
|
||||
app: app.id.to_string(),
|
||||
latest: LatestRelease::Failed("secure connections aren't available".into()),
|
||||
@@ -339,17 +339,23 @@ impl NativeBackend {
|
||||
shared.emit(Event::Notices { announcements: f.announcements.clone(), alerts: f.alerts.clone() });
|
||||
}
|
||||
let now = crate::now();
|
||||
let queue = Mutex::new(SUITE.iter().chain(std::iter::once(&LAUNCHER)).collect::<Vec<&CraftApp>>());
|
||||
let queue = Mutex::new(
|
||||
SUITE
|
||||
.iter()
|
||||
.filter(|a| platform.is_none_or(|p| a.runs_on(p.os)))
|
||||
.chain(std::iter::once(&LAUNCHER))
|
||||
.collect::<Vec<&CraftApp>>(),
|
||||
);
|
||||
let sources = sweep(&queue, &mirror, feed.as_ref(), fallback, &source, &shared, &paths, (platform, &options), force, now);
|
||||
source.save();
|
||||
{
|
||||
let answered = shared.answered.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
if SUITE.iter().all(|a| answered.contains(a.id)) {
|
||||
if SUITE.iter().filter(|a| platform.is_none_or(|p| a.runs_on(p.os))).all(|a| answered.contains(a.id)) {
|
||||
shared.loaded.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
// A newer release changes which file an install would use.
|
||||
for app in &SUITE {
|
||||
for app in SUITE.iter().filter(|a| platform.is_none_or(|p| a.runs_on(p.os))) {
|
||||
report_partial(app, &shared, &paths, platform);
|
||||
}
|
||||
let source_used = source_used_of(&sources);
|
||||
@@ -364,6 +370,9 @@ impl NativeBackend {
|
||||
fn start_job(&self, app_id: &str, work: Work) {
|
||||
let uninstall = matches!(work, Work::Uninstall);
|
||||
let Some(app) = CraftApp::by_id(app_id) else { return };
|
||||
if self.platform.is_some_and(|p| !app.runs_on(p.os)) {
|
||||
return;
|
||||
}
|
||||
log::info!("{}: starting {} job", app.name, if uninstall { "uninstall" } else { "install/update" });
|
||||
let stop = Arc::new(Stop::default());
|
||||
let restarting = {
|
||||
|
||||
@@ -260,7 +260,7 @@ impl LauncherUi {
|
||||
Self {
|
||||
backend,
|
||||
settings,
|
||||
entries: SUITE.iter().map(AppEntry::new).collect(),
|
||||
entries: SUITE.iter().filter(|a| platform.is_none_or(|p| a.runs_on(p.os))).map(AppEntry::new).collect(),
|
||||
caps,
|
||||
platform,
|
||||
checking: false,
|
||||
@@ -926,6 +926,7 @@ impl LauncherUi {
|
||||
.filter(|(_, e)| {
|
||||
self.in_filter(e, self.settings.filter)
|
||||
&& !(self.settings.hide_community_apps && e.app.kind == artcraft_launcher_core::catalog::AppKind::Community)
|
||||
&& self.platform.is_none_or(|p| e.app.runs_on(p.os))
|
||||
&& (needle.is_empty() || e.app.name.to_lowercase().contains(&needle) || e.app.tagline.to_lowercase().contains(&needle))
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -247,7 +247,12 @@ impl LauncherUi {
|
||||
fn neighbour_apps(&self, id: &str) -> (Option<String>, Option<String>) {
|
||||
let mut ids: Vec<&str> = self.visible_ids().into_iter().filter(|i| AppDetails::by_id(i).is_some()).collect();
|
||||
if !ids.contains(&id) {
|
||||
ids = artcraft_launcher_core::SUITE.iter().map(|a| a.id).filter(|i| AppDetails::by_id(i).is_some()).collect();
|
||||
ids = artcraft_launcher_core::SUITE
|
||||
.iter()
|
||||
.filter(|a| self.platform.is_none_or(|p| a.runs_on(p.os)))
|
||||
.map(|a| a.id)
|
||||
.filter(|i| AppDetails::by_id(i).is_some())
|
||||
.collect();
|
||||
}
|
||||
let Some(at) = ids.iter().position(|i| *i == id) else { return (None, None) };
|
||||
if ids.len() < 2 {
|
||||
|
||||
@@ -788,6 +788,25 @@ fn a_freebsd_version_row_sizes_the_tarball() {
|
||||
assert!(facts.ends_with(", 2026 · 140 MB"), "{facts:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freebsd_skips_artcraft_studio() {
|
||||
let freebsd = Platform { os: Os::FreeBsd, arch: Arch::X64 };
|
||||
let (h, _) = harness_full(
|
||||
events(),
|
||||
ViewMode::Grid,
|
||||
egui::Theme::Dark,
|
||||
true,
|
||||
1.0,
|
||||
artcraft_launcher_ui_egui::TitleBar::Native,
|
||||
Rc::default(),
|
||||
Some(freebsd),
|
||||
None,
|
||||
);
|
||||
let ids = h.state().as_ref().unwrap().visible_ids_for_test();
|
||||
assert!(!ids.contains(&"artcraft"), "ArtCraft Studio must be skipped on FreeBSD: {ids:?}");
|
||||
assert!(ids.contains(&"photocraft"), "PhotoCraft remains visible on FreeBSD");
|
||||
}
|
||||
|
||||
/// The launcher's own list in Settings sizes the launcher's download for this machine.
|
||||
#[test]
|
||||
fn the_launcher_version_list_sizes_the_launchers_download() {
|
||||
|
||||
@@ -19,6 +19,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Every Craft App: (id = folder name, display name). Keep in step with `catalog::SUITE`.
|
||||
const APPS: &[(&str, &str)] = &[
|
||||
("artcraft", "ArtCraft Studio"),
|
||||
("photocraft", "PhotoCraft"),
|
||||
("vectorcraft", "VectorCraft"),
|
||||
("filmcraft", "FilmCraft"),
|
||||
|
||||
Reference in new issue
Block a user