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
4 changes: 4 additions & 0 deletions apps/app-frontend/src/helpers/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export async function refresh_content_updates(instanceId: string): Promise<void>
return await invoke('plugin:instance|instance_refresh_content_updates', { instanceId })
}

export async function sync_content_files(instanceId: string): Promise<void> {
return await invoke('plugin:instance|instance_sync_content_files', { instanceId })
}

// Linked modpack info returned from backend
export interface LinkedModpackInfo {
project: Labrinth.Projects.v2.Project
Expand Down
1 change: 1 addition & 0 deletions apps/app-frontend/src/pages/instance/content/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ let resolveUnknownFileConfirmation: ((confirmed: boolean) => void) | null = null
const modpackContentQueryKey = computed(() => instanceKeys.linkedContent(instance.value.id))
const modpackContentQuery = useQuery({
queryKey: modpackContentQueryKey,
networkMode: 'always',
queryFn: () => get_linked_modpack_content(instance.value.id),
enabled: computed(
() =>
Expand Down
45 changes: 41 additions & 4 deletions apps/app-frontend/src/pages/instance/layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ import {
refresh_content_updates,
remove,
run,
sync_content_files,
} from '@/helpers/instance'
import { useSharedInstanceErrors } from '@/helpers/shared-instance-errors'
import type { GameInstance } from '@/helpers/types'
Expand Down Expand Up @@ -232,18 +233,54 @@ useQuery(
})),
)
const instance = computed(() => instanceQuery.data.value)
async function invalidateContent(targetInstanceId: string) {
await Promise.all([
queryClient.invalidateQueries({ queryKey: instanceKeys.content(targetInstanceId) }),
queryClient.invalidateQueries({ queryKey: instanceKeys.linkedContent(targetInstanceId) }),
])
}

const contentSyncQuery = useQuery(
computed(() => {
const targetInstanceId = instanceId.value
return {
queryKey: instanceKeys.contentSync(targetInstanceId),
queryFn: async () => {
try {
await sync_content_files(targetInstanceId)
await invalidateContent(targetInstanceId)
return targetInstanceId
} catch (error) {
handleError(toError(error))
throw error
}
},
enabled: !!targetInstanceId && instance.value?.install_stage === 'installed',
networkMode: 'always' as const,
staleTime: 0,
gcTime: 0,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
retry: false,
}
}),
)
useQuery(
computed(() => ({
queryKey: instanceKeys.contentUpdateCheck(instanceId.value),
queryFn: async () => {
const targetInstanceId = instanceId.value
await refresh_content_updates(targetInstanceId)
await queryClient.invalidateQueries({
queryKey: instanceKeys.content(targetInstanceId),
})
await invalidateContent(targetInstanceId)
return targetInstanceId
},
enabled: !!instanceId.value && !offline.value && instance.value?.install_stage === 'installed',
enabled:
!!instanceId.value &&
!offline.value &&
instance.value?.install_stage === 'installed' &&
contentSyncQuery.isSuccess.value &&
!contentSyncQuery.isFetching.value &&
contentSyncQuery.data.value === instanceId.value,
staleTime: 10 * 60_000,
gcTime: 30 * 60_000,
retry: false,
Expand Down
3 changes: 3 additions & 0 deletions apps/app-frontend/src/pages/instance/query-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const instanceKeys = {
detail: (instanceId: string) => [...instanceKeys.all, 'summary', instanceId] as const,
processes: (instanceId: string) => [...instanceKeys.all, 'processes', instanceId] as const,
content: (instanceId: string) => [...instanceKeys.all, 'content', instanceId] as const,
contentSync: (instanceId: string) => [...instanceKeys.all, 'content-sync', instanceId] as const,
contentUpdateCheck: (instanceId: string) =>
[...instanceKeys.all, 'content-update-check', instanceId] as const,
rootPath: (instanceId: string) => [...instanceKeys.detail(instanceId), 'root-path'] as const,
Expand Down Expand Up @@ -79,6 +80,7 @@ export function screenshotGroupsQueryOptions() {
export function instanceDetailQueryOptions(instanceId: string) {
return queryOptions({
queryKey: instanceKeys.detail(instanceId),
networkMode: 'always',
queryFn: async () => {
const instance = await getInstance(instanceId)
if (!instance) throw new Error(`Instance ${instanceId} is not managed`)
Expand Down Expand Up @@ -113,6 +115,7 @@ export function instanceContentQueryOptions(
) {
return queryOptions({
queryKey: instanceKeys.content(instanceId),
networkMode: 'always',
queryFn: () => loadInstanceContentData(instanceId, undefined, onError),
staleTime: 30_000,
})
Expand Down
1 change: 1 addition & 0 deletions apps/app/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ fn main() {
"instance_get_install_candidates",
"instance_content",
"instance_get_content_items",
"instance_sync_content_files",
"instance_refresh_content_updates",
"instance_get_dependencies_as_content_items",
"instance_get_linked_modpack_info",
Expand Down
7 changes: 7 additions & 0 deletions apps/app/src/api/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
instance_get_install_candidates,
instance_content,
instance_get_content_items,
instance_sync_content_files,
instance_refresh_content_updates,
instance_get_dependencies_as_content_items,
instance_get_linked_modpack_info,
Expand Down Expand Up @@ -641,6 +642,12 @@ pub async fn instance_get_content_items(
)
}

#[tauri::command]
pub async fn instance_sync_content_files(instance_id: &str) -> Result<()> {
theseus::instance::sync_content_files(instance_id).await?;
Ok(())
}

#[tauri::command]
pub async fn instance_refresh_content_updates(instance_id: &str) -> Result<()> {
Ok(theseus::instance::refresh_content_updates(instance_id).await?)
Expand Down
9 changes: 6 additions & 3 deletions packages/app-lib/src/api/instance/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,12 @@ pub async fn get_content_items(
cache_behaviour: Option<CacheBehaviour>,
) -> crate::Result<Vec<ContentItem>> {
let state = State::get().await?;
let mut items =
crate::state::list_content(instance_id, None, cache_behaviour, &state)
.await?;
let mut items = crate::state::list_indexed_content(
instance_id,
cache_behaviour,
&state,
)
.await?;
super::synced_packs::decorate_content(instance_id, &mut items, &state)
.await?;
Ok(items)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ struct Candidate {

pub(crate) fn queue_game_locale_index() {
if let Some(state) = State::get_if_initialized() {
tracing::info!(
started = state.game_locale_indexer.started.load(Ordering::Acquire),
"Game setting locales: indexing queued"
);
state.game_locale_indexer.notify.notify_one();
} else {
tracing::warn!(
Expand All @@ -100,33 +96,22 @@ pub(crate) fn start_game_locale_indexer(state: Arc<State>) {
{
return;
}
tracing::info!("Game setting locales: indexer started");
state.game_locale_indexer.notify.notify_one();
tokio::spawn(async move {
loop {
state.game_locale_indexer.notify.notified().await;
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
let started = std::time::Instant::now();
if let Err(error) = index_installed_sources(&state).await {
tracing::warn!(%error, "Game setting locales: indexing failed");
}
tracing::info!(
elapsed_ms = started.elapsed().as_millis(),
"Game setting locales: indexing pass finished"
);
#[cfg(feature = "tauri")]
{
use tauri::Emitter;
match crate::EventState::get()
if let Err(error) = crate::EventState::get()
.app
.emit("game-option-locales-updated", ())
{
Ok(()) => tracing::info!(
"Game setting locales: emitted game-option-locales-updated"
),
Err(error) => {
tracing::warn!(%error, "Game setting locales: update event failed")
}
tracing::warn!(%error, "Game setting locales: update event failed");
}
}
}
Expand Down Expand Up @@ -221,19 +206,11 @@ async fn cached_archive(
#[tracing::instrument(skip_all, err)]
async fn index_installed_sources(state: &State) -> crate::Result<()> {
let mut instances = crate::state::list_instances(&state.pool).await?;
tracing::info!(
instances = instances.len(),
"Game setting locales: indexing installed instances"
);
instances.sort_by(|a, b| a.instance.id.cmp(&b.instance.id));
let mut candidates = Vec::new();
let mut resolver = origins::OriginResolver::default();
for metadata in instances {
if super::super::sync_files_are_protected(&metadata) {
tracing::info!(
instance_id = metadata.instance.id,
"Game setting locales: skipping protected instance"
);
continue;
}
let document = match read_document(&options_path(&metadata, state))
Expand Down Expand Up @@ -261,9 +238,6 @@ async fn index_installed_sources(state: &State) -> crate::Result<()> {
continue;
}
};
tracing::info!(instance_id = metadata.instance.id, %snapshot_id, game_version = snapshot.game_version,
mods = snapshot.mods.len(),
"Game setting locales: snapshot loaded");
resolver
.index_snapshot(state, &snapshot_id, snapshot.clone())
.await?;
Expand All @@ -284,26 +258,13 @@ async fn index_installed_sources(state: &State) -> crate::Result<()> {
.await?;
}
tx.commit().await?;
tracing::info!(
instance_id = metadata.instance.id,
options = keys.len(),
"Game setting locales: observations recorded"
);
candidates.push(Candidate {
snapshot_id,
snapshot,
keys,
});
}
let rows = storage::load(&state.pool, None).await?;
tracing::info!(
candidates = candidates.len(),
rows = rows.len(),
resolved = rows.iter().filter(|row| row.origin.is_some()).count(),
"Game setting locales: resolving translation origins"
);
let mut pinned = 0;
let mut unresolved = 0;
for row in rows {
if row.origin.is_some() {
continue;
Expand Down Expand Up @@ -342,14 +303,10 @@ async fn index_installed_sources(state: &State) -> crate::Result<()> {
raw_key: c.keys[&row.option_id].clone(),
}));
}
let mut resolved = false;
let observation_count = observations.len();
for observation in observations {
match resolver.resolve(state, &observation).await {
Ok(Some(origin)) => {
storage::pin(&state.pool, &row, &origin).await?;
resolved = true;
pinned += 1;
break;
}
Ok(None) => {}
Expand All @@ -359,22 +316,7 @@ async fn index_installed_sources(state: &State) -> crate::Result<()> {
}
}
}
if !resolved {
tracing::debug!(
scope = row.scope,
option_id = row.option_id,
observation_count,
backfilled = row.backfilled,
"Game setting locales: no matching translation origin"
);
unresolved += 1;
}
}
tracing::info!(
pinned,
unresolved,
"Game setting locales: origin resolution finished"
);
Ok(())
}

Expand Down Expand Up @@ -405,7 +347,6 @@ pub async fn get_game_setting_locale_labels(
option_ids: Vec<String>,
refresh_sources: bool,
) -> crate::Result<GameSettingLocaleLabels> {
tracing::info!("Game setting locales: label request received");
if option_ids.len() > 16_384 {
return Err(input_error("Too many requested game-setting labels"));
}
Expand All @@ -430,12 +371,6 @@ pub async fn get_game_setting_locale_labels(
}
let scope = instance_id.unwrap_or("");
let mut rows = storage::load(&state.pool, Some(scope)).await?;
tracing::info!(
scope,
rows = rows.len(),
resolved = rows.iter().filter(|row| row.origin.is_some()).count(),
"Game setting locales: scoped origins loaded"
);
if scope.is_empty() {
let mut sourced: std::collections::HashSet<_> =
rows.iter().map(|row| row.option_id.clone()).collect();
Expand All @@ -450,11 +385,6 @@ pub async fn get_game_setting_locale_labels(
}
}
}
tracing::info!(
rows = rows.len(),
resolved = rows.iter().filter(|row| row.origin.is_some()).count(),
"Game setting locales: origins selected including fallbacks"
);
let hashes: std::collections::HashSet<_> = rows
.iter()
.filter(|row| requested.contains(&row.option_id))
Expand All @@ -473,13 +403,11 @@ pub async fn get_game_setting_locale_labels(
let mut result = GameSettingLocaleLabels::default();
let mut archives = HashMap::new();
let mut dictionaries: HashMap<String, Translations> = HashMap::new();
let mut missing_origins = Vec::new();
for row in rows {
if !requested.contains(&row.option_id) {
continue;
}
let Some(origin) = row.origin else {
missing_origins.push(row.option_id);
continue;
};
if origin.legacy_game_jar_hash.as_deref()
Expand Down Expand Up @@ -522,8 +450,6 @@ pub async fn get_game_setting_locale_labels(
translations.extend(selected.clone());
}
bundle.deprecated.apply(&mut translations);
tracing::info!(%dictionary_id, %locale, translations = translations.len(),
"Game setting locales: dictionary loaded");
dictionaries.insert(dictionary_id.clone(), translations);
}
let translations = &dictionaries[&dictionary_id];
Expand Down Expand Up @@ -576,16 +502,5 @@ pub async fn get_game_setting_locale_labels(
);
}
}
let mut missing: Vec<_> = requested
.iter()
.filter(|id| !result.settings.contains_key(*id))
.collect();
missing.sort();
tracing::info!(
returned = result.settings.len(),
?missing,
?missing_origins,
"Game setting locales: label request completed"
);
Ok(result)
}
Loading
Loading