<commit_before>function install_app($app, $architecture, $global) {
	$app, $manifest, $bucket, $url = locate $app

	if(!$manifest) {
		abort "couldn't find manifest for $app$(if($url) { " at the URL $url" })"
	}

	$version = $manifest.version
	if(!$version) { abort "manifest doesn't specify a version" }
	if($version -match '[^\w\.\-_]') {
		abort "manifest version has unsupported character '$($matches[0])'"
	}

	echo "installing $app ($version)"

	$dir = ensure (versiondir $app $version $global)

	$fname = dl_urls $app $version $manifest $architecture $dir
	unpack_inno $fname $manifest $dir
	run_installer $fname $manifest $architecture $dir
	ensure_install_dir_not_in_path $dir $global
	create_shims $manifest $dir $global
	if($global) { ensure_scoop_in_path $global } # can assume local scoop is in path
	env_add_path $manifest $dir $global
	env_set $manifest $dir $global
	post_install $manifest

	# save info for uninstall
	save_installed_manifest $app $bucket $dir $url
	save_install_info @{ 'architecture' = $architecture; 'url' = $url; 'bucket' = $bucket } $dir

	success "$app ($version) was installed successfully!"

	show_notes $manifest
}

function ensure_architecture($architecture_opt) {
	switch($architecture_opt) {
		'' { return default_architecture }
		{ @('32bit','64bit') -contains $_ } { return $_ }
		default { abort "invalid architecture: '$architecture'"}
	}
}

function cache_path($app, $version, $url) {
	"$cachedir\$app#$version#$($url -replace '[^\w\.\-]+', '_')"
}

function appname_from_url($url) {
	(split-path $url -leaf) -replace '.json$', ''
}

function locate($app) {
	$manifest, $bucket, $url = $null, $null, $null

	# check if app is a url
	if($app -match '^((ht)|f)tps?://') {
		$url = $app
		$app = appname_from_url $url
		$manifest = url_manifest $url
	} else {
		# check buckets
		$manifest, $bucket = find_manifest $app

		if(!$manifest) {
			# couldn't find app in buckets: check if it's a local path
			$path = $app
			if(!$path.endswith('.json')) { $path += '.json' }
			if(test-path $path) {
				$url = "$(resolve-path $path)"
				$app = appname_from_url $url
				$manifest, $bucket = url_manifest $url
			}
		}
	}

	return $app, $manifest, $bucket, $url
}

function dl_with_cache($app, $version, $url, $to) {
	$cached = fullpath (cache_path $app $version $url)
	if(!(test-path $cached)) {
		$null = ensure $cachedir
		write-host "downloading $url..." -nonewline
		dl_progress $url "$cached.download"
		mv "$cached.download" $cached
		write-host "done"
	} else { write-host "loading $url from cache..."}
	cp $cached $to
}

function dl_progress($url, $to) {
	if([console]::isoutputredirected) {
		# can't set cursor position: just do simple download
		(new-object net.webclient).downloadfile($url, $to)
		return
	}

	$left = [console]::cursorleft
	$top = [console]::cursortop

	$wc = new-object net.webclient
	register-objectevent $wc downloadprogresschanged progress | out-null
	register-objectevent $wc downloadfilecompleted complete | out-null
	try {
		$wc.downloadfileasync($url, $to)

		function is_complete {
			try { get-event complete -ea stop; $true } catch { $false }
		}

		$last_p = -1
		while(!(is_complete)) {
			$e = wait-event progress -timeout 1
			if(!$e) { continue } # avoid deadlock

			remove-event progress
			$p = $e.sourceeventargs.progresspercentage
			if($p -ne $last_p) {
				[console]::setcursorposition($left, $top)
				write-host "$p%" -nonewline
				$last_p = $p
			}<commit_msg>check for download error and abort the install with an appropriate message if there was an error (fixes issue #26, see also #24 and #45)<commit_after>function install_app($app, $architecture, $global) {
	$app, $manifest, $bucket, $url = locate $app

	if(!$manifest) {
		abort "couldn't find manifest for $app$(if($url) { " at the URL $url" })"
	}

	$version = $manifest.version
	if(!$version) { abort "manifest doesn't specify a version" }
	if($version -match '[^\w\.\-_]') {
		abort "manifest version has unsupported character '$($matches[0])'"
	}

	echo "installing $app ($version)"

	$dir = ensure (versiondir $app $version $global)

	$fname = dl_urls $app $version $manifest $architecture $dir
	unpack_inno $fname $manifest $dir
	run_installer $fname $manifest $architecture $dir
	ensure_install_dir_not_in_path $dir $global
	create_shims $manifest $dir $global
	if($global) { ensure_scoop_in_path $global } # can assume local scoop is in path
	env_add_path $manifest $dir $global
	env_set $manifest $dir $global
	post_install $manifest

	# save info for uninstall
	save_installed_manifest $app $bucket $dir $url
	save_install_info @{ 'architecture' = $architecture; 'url' = $url; 'bucket' = $bucket } $dir

	success "$app ($version) was installed successfully!"

	show_notes $manifest
}

function ensure_architecture($architecture_opt) {
	switch($architecture_opt) {
		'' { return default_architecture }
		{ @('32bit','64bit') -contains $_ } { return $_ }
		default { abort "invalid architecture: '$architecture'"}
	}
}

function cache_path($app, $version, $url) {
	"$cachedir\$app#$version#$($url -replace '[^\w\.\-]+', '_')"
}

function appname_from_url($url) {
	(split-path $url -leaf) -replace '.json$', ''
}

function locate($app) {
	$manifest, $bucket, $url = $null, $null, $null

	# check if app is a url
	if($app -match '^((ht)|f)tps?://') {
		$url = $app
		$app = appname_from_url $url
		$manifest = url_manifest $url
	} else {
		# check buckets
		$manifest, $bucket = find_manifest $app

		if(!$manifest) {
			# couldn't find app in buckets: check if it's a local path
			$path = $app
			if(!$path.endswith('.json')) { $path += '.json' }
			if(test-path $path) {
				$url = "$(resolve-path $path)"
				$app = appname_from_url $url
				$manifest, $bucket = url_manifest $url
			}
		}
	}

	return $app, $manifest, $bucket, $url
}

function dl_with_cache($app, $version, $url, $to) {
	$cached = fullpath (cache_path $app $version $url)
	if(!(test-path $cached)) {
		$null = ensure $cachedir
		write-host "downloading $url..." -nonewline
		dl_progress $url "$cached.download"
		mv "$cached.download" $cached
		write-host "done"
	} else { write-host "loading $url from cache..."}
	cp $cached $to
}

function dl_progress($url, $to) {
	if([console]::isoutputredirected) {
		# can't set cursor position: just do simple download
		(new-object net.webclient).downloadfile($url, $to)
		return
	}

	$left = [console]::cursorleft
	$top = [console]::cursortop

	$wc = new-object net.webclient
	register-objectevent $wc downloadprogresschanged progress | out-null
	register-objectevent $wc downloadfilecompleted complete | out-null
	try {
		$wc.downloadfileasync($url, $to)

		function is_complete {
			try {
				$complete = get-event complete -ea stop
				$err = $complete.sourceeventargs.error
				if($err) { abort "$($err.message)" }
				$true
			}
			catch {
				$false
			}
		}

		$last_p = -1
		while(!(is_complete)) {
			$e = wait-event progress -timeout 1
			if(!$e) { continue } # avoid deadlock

			remove-event progress
			$p = $e.sourceeventargs.progresspercentage
			if($p -ne $last_p) {
				[console]::setcursorposition($left, $top)
				write-host "$p%" -nonewline
				$last_p = $p
			}
{'fraction_non_alphanumeric': 0.1555648652422846, 'fraction_numerical': 0.0027929060187124703, 'mean_word_length': 4.171119133574007, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 17, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4284397', 'n_tokens_mistral': 2756, 'n_tokens_neox': 2467, 'n_words': 670}