Misc bugs 4 (#381)

* bug fixes

* fixed jres being undetected

* cleanup

* prettier

* fixed folders not displaying windows exporting

* fixes, more bugs

* missed function

* clippy, fmt

* prettier
This commit is contained in:
Wyatt Verchere 2023-07-28 19:56:49 -07:00 committed by GitHub
parent 744d11f09e
commit 87449f91c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 171 additions and 78 deletions

View File

@ -94,6 +94,19 @@ pub async fn get_by_uuid(
Ok(profile)
}
/// Get profile's full path in the filesystem
#[tracing::instrument]
pub async fn get_full_path(path: &ProfilePathId) -> crate::Result<PathBuf> {
let _ = get(path, Some(true)).await?.ok_or_else(|| {
crate::ErrorKind::OtherError(format!(
"Tried to get the full path of a nonexistent or unloaded profile at path {}!",
path
))
})?;
let full_path = io::canonicalize(path.get_full_path().await?)?;
Ok(full_path)
}
/// Edit a profile using a given asynchronous closure
pub async fn edit<Fut>(
path: &ProfilePathId,
@ -373,6 +386,7 @@ pub async fn update_project(
profile.projects.insert(path.clone(), project);
}
}
drop(profiles);
if !skip_send_event.unwrap_or(false) {
emit_profile(
@ -409,7 +423,7 @@ pub async fn add_project_from_version(
version_id: String,
) -> crate::Result<ProjectPathId> {
if let Some(profile) = get(profile_path, None).await? {
let (path, _) = profile.add_project_version(version_id).await?;
let (project_path, _) = profile.add_project_version(version_id).await?;
emit_profile(
profile.uuid,
@ -418,9 +432,7 @@ pub async fn add_project_from_version(
ProfilePayloadType::Edited,
)
.await?;
State::sync().await?;
Ok(path)
Ok(project_path)
} else {
Err(
crate::ErrorKind::UnmanagedProfileError(profile_path.to_string())

View File

@ -143,6 +143,8 @@ impl Children {
mc_exit_status = t;
break;
}
// sleep for 10ms
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
}
{
@ -204,6 +206,9 @@ impl Children {
mc_exit_status = t;
break;
}
// sleep for 10ms
tokio::time::sleep(tokio::time::Duration::from_millis(10))
.await;
}
}

View File

@ -270,7 +270,7 @@ pub async fn init_watcher() -> crate::Result<Debouncer<RecommendedWatcher>> {
let (mut tx, mut rx) = channel(1);
let file_watcher = new_debouncer(
Duration::from_secs(2),
Duration::from_secs_f32(0.25),
None,
move |res: DebounceEventResult| {
futures::executor::block_on(async {

View File

@ -107,7 +107,8 @@ impl ProjectPathId {
let profiles_dir: PathBuf = io::canonicalize(
State::get().await?.directories.profiles_dir().await,
)?;
path.strip_prefix(profiles_dir)
let path = path
.strip_prefix(profiles_dir)
.ok()
.map(|p| p.components().skip(1).collect::<PathBuf>())
.ok_or_else(|| {
@ -458,7 +459,6 @@ impl Profile {
version_id: String,
) -> crate::Result<(ProjectPathId, ModrinthVersion)> {
let state = State::get().await?;
let version = fetch_json::<ModrinthVersion>(
Method::GET,
&format!("{MODRINTH_API_URL}version/{version_id}"),
@ -467,7 +467,6 @@ impl Profile {
&state.fetch_semaphore,
)
.await?;
let file = if let Some(file) = version.files.iter().find(|x| x.primary)
{
file
@ -486,7 +485,6 @@ impl Profile {
&state.fetch_semaphore,
)
.await?;
let path = self
.add_project_bytes(
&file.filename,
@ -494,7 +492,6 @@ impl Profile {
ProjectType::get_from_loaders(version.loaders.clone()),
)
.await?;
Ok((path, version))
}
@ -569,7 +566,6 @@ impl Profile {
}
/// Toggle a project's disabled state.
/// 'path' should be relative to the profile's path.
#[tracing::instrument(skip(self))]
#[theseus_macros::debug_pin]
pub async fn toggle_disable_project(
@ -662,11 +658,11 @@ impl Profile {
}
}
} else {
return Err(crate::ErrorKind::InputError(format!(
"Project path does not exist: {:?}",
// If we are removing a project that doesn't exist, allow it to pass through without error, but warn
tracing::warn!(
"Attempted to remove non-existent project: {:?}",
relative_path
))
.into());
);
}
Ok(())

View File

@ -272,6 +272,16 @@ pub async fn infer_data_from_files(
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
for path in paths {
if !path.exists() {
continue;
}
if let Some(ext) = path.extension() {
// Ignore txt configuration files
if ext == "txt" {
continue;
}
}
let mut file = tokio::fs::File::open(path.clone())
.await
.map_err(|e| IOError::with_path(e, &path))?;
@ -460,9 +470,7 @@ pub async fn infer_data_from_files(
.await
.is_ok()
{
if let Ok(pack) =
serde_json::from_str::<ForgeModInfo>(&file_str)
{
if let Ok(pack) = toml::from_str::<ForgeModInfo>(&file_str) {
if let Some(pack) = pack.mods.first() {
let icon = read_icon_from_file(
pack.logo_file.clone(),

View File

@ -2,11 +2,9 @@ use super::io;
use futures::prelude::*;
use serde::{Deserialize, Serialize};
use std::env;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::{collections::HashSet, path::Path};
use tempfile::NamedTempFile;
use tokio::task::JoinError;
use crate::State;
@ -280,20 +278,17 @@ pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
return None;
};
let mut file = NamedTempFile::new().ok()?;
file.write_all(include_bytes!("../../library/JavaInfo.class"))
.ok()?;
let bytes = include_bytes!("../../library/JavaInfo.class");
let tempdir: PathBuf = tempfile::tempdir().ok()?.into_path();
if !tempdir.exists() {
return None;
}
let file_path = tempdir.join("JavaInfo.class");
io::write(&file_path, bytes).await.ok()?;
let original_path = file.path().to_path_buf();
let mut new_path = original_path.clone();
new_path.set_file_name("JavaInfo");
new_path.set_extension("class");
tokio::fs::rename(&original_path, &new_path).await.ok()?;
// Run java checker on java binary
let output = Command::new(&java)
.arg("-cp")
.arg(file.path().parent().unwrap())
.arg(file_path.parent().unwrap())
.arg("JavaInfo")
.output()
.ok()?;

View File

@ -12,6 +12,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
profile_remove,
profile_get,
profile_get_optimal_jre_key,
profile_get_full_path,
profile_list,
profile_check_installed,
profile_install,
@ -55,6 +56,14 @@ pub async fn profile_get(
Ok(res)
}
// Get a profile's full path
// invoke('plugin:profile|profile_get_full_path',path)
#[tauri::command]
pub async fn profile_get_full_path(path: ProfilePathId) -> Result<PathBuf> {
let res = profile::get_full_path(&path).await?;
Ok(res)
}
// Get optimal java version from profile
#[tauri::command]
pub async fn profile_get_optimal_jre_key(

View File

@ -59,7 +59,7 @@ pub fn show_in_folder(path: String) -> Result<()> {
#[cfg(target_os = "windows")]
{
Command::new("explorer")
.args(["/select,", &path]) // The comma after select is not a typo
.args([&path]) // The comma after select is not a typo
.spawn()?;
}
@ -86,7 +86,7 @@ pub fn show_in_folder(path: String) -> Result<()> {
#[cfg(target_os = "macos")]
{
Command::new("open").args(["-R", &path]).spawn()?;
Command::new("open").args([&path]).spawn()?;
}
Ok::<(), theseus::Error>(())

View File

@ -26,6 +26,19 @@ fn is_dev() -> bool {
cfg!(debug_assertions)
}
// Toggles decorations
#[tauri::command]
async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
window.set_decorations(b).map_err(|e| {
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
"Failed to toggle decorations: {}",
e
)))
})?;
println!("Toggled decorations!");
Ok(())
}
#[derive(Clone, serde::Serialize)]
struct Payload {
args: Vec<String>,
@ -84,16 +97,12 @@ fn main() {
}
#[cfg(target_os = "macos")]
{
win.set_decorations(true).unwrap();
use macos::window_ext::WindowExt;
win.set_transparent_titlebar(true);
win.position_traffic_lights(9.0, 16.0);
}
#[cfg(not(target_os = "macos"))]
{
win.set_decorations(false).unwrap();
}
#[cfg(target_os = "macos")]
{
macos::delegate::register_open_file(|filename| {
tauri::async_runtime::spawn(api::utils::handle_command(
filename,
@ -102,6 +111,9 @@ fn main() {
.unwrap();
}
// Show app now that we are setup
win.show().unwrap();
Ok(())
});
@ -129,7 +141,11 @@ fn main() {
.plugin(api::settings::init())
.plugin(api::tags::init())
.plugin(api::utils::init())
.invoke_handler(tauri::generate_handler![initialize_state, is_dev]);
.invoke_handler(tauri::generate_handler![
initialize_state,
is_dev,
toggle_decorations
]);
builder
.run(tauri::generate_context!())

View File

@ -101,7 +101,9 @@
"title": "Modrinth App",
"width": 1280,
"minHeight": 630,
"minWidth": 1100
"minWidth": 1100,
"visible": false,
"decorations": false
}
]
}

View File

@ -308,7 +308,7 @@ const accounts = ref(null)
align-items: center;
flex-grow: 1;
background: var(--color-raised-bg);
box-shadow: inset 0px -3px 0px black;
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
text-align: center;
padding: var(--gap-md);
height: 3.25rem;

View File

@ -27,7 +27,7 @@ import {
import { handleError } from '@/store/notifications.js'
import { remove, run } from '@/helpers/profile.js'
import { useRouter } from 'vue-router'
import { showInFolder } from '@/helpers/utils.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import { useFetch } from '@/helpers/fetch.js'
import { install as pack_install } from '@/helpers/pack.js'
import { useTheming } from '@/store/state.js'
@ -155,7 +155,7 @@ const handleOptionsClick = async (args) => {
deleteConfirmModal.value.show()
break
case 'open_folder':
await showInFolder(args.item.path)
await showProfileInFolder(args.item.path)
break
case 'copy_path':
await navigator.clipboard.writeText(args.item.path)

View File

@ -5,6 +5,7 @@ import { ref } from 'vue'
import { export_profile_mrpack, get_potential_override_folders } from '@/helpers/profile.js'
import { open } from '@tauri-apps/api/dialog'
import { handleError } from '@/store/notifications.js'
import { sep } from '@tauri-apps/api/path'
const props = defineProps({
instance: {
@ -33,11 +34,11 @@ const initFiles = async () => {
filePaths
.map((folder) => ({
path: folder,
name: folder.split('/').pop(),
name: folder.split(sep).pop(),
selected: false,
}))
.forEach((pathData) => {
const parent = pathData.path.split('/').slice(0, -1).join('/')
const parent = pathData.path.split(sep).slice(0, -1).join(sep)
if (parent !== '') {
if (newFolders.has(parent)) {
newFolders.get(parent).push(pathData)

View File

@ -31,14 +31,13 @@ defineExpose({
async function install() {
installing.value = true
console.log(`Installing ${projectId.value} ${version.value} ${title.value} ${icon.value}`)
confirmModal.value.hide()
await pack_install(
projectId.value,
version.value,
title.value,
icon.value ? icon.value : null
).catch(handleError)
confirmModal.value.hide()
mixpanel.track('PackInstall', {
id: projectId.value,
version_id: version.value,

View File

@ -14,7 +14,7 @@ import {
import { process_listener } from '@/helpers/events'
import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/state.js'
import { showInFolder } from '@/helpers/utils.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
import mixpanel from 'mixpanel-browser'
@ -155,7 +155,7 @@ const stop = async (e, context) => {
}
const openFolder = async () => {
await showInFolder(props.instance.path)
await showProfileInFolder(props.instance.path)
}
const addContent = async () => {

View File

@ -37,6 +37,12 @@ export async function get(path, clearProjects) {
return await invoke('plugin:profile|profile_get', { path, clearProjects })
}
// Get a profile's full fs path
// Returns a path
export async function get_full_path(path) {
return await invoke('plugin:profile|profile_get_full_path', { path })
}
// Get optimal java version from profile
// Returns a java version
export async function get_optimal_jre_key(path) {

View File

@ -1,4 +1,8 @@
import { add_project_from_version as installMod, check_installed } from '@/helpers/profile'
import {
add_project_from_version as installMod,
check_installed,
get_full_path,
} from '@/helpers/profile'
import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/notifications.js'
import { invoke } from '@tauri-apps/api/tauri'
@ -11,6 +15,12 @@ export async function showInFolder(path) {
return await invoke('plugin:utils|show_in_folder', { path })
}
// Opens a profile's folder in the OS file explorer
export async function showProfileInFolder(path) {
const fullPath = await get_full_path(path)
return await showInFolder(fullPath)
}
export const releaseColor = (releaseType) => {
switch (releaseType) {
case 'release':

View File

@ -23,7 +23,11 @@ app.mixin(loadCssMixin)
const mountedApp = app.mount('#app')
const raw_invoke = async (plugin, fn, args) => {
return await invoke('plugin:' + plugin + '|' + fn, args)
if (plugin == '') {
return await invoke(fn, args)
} else {
return await invoke('plugin:' + plugin + '|' + fn, args)
}
}
isDev()
.then((dev) => {

View File

@ -654,7 +654,7 @@ const showLoaders = computed(
</Card>
</aside>
<div class="search">
<Promotion class="promotion" />
<Promotion class="promotion" :external="false" />
<Card class="project-type-container">
<NavRow :links="selectableProjectTypes" />
</Card>

View File

@ -51,7 +51,7 @@
<Button
v-tooltip="'Open instance folder'"
class="instance-button"
@click="showInFolder(instance.path)"
@click="showProfileInFolder(instance.path)"
>
<FolderOpenIcon />
Folder
@ -148,7 +148,7 @@ import { process_listener, profile_listener } from '@/helpers/events'
import { useRoute, useRouter } from 'vue-router'
import { ref, onUnmounted } from 'vue'
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
import { showInFolder } from '@/helpers/utils.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import mixpanel from 'mixpanel-browser'
import { PackageIcon } from '@/assets/icons/index.js'
@ -268,7 +268,7 @@ const handleOptionsClick = async (args) => {
})
break
case 'open_folder':
await showInFolder(instance.value.path)
await showProfileInFolder(instance.value.path)
break
case 'copy_path':
await navigator.clipboard.writeText(instance.value.path)

View File

@ -223,7 +223,11 @@
:checked="!mod.disabled"
@change="toggleDisableMod(mod)"
/>
<Button v-tooltip="`Show ${mod.file_name}`" icon-only @click="showInFolder(mod.path)">
<Button
v-tooltip="`Show ${mod.file_name}`"
icon-only
@click="showProfileInFolder(mod.path)"
>
<FolderOpenIcon />
</Button>
</div>
@ -345,7 +349,7 @@ import mixpanel from 'mixpanel-browser'
import { open } from '@tauri-apps/api/dialog'
import { listen } from '@tauri-apps/api/event'
import { convertFileSrc } from '@tauri-apps/api/tauri'
import { showInFolder } from '@/helpers/utils.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage } from '@/assets/icons'
const router = useRouter()
@ -605,17 +609,39 @@ const updateProject = async (mod) => {
})
}
let locks = {}
const toggleDisableMod = async (mod) => {
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
mod.disabled = !mod.disabled
mixpanel.track('InstanceProjectDisable', {
loader: props.instance.metadata.loader,
game_version: props.instance.metadata.game_version,
id: mod.id,
name: mod.name,
project_type: mod.project_type,
disabled: mod.disabled,
})
// Use mod's id as the key for the lock. If mod doesn't have a unique id, replace `mod.id` with some unique property.
if (!locks[mod.id]) {
locks[mod.id] = ref(null)
}
let lock = locks[mod.id]
while (lock.value) {
await lock.value
}
lock.value = toggle_disable_project(props.instance.path, mod.path)
.then((newPath) => {
mod.path = newPath
mod.disabled = !mod.disabled
mixpanel.track('InstanceProjectDisable', {
loader: props.instance.metadata.loader,
game_version: props.instance.metadata.game_version,
id: mod.id,
name: mod.name,
project_type: mod.project_type,
disabled: mod.disabled,
})
})
.catch(handleError)
.finally(() => {
lock.value = null
})
await lock.value
}
const removeMod = async (mod) => {

View File

@ -350,15 +350,19 @@ async function install(version) {
}
if (installed.value) {
await remove_project(
instance.value.path,
Object.entries(instance.value.projects)
.map(([key, value]) => ({
key,
value,
}))
.find((p) => p.value.metadata?.version?.project_id === data.value.id).key
)
const old_project = Object.entries(instance.value.projects)
.map(([key, value]) => ({
key,
value,
}))
.find((p) => p.value.metadata?.version?.project_id === data.value.id)
if (!old_project) {
// Switching too fast, old project is not recognized as a Modrinth project yet
installing.value = false
return
}
await remove_project(instance.value.path, old_project.key)
}
if (version) {