Skip to content

Commit

Permalink
#18 Fix handling of main REAPER installations at non-default locations
Browse files Browse the repository at this point in the history
  • Loading branch information
helgoboss committed Dec 26, 2024
1 parent c9b0bb5 commit 78e0a8c
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 24 deletions.
7 changes: 5 additions & 2 deletions cli/src/commands/install/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub async fn install(args: InstallArgs) -> anyhow::Result<()> {
skip_failed_packages: args.skip_failed_packages,
recipe: None,
selected_features: Default::default(),
install_reaper: None,
install_reapack: None,
};
let (interaction_sender, interaction_receiver) = tokio::sync::broadcast::channel(10);
Expand All @@ -104,9 +105,11 @@ pub async fn install(args: InstallArgs) -> anyhow::Result<()> {
let installer = Installer::new(installer_new_args).await?;
// Show REAPER EULA if necessary
let skip_license_prompts = args.non_interactive || args.accept_licenses;
let resolved_config = installer.resolved_config();
if !skip_license_prompts
&& installer.reaper_is_installable()
&& !installer.resolved_config().reaper_exe_exists
&& !resolved_config.reaper_exe_exists
&& resolved_config.reaper_is_installable
&& resolved_config.install_reaper
&& !confirm_license().await?
{
println!("You haven't agreed to the license terms. Exiting.");
Expand Down
7 changes: 7 additions & 0 deletions core/bindings/InstallerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ recipe?: Recipe,
* Features not contained in the recipe will be ignored.
*/
selected_features: Array<string>,
/**
* Whether to install REAPER or not if ReaBoot detects that it's missing (by default true).
*
* Important because ReaBoot's detection can only detect main installations in the default
* location.
*/
install_reaper?: boolean,
/**
* Update REAPER if there's a new version available (by default false).
*/
Expand Down
10 changes: 10 additions & 0 deletions core/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ pub struct InstallerConfig {
///
/// Features not contained in the recipe will be ignored.
pub selected_features: HashSet<String>,
/// Whether to install REAPER or not if ReaBoot detects that it's missing (by default true).
///
/// Important because ReaBoot's detection can only detect main installations in the default
/// location.
#[ts(optional)]
pub install_reaper: Option<bool>,
/// Update REAPER if there's a new version available (by default false).
pub update_reaper: bool,
/// Install ReaPack (by default true).
Expand Down Expand Up @@ -135,6 +141,8 @@ pub struct ResolvedInstallerConfig {
/// Whether the resolved REAPER resource directory belongs to the main REAPER installation
/// or to a portable REAPER installation.
pub portable: bool,
/// Whether ReaBoot would be capable of installing REAPER automatically.
pub reaper_is_installable: bool,
/// Resolved REAPER platform.
pub platform: ReaperPlatform,
/// Resolved package URLs.
Expand All @@ -151,6 +159,8 @@ pub struct ResolvedInstallerConfig {
pub dry_run: bool,
pub reaper_version: VersionRef,
pub skip_failed_packages: bool,
/// Whether to install REAPER if necessary.
pub install_reaper: bool,
pub update_reaper: bool,
pub install_reapack: bool,
#[ts(optional)]
Expand Down
12 changes: 4 additions & 8 deletions core/src/installer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,6 @@ impl<L: InstallerListener> Installer<L> {
Ok(installer)
}

/// Returns whether ReaBoot is capable of installing REAPER automatically.
pub fn reaper_is_installable(&self) -> bool {
// An automated main installation is only possible on Windows
self.resolved_config.portable || cfg!(target_os = "windows")
}

pub fn resolved_config(&self) -> &ResolvedInstallerConfig {
&self.resolved_config
}
Expand Down Expand Up @@ -768,7 +762,9 @@ impl<L: InstallerListener> Installer<L> {
async fn download_and_prepare_reaper_if_necessary(
&self,
) -> anyhow::Result<Option<ReaperPreparationOutcome>> {
if self.resolved_config.reaper_exe_exists && !self.resolved_config.update_reaper {
if (self.resolved_config.reaper_exe_exists && !self.resolved_config.update_reaper)
|| (!self.resolved_config.reaper_exe_exists && !self.resolved_config.install_reaper)
{
return Ok(None);
}
// Check for latest REAPER version
Expand Down Expand Up @@ -837,7 +833,7 @@ impl<L: InstallerListener> Installer<L> {
&self,
reaper_download: ToolDownload,
) -> anyhow::Result<ReaperPreparationOutcome> {
if !self.reaper_is_installable() {
if !self.resolved_config.reaper_is_installable {
let outcome = ReaperPreparationOutcome::InstallManually(reaper_download);
return Ok(outcome);
}
Expand Down
5 changes: 4 additions & 1 deletion core/src/reaboot_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub async fn resolve_config(config: InstallerConfig) -> anyhow::Result<ResolvedI
reaper_resource_dir,
reaper_exe,
portable,
// An automated main installation is only possible on Windows
reaper_is_installable: portable || cfg!(target_os = "windows"),
platform: reaper_platform,
package_urls: package_urls.into_iter().collect(),
backup_dir,
Expand All @@ -128,6 +130,7 @@ pub async fn resolve_config(config: InstallerConfig) -> anyhow::Result<ResolvedI
.unwrap_or(DEFAULT_CONCURRENT_DOWNLOADS),
dry_run: config.dry_run,
reaper_version: config.reaper_version.unwrap_or_default(),
install_reaper: config.install_reaper.unwrap_or(true),
update_reaper: config.update_reaper,
skip_failed_packages: config.skip_failed_packages,
recipe: config.recipe,
Expand Down Expand Up @@ -169,7 +172,7 @@ pub async fn complain_if_reapack_db_too_new(
pub async fn determine_initial_installation_stage(
resolved_config: &ResolvedInstallerConfig,
) -> anyhow::Result<InstallationStage> {
let reaper_installed = resolved_config.reaper_exe_exists;
let reaper_installed = resolved_config.reaper_exe_exists || !resolved_config.install_reaper;
if !reaper_installed {
return Ok(InstallationStage::NothingInstalled);
};
Expand Down
2 changes: 1 addition & 1 deletion core/src/reaper_resource_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl ReaperResourceDir {
// Prevent the user from accidentally picking a main REAPER installation
ensure!(
contains_reaper_ini_or_is_empty(&dir),
"This doesn't look like a portable REAPER installation. Either select a valid portable REAPER installation (directory must contain a \"reaper.ini\" file) or an empty directory (to create a new portable installation)!"
"This doesn't appear to be a portable REAPER installation. Please select a valid portable installation (directory must contain a 'reaper.ini' file) or an empty directory to create a new portable installation."
);
// Make canonical
dunce::canonicalize(dir)?
Expand Down
2 changes: 1 addition & 1 deletion core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ impl TestCase {
let installer = reaboot_core::installer::Installer::new(installer_new_args)
.await
.unwrap();
assert!(installer.reaper_is_installable());
let resolved_config = installer.resolved_config();
assert!(resolved_config.reaper_is_installable);
assert_eq!(resolved_config.reaper_exe_exists, true);
assert_eq!(resolved_config.reaper_ini_exists, true);
assert_eq!(resolved_config.portable, true);
Expand Down
7 changes: 7 additions & 0 deletions gui/src-tauri/bindings/InstallerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ recipe?: Recipe,
* Features not contained in the recipe will be ignored.
*/
selected_features: Array<string>,
/**
* Whether to install REAPER or not if ReaBoot detects that it's missing (by default true).
*
* Important because ReaBoot's detection can only detect main installations in the default
* location.
*/
install_reaper?: boolean,
/**
* Update REAPER if there's a new version available (by default false).
*/
Expand Down
10 changes: 9 additions & 1 deletion gui/src-tauri/bindings/ResolvedInstallerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ reaper_exe_exists: boolean,
* or to a portable REAPER installation.
*/
portable: boolean,
/**
* Whether ReaBoot would be capable of installing REAPER automatically.
*/
reaper_is_installable: boolean,
/**
* Resolved REAPER platform.
*/
Expand All @@ -52,4 +56,8 @@ package_urls: Array<PackageUrl>,
/**
* Directory into which ReaBoot writes backups of modified configuration files.
*/
backup_dir: string, num_download_retries: number, temp_parent_dir: string, keep_temp_dir: boolean, concurrent_downloads: number, dry_run: boolean, reaper_version: VersionRef, skip_failed_packages: boolean, update_reaper: boolean, install_reapack: boolean, recipe?: Recipe, };
backup_dir: string, num_download_retries: number, temp_parent_dir: string, keep_temp_dir: boolean, concurrent_downloads: number, dry_run: boolean, reaper_version: VersionRef, skip_failed_packages: boolean,
/**
* Whether to install REAPER if necessary.
*/
install_reaper: boolean, update_reaper: boolean, install_reapack: boolean, recipe?: Recipe, };
51 changes: 50 additions & 1 deletion gui/src/epics/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {showDialog} from "../components/GlobalDialog.tsx";
import {createResource, Match, Switch} from "solid-js";
import {Toast, toaster} from "@kobalte/core";
import {FaSolidX} from "solid-icons/fa";
import {ResolvedInstallerConfig} from "../../src-tauri/bindings/ResolvedInstallerConfig.ts";
import {configureInstaller} from "./install.ts";

export function showError(message: any) {
showDialog<boolean>({
Expand Down Expand Up @@ -46,6 +48,31 @@ function showToast(clazz: string, message: string) {
));
}

async function confirmReaperInstall(resolvedConfig: ResolvedInstallerConfig): Promise<boolean> {
const yes = await showDialog<boolean>({
title: "Install REAPER?",
fullScreen: false,
content: <div class="text-center">
<p>
We found REAPER configuration files on your disk, but we couldn't detect an existing installation at:
</p>
<p class="mt-2 font-mono">{resolvedConfig.reaper_exe}</p>
<p class="mt-2">
Would you like to install REAPER at this default location?
</p>
</div>,
buildButtons: (close) => {
return <>
<button class="btn" onClick={() => close(true)}>Yes, please install it!</button>
<button class="btn btn-primary" onClick={() => close(false)}>
No, it's already installed!
</button>
</>;
}
});
return yes || false
}

async function confirmReaperEula(): Promise<boolean> {
const [eulaResource] = createResource(mainService.getReaperEula);
const yes = await showDialog<boolean>({
Expand Down Expand Up @@ -126,15 +153,37 @@ export function getPage(pageId: PageId) {
}

async function ensureUserAgreedToEula(): Promise<boolean> {
if (mainStore.state.resolvedConfig?.reaper_exe_exists) {
const resolvedConfig = mainStore.state.resolvedConfig;
if (!resolvedConfig) {
return false;
}
if (resolvedConfig.reaper_exe_exists) {
// If REAPER is already installed, we are not going to install REAPER from scratch, so we don't need
// to let the user confirm the EULA again.
return true;
}
if (!resolvedConfig.install_reaper) {
// If REAPER has opted out from installing REAPER, there's also no need to get an agreement.
return true;
}
if (!resolvedConfig.portable && resolvedConfig.reaper_ini_exists
&& mainStore.state.installerConfig.install_reaper === undefined) {
// ReaBoot detected an existing main REAPER resource path but no main binaries, at least not at their default
// location. Ask user if he wants to install REAPER.
const installReaper = await confirmReaperInstall(resolvedConfig);
await configureInstaller({
installReaper
});
if (!installReaper) {
// User opted out from REAPER installation
return true;
}
}
if (mainStore.state.agreedToReaperEula) {
// User agreed to EULA within this ReaBoot session
return true;
}
// User has not opted out from REAPER install and not confirmed the EULA yet. Display dialog.
if (await confirmReaperEula()) {
mainStore.agreeToEula();
return true;
Expand Down
2 changes: 2 additions & 0 deletions gui/src/epics/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type PatchConfigurationArgs = {
customReaperResourceDir?: string | null,
packageUrls?: string[],
selectedFeatures?: string[],
installReaper?: boolean,
updateReaper?: boolean,
};

Expand All @@ -25,6 +26,7 @@ export async function configureInstaller(args: PatchConfigurationArgs) {
custom_reaper_resource_dir: args.customReaperResourceDir === undefined ? oldConfig.custom_reaper_resource_dir : (args.customReaperResourceDir ?? undefined),
package_urls: args.packageUrls ?? oldConfig.package_urls,
selected_features: args.selectedFeatures ?? oldConfig.selected_features,
install_reaper: args.installReaper ?? oldConfig.install_reaper,
update_reaper: args.updateReaper ?? oldConfig.update_reaper,
};
try {
Expand Down
11 changes: 2 additions & 9 deletions gui/src/pages/PickReaperPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,7 @@ export function PickReaperPage() {
return (
<Page>
<p class="text-center font-bold">
<Switch>
<Match when={backendInfo.main_reaper_exe_exists}>
Which REAPER installation do you want to modify?
</Match>
<Match when={true}>
Do you want to create a new main REAPER installation or pick a portable one?
</Match>
</Switch>
Which type of REAPER installation would you like to set up?
</p>
<div class="grow my-4 flex flex-col items-center justify-center">
<ProminentChoice selected={!resolvedConfig.portable}
Expand All @@ -39,7 +32,7 @@ export function PickReaperPage() {
</h2>
<p class="text-base-content/50">
<Switch>
<Match when={backendInfo.main_reaper_exe_exists}>
<Match when={backendInfo.main_reaper_exe_exists || backendInfo.main_reaper_ini_exists}>
Add packages to your existing main REAPER installation.
</Match>
<Match when={true}>
Expand Down

0 comments on commit 78e0a8c

Please sign in to comment.