diff --git a/src-ui/app/App.jsx b/src-ui/app/App.jsx index f8314681..9c7e8aa0 100644 --- a/src-ui/app/App.jsx +++ b/src-ui/app/App.jsx @@ -29,9 +29,9 @@ export const App = () => { const { currentIsBackendReady } = useIsBackendReady(); const { WindowGeometryController } = useWindow(); const { i18n } = useTranslation(); - const fetchPluginsHasRunRef = useRef({ + const pluginsControllerHasRunRef = useRef({ is_initialized_load_plugin: false, - is_fetched_plugins_info: false, + is_init_fetched_plugins_info: false, }); return ( @@ -49,7 +49,7 @@ export const App = () => { {(currentIsBackendReady.data === false || currentIsVrctAvailable.data === false) ? - : + : } @@ -58,11 +58,11 @@ export const App = () => { ); }; -const Contents = ({ fetchPluginsHasRunRef }) => { +const Contents = ({ pluginsControllerHasRunRef }) => { const { currentIsSoftwareUpdating } = useIsSoftwareUpdating(); return ( <> - + {currentIsSoftwareUpdating.data === false diff --git a/src-ui/app/_app_controllers/PluginsController.jsx b/src-ui/app/_app_controllers/PluginsController.jsx index 9939bfe3..9840afd5 100644 --- a/src-ui/app/_app_controllers/PluginsController.jsx +++ b/src-ui/app/_app_controllers/PluginsController.jsx @@ -1,15 +1,24 @@ -import React, { useEffect } from "react"; +import React from "react"; +import clsx from "clsx"; + +if (typeof window !== "undefined") { + window.React = React; + window.clsx = clsx; +} + import { LoadPluginsController } from "./plugins_controllers/LoadPluginsController"; import { FetchLatestPluginsDataController } from "./plugins_controllers/FetchLatestPluginsDataController"; -import { MergeSavedPluginsStatusController } from "./plugins_controllers/MergeSavedPluginsStatusController"; +// import { MergeSavedPluginsStatusController } from "./plugins_controllers/MergeSavedPluginsStatusController"; +import { MergePluginsController } from "./plugins_controllers/MergePluginsController"; -export const PluginsController = () => { +export const PluginsController = ({ pluginsControllerHasRunRef }) => { return ( <> - - - + + + + {/* */} ); }; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/plugins_controllers/FetchLatestPluginsDataController.jsx b/src-ui/app/_app_controllers/plugins_controllers/FetchLatestPluginsDataController.jsx index e0fde914..088d9cae 100644 --- a/src-ui/app/_app_controllers/plugins_controllers/FetchLatestPluginsDataController.jsx +++ b/src-ui/app/_app_controllers/plugins_controllers/FetchLatestPluginsDataController.jsx @@ -1,115 +1,25 @@ import { useEffect } from "react"; import { usePlugins } from "@logics_configs"; -export const FetchLatestPluginsDataController = () => { +export const FetchLatestPluginsDataController = ({ pluginsControllerHasRunRef }) => { const { asyncFetchPluginsInfo, - updatePluginsData, - downloadAndExtractPlugin, - currentIsInitializedLoadPlugin, - currentIsFetchedPluginsInfo, - updateIsFetchedPluginsInfo, } = usePlugins(); - const asyncUpdateLatestPluginsData = async () => { + const asyncInitFetchPluginsInfo = async () => { try { - const info_array = await asyncFetchPluginsInfo(); - updatePluginsData(prev => { - // Map を利用してそれぞれの配列を plugin_id で参照できるようにする - const info_map = new Map(info_array.map(info => [info.plugin_id, info])); - const prev_map = new Map(prev.data.map(item => [item.plugin_id, item])); - - console.log(prev_map); - - const new_data = []; - for (const info of info_array) { - let new_plugin_info = {}; - if (prev_map.has(info.plugin_id)) { // plugin_id 登録済み - const target_downloaded_plugin = prev_map.get(info.plugin_id); - - if (target_downloaded_plugin.is_downloaded) { // 既にダウンロード済み - const is_latest_version_available = !(target_downloaded_plugin.plugin_version === info.plugin_version); - - new_plugin_info = { - ...target_downloaded_plugin, - is_downloaded: true, - latest_plugin_info: { ...info }, - is_latest_version_available: is_latest_version_available, - is_latest_version_already: (target_downloaded_plugin.downloaded_plugin_info?.plugin_version === info.plugin_version), - }; - } else { // infoにもあり登録済みだがダウンロードされていない - new_plugin_info = { - ...target_downloaded_plugin, - is_downloaded: false, - is_latest_version_already: false, - is_latest_version_available: info.is_latest_version_available, - latest_plugin_info: { ...info }, - } - } - } else { // 未ダウンロード - new_plugin_info = { - plugin_id: info.plugin_id, - is_downloaded: false, - is_latest_version_already: false, - latest_plugin_info: { ...info }, - }; - } - - new_data.push(new_plugin_info); - } - - // prev.data にのみ存在するアイテム = latest plugin infoには存在しない - // を追加し、is_outdated: true を付与 - prev.data.forEach(item => { - if (!info_map.has(item.plugin_id)) { - new_data.push({ ...item, is_outdated: true }); - } - }); - - new_data.forEach(plugin => { - if (!plugin.is_outdated) { - plugin.is_latest_version_available = (plugin.latest_plugin_info.is_plugin_supported); - } - }); - - // ダウンロード済みで最新版じゃない場合、自動的にアップデート - // is_latest_version_supported: true のみ。 - // 失敗した場合、現在のバージョンが非対応の場合はdisabledにする。 - new_data.forEach(async plugin => { - if (plugin.is_enabled) { - console.log(plugin); - - if (!plugin.is_latest_version_already && plugin.is_latest_version_available) { - await downloadAndExtractPlugin(plugin); - } - } - }); - - new_data.forEach(async plugin => { - if (plugin.is_downloaded && plugin.is_enabled) { - if (!plugin.downloaded_plugin_info?.is_plugin_supported && !plugin.latest_plugin_info?.is_plugin_supported) { - plugin.is_enabled = false - } - } - }); - - return new_data; - }); - + await asyncFetchPluginsInfo(); } catch (error) { console.error(error); } }; useEffect(() => { - console.log(currentIsInitializedLoadPlugin.data); - if (currentIsInitializedLoadPlugin.data && !currentIsFetchedPluginsInfo.data) { - asyncUpdateLatestPluginsData().then(() => { - updateIsFetchedPluginsInfo(true); - }); + if (!pluginsControllerHasRunRef.current.is_init_fetched_plugins_info) { + asyncInitFetchPluginsInfo(); + pluginsControllerHasRunRef.current.is_init_fetched_plugins_info = true; } - - }, [currentIsInitializedLoadPlugin.data]); + }, []); return null; }; \ No newline at end of file diff --git a/src-ui/app/_app_controllers/plugins_controllers/LoadPluginsController.jsx b/src-ui/app/_app_controllers/plugins_controllers/LoadPluginsController.jsx index 23422cc5..0945df96 100644 --- a/src-ui/app/_app_controllers/plugins_controllers/LoadPluginsController.jsx +++ b/src-ui/app/_app_controllers/plugins_controllers/LoadPluginsController.jsx @@ -1,17 +1,9 @@ import React, { useEffect } from "react"; import { usePlugins } from "@logics_configs"; -import clsx from "clsx"; - -if (typeof window !== "undefined") { - window.React = React; - window.clsx = clsx; -} - -export const LoadPluginsController = () => { +export const LoadPluginsController = ({ pluginsControllerHasRunRef }) => { const { asyncLoadAllPlugins, - currentIsInitializedLoadPlugin, updateIsInitializedLoadPlugin, } = usePlugins(); @@ -24,11 +16,11 @@ export const LoadPluginsController = () => { }; useEffect(() => { - - if (!currentIsInitializedLoadPlugin.data) { + if (!pluginsControllerHasRunRef.current.is_initialized_load_plugin) { asyncInitLoadPlugins().then(() => { updateIsInitializedLoadPlugin(true); }); + pluginsControllerHasRunRef.current.is_initialized_load_plugin = true; } }, []); diff --git a/src-ui/app/_app_controllers/plugins_controllers/MergePluginsController.jsx b/src-ui/app/_app_controllers/plugins_controllers/MergePluginsController.jsx new file mode 100644 index 00000000..2fdb51c9 --- /dev/null +++ b/src-ui/app/_app_controllers/plugins_controllers/MergePluginsController.jsx @@ -0,0 +1,207 @@ +import { useEffect, useRef } from "react"; +import { usePlugins } from "@logics_configs"; +import { useSoftwareVersion } from "@logics_common"; + +export const MergePluginsController = () => { + const { + currentLoadedPlugins, + updatePluginsData, + currentPluginsData, + currentFetchedPluginsInfo, + currentSavedPluginsStatus, + downloadAndExtractPlugin, + } = usePlugins(); + const { checkVrctVerCompatibility } = useSoftwareVersion(); + + // downloaded, fetched, saved の各情報をまとめてマージ + useEffect(() => { + const mergePluginData = () => { + updatePluginsData(prev => { + // downloaded, fetched, 保存済み状態のMapをそれぞれ作成(plugin_id をキー) + const downloaded_map = new Map( + currentLoadedPlugins.data.map(info => [info.plugin_id, info]) + ); + const fetched_map = new Map( + currentFetchedPluginsInfo.data.map(info => [info.plugin_id, info]) + ); + const saved_map = new Map( + currentSavedPluginsStatus.data.map(saved => [saved.plugin_id, saved]) + ); + const prev_map = new Map( + prev.data.map(item => [item.plugin_id, item]) + ); + + // union_keys: Saved以外の情報に対して重複なくキーを取得する + const union_keys = new Set([ + ...downloaded_map.keys(), + ...fetched_map.keys(), + ...prev_map.keys(), + ]); + + const new_data = []; + for (const id of union_keys) { + const downloaded = downloaded_map.get(id); + const fetched = fetched_map.get(id); + const prev_plugin = prev_map.get(id); + let plugin = {}; + + if (downloaded) { + // ダウンロード済み情報に対してサポート確認 + const { is_plugin_supported, is_plugin_supported_latest_vrct } = + checkVrctVerCompatibility( + downloaded.min_supported_vrct_version, + downloaded.max_supported_vrct_version + ); + plugin = { + // prevの情報があれば引き継ぎつつ上書き + ...(prev_plugin || {}), + plugin_id: downloaded.plugin_id, + component: downloaded.component, + is_downloaded: true, + downloaded_plugin_info: { + ...downloaded, + is_plugin_supported, + is_plugin_supported_latest_vrct, + }, + }; + + if (fetched) { + const is_latest_version_available = + (downloaded.plugin_version !== fetched.plugin_version && fetched.is_plugin_supported); + plugin = { + ...plugin, + is_outdated: false, + latest_plugin_info: { ...fetched }, + is_latest_version_available: + plugin.is_downloaded && is_latest_version_available, + is_latest_version_already: + downloaded.plugin_version === fetched.plugin_version, + }; + } else { + // フェッチ情報がない場合の初期状態 + plugin = { + ...plugin, + is_latest_version_available: false, + is_latest_version_already: true, + }; + } + } else if (fetched) { + // フェッチ情報のみの場合は、ダウンロードしていない初期状態 + plugin = { + ...(prev_plugin || {}), + plugin_id: fetched.plugin_id, + is_downloaded: false, + is_latest_version_available: fetched.is_plugin_supported, + is_latest_version_already: false, + is_outdated: false, + latest_plugin_info: { ...fetched }, + }; + } else if (prev_plugin) { + // 既存情報のみ存在する場合は outdated フラグを付与 + plugin = { ...prev_plugin, is_outdated: true }; + } + // いずれかの情報がある場合のみ new_data に追加 + if (plugin.plugin_id) { + new_data.push(plugin); + } + } + + // 保存済み状態(currentSavedPluginsStatus)のマージ + // ・new_dataに存在する各プラグインに対して、保存済みの is_enabled を上書き + new_data.forEach(plugin => { + if (saved_map.has(plugin.plugin_id)) { + plugin.is_enabled = saved_map.get(plugin.plugin_id).is_enabled; + } + }); + // ・prev.data には存在せず、保存済み情報にのみある場合は追加 + for (const [id, saved] of saved_map.entries()) { + if (!new_data.some(item => item.plugin_id === id)) { + new_data.push({ plugin_id: saved.plugin_id, is_enabled: saved.is_enabled }); + } + } + + + // 追加処理: ダウンロード済みかつ有効なプラグインで、サポート対象でない場合は無効化 + new_data.forEach(plugin => { + if (plugin.is_downloaded && plugin.is_enabled) { + if ( + !plugin.downloaded_plugin_info?.is_plugin_supported && + !plugin.latest_plugin_info?.is_plugin_supported + ) { + plugin.is_enabled = false; + } + } + }); + + console.log("merged plugin data", new_data); + return new_data; + }); + }; + + mergePluginData(); + }, [currentFetchedPluginsInfo.data, currentLoadedPlugins.data, currentSavedPluginsStatus]); + + + + // --- 自動アップデート(ダウンロード処理)のuseEffect --- + // ※downloadAndExtractPlugin の重複実行を防ぐため、実行中の plugin_id を useRef で管理 + const downloadingRef = useRef(new Set()); + + useEffect(() => { + if (!currentPluginsData.data.length) return; + // マージ結果の currentPluginsData.data を元にダウンロード処理をチェック + currentPluginsData.data.forEach(plugin => { + if (plugin.is_downloaded && + plugin.is_enabled && + !plugin.is_latest_version_already && + plugin.is_latest_version_available + ) { + console.log(!downloadingRef.current.has(plugin.plugin_id)); + + if (!downloadingRef.current.has(plugin.plugin_id)) { + downloadingRef.current.add(plugin.plugin_id); + // ※ downloadAndExtractPlugin は外部通信を伴い currentLoadedPlugins.data 更新を引き起こすので注意 + downloadAndExtractPlugin(plugin) + .then(() => { + console.log(`Plugin ${plugin.plugin_id} updated successfully`); + downloadingRef.current.delete(plugin.plugin_id); + }) + .catch((error) => { + console.error(`Plugin ${plugin.plugin_id} update failed`, error); + downloadingRef.current.delete(plugin.plugin_id); + }); + } + } + }); + }, [currentPluginsData.data]); + + + + + return null; +}; + + + + + +// ダウンロード済みで最新版じゃない場合、自動的にアップデート +// // is_latest_version_supported: true のみ。 +// // 失敗した場合、現在のバージョンが非対応の場合はdisabledにする。 +// new_data.forEach(async plugin => { +// if (plugin.is_enabled) { +// console.log(plugin); + +// if (!plugin.is_latest_version_already && plugin.is_latest_version_available) { +// await downloadAndExtractPlugin(plugin); +// } +// } +// }); + +// new_data.forEach(plugin => { +// if (plugin.is_downloaded && plugin.is_enabled) { +// if (!plugin.downloaded_plugin_info?.is_plugin_supported && !plugin.latest_plugin_info?.is_plugin_supported) { +// plugin.is_enabled = false +// } +// } +// }); \ No newline at end of file diff --git a/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx index 9baed8fb..9f7f490e 100644 --- a/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx +++ b/src-ui/app/config_page/setting_section/setting_box/plugins/Plugins.jsx @@ -8,7 +8,7 @@ export const Plugins = () => { currentIsPluginsInitialized, } = usePlugins(); - if (!currentIsPluginsInitialized.data) return null; + // if (!currentIsPluginsInitialized.data) return null; return (
diff --git a/src-ui/logics/configs/plugins/usePlugins.js b/src-ui/logics/configs/plugins/usePlugins.js index aa8c5ad7..80e7e717 100644 --- a/src-ui/logics/configs/plugins/usePlugins.js +++ b/src-ui/logics/configs/plugins/usePlugins.js @@ -7,6 +7,9 @@ import { useStore_IsPluginsInitialized, useStore_IsInitializedLoadPlugin, useStore_IsFetchedPluginsInfo, + + useStore_FetchedPluginsInfo, + useStore_LoadedPlugins, } from "@store"; import { useStdoutToPython } from "@logics/useStdoutToPython"; @@ -39,6 +42,8 @@ export const usePlugins = () => { const { currentIsInitializedLoadPlugin, updateIsInitializedLoadPlugin, pendingIsInitializedLoadPlugin } = useStore_IsInitializedLoadPlugin(); const { currentIsFetchedPluginsInfo, updateIsFetchedPluginsInfo, pendingIsFetchedPluginsInfo } = useStore_IsFetchedPluginsInfo(); + const { currentFetchedPluginsInfo, updateFetchedPluginsInfo, pendingFetchedPluginsInfo } = useStore_FetchedPluginsInfo(); + const { currentLoadedPlugins, updateLoadedPlugins, pendingLoadedPlugins } = useStore_LoadedPlugins(); const { currentSavedPluginsStatus, updateSavedPluginsStatus, pendingSavedPluginsStatus } = useStore_SavedPluginsStatus(); const { currentPluginsData, updatePluginsData, pendingPluginsData } = useStore_PluginsData(); @@ -47,65 +52,24 @@ export const usePlugins = () => { const { asyncTauriFetchGithub } = useFetch(); + + const generatePluginContext = (downloaded_plugin_info) => { const plugin_context = { registerComponent: (component) => { if (!downloaded_plugin_info.plugin_id || !downloaded_plugin_info.location || !component) { return console.error("An invalid plugin was detected.", downloaded_plugin_info.plugin_id, downloaded_plugin_info.location, component); } - updatePluginsData(prev => { - console.log("-----updated downloaded plugin info----"); + updateLoadedPlugins(prev => { const prev_map = new Map(prev.data.map(item => [item.plugin_id, item])); - const new_data = []; - let new_value = {}; - const { is_plugin_supported, is_plugin_supported_latest_vrct } = checkVrctVerCompatibility(downloaded_plugin_info.min_supported_vrct_version, downloaded_plugin_info.max_supported_vrct_version); - - const target_plugin = prev_map.get(downloaded_plugin_info.plugin_id); - if (!target_plugin) { // 未ダウンロード 新規登録 - new_value = { - plugin_id: downloaded_plugin_info.plugin_id, - component: component, - is_downloaded: true, - is_latest_version_available: false, - is_latest_version_already: true, - downloaded_plugin_info: { - ...downloaded_plugin_info, - component: component, - is_plugin_supported: is_plugin_supported, - is_plugin_supported_latest_vrct: is_plugin_supported_latest_vrct, - }, - }; - return [...prev.data, new_value]; - } - - for (const old_plugin_data of prev.data) { - - if (old_plugin_data.plugin_id === downloaded_plugin_info.plugin_id) { // ダウンロード済み or 登録済 アップデート - const target_prev_plugin = prev_map.get(downloaded_plugin_info.plugin_id); - const is_latest_version_available = (target_prev_plugin.is_downloaded) && (target_prev_plugin.latest_plugin_info?.plugin_version) && !(downloaded_plugin_info.plugin_version === target_prev_plugin.latest_plugin_info?.plugin_version); - - new_value = { - ...target_prev_plugin, - plugin_id: downloaded_plugin_info.plugin_id, - component: component, - is_downloaded: true, - is_latest_version_available: is_latest_version_available, - is_latest_version_already: (target_plugin.plugin_version === old_plugin_data.latest_plugin_info?.plugin_info), - downloaded_plugin_info: { - ...downloaded_plugin_info, - component: component, - is_plugin_supported: is_plugin_supported, - is_plugin_supported_latest_vrct: is_plugin_supported_latest_vrct, - }, - }; - } else { - new_value = old_plugin_data; - } - new_data.push(new_value); - } - return new_data; + prev_map.set(downloaded_plugin_info.plugin_id, { + ...downloaded_plugin_info, + component: component, + }); + return Array.from(prev_map.values()); }); + }, createAtomWithHook: (...args) => createAtomWithHook(...args), logics: { ...logics_common, ...logics_configs, ...logics_main } @@ -259,10 +223,11 @@ export const usePlugins = () => { const asyncFetchPluginsInfo = async () => { + if (currentIsFetchedPluginsInfo.data) return; try { const response = await asyncTauriFetchGithub(PLUGIN_LIST_URL); if (response.status !== 200) { - throw new Error("Failed to fetch plugin list, status: " + response.status); + throw new Error("Failed to fetch plugins list, status: " + response.status); } const plugins_data = response.data; const updated_list = await Promise.all( @@ -271,7 +236,7 @@ export const usePlugins = () => { const plugin_info = await asyncFetchPluginInfo(plugin_data.url); return plugin_info; } catch (error) { - console.error("Error fetching plugin info for URL:", plugin_data.url, error); + console.error("Error fetching plugin info for URL: ", plugin_data.url, error); return { title: plugin_data.title, plugin_id: plugin_data.plugin_id || plugin_data.title, @@ -282,13 +247,15 @@ export const usePlugins = () => { } }) ); - return updated_list; + updateFetchedPluginsInfo(updated_list); + updateIsFetchedPluginsInfo(true); } catch (error) { - console.error("Error fetching plugin info list:", error); + console.error("Error fetching plugin info list: ", error); } } const asyncFetchPluginInfo = async (plugin_info_asset_url) => { + const release_response = await asyncTauriFetchGithub(plugin_info_asset_url); if (release_response.status !== 200) { throw new Error(`Failed to fetch release info from ${plugin_info_asset_url}`); @@ -371,6 +338,12 @@ export const usePlugins = () => { currentIsFetchedPluginsInfo, updateIsFetchedPluginsInfo, + currentFetchedPluginsInfo, + updateFetchedPluginsInfo, + + currentLoadedPlugins, + updateLoadedPlugins, + setSavedPluginsStatus, handlePendingPlugin, diff --git a/src-ui/store.js b/src-ui/store.js index 58012777..3d4bac13 100644 --- a/src-ui/store.js +++ b/src-ui/store.js @@ -281,7 +281,9 @@ export const { atomInstance: Atom_Hotkeys, useHook: useStore_Hotkeys } = createA export const { atomInstance: Atom_IsPluginsInitialized, useHook: useStore_IsPluginsInitialized } = createAtomWithHook(false, "IsPluginsInitialized"); export const { atomInstance: Atom_IsInitializedLoadPlugin, useHook: useStore_IsInitializedLoadPlugin } = createAtomWithHook(false, "IsInitializedLoadPlugin"); export const { atomInstance: Atom_IsFetchedPluginsInfo, useHook: useStore_IsFetchedPluginsInfo } = createAtomWithHook(false, "IsFetchedPluginsInfo"); -export const { atomInstance: Atom_FetchedPluginsInfo, useHook: useStore_FetchedPluginsInfo } = createAtomWithHook(false, "FetchedPluginsInfo"); + +export const { atomInstance: Atom_FetchedPluginsInfo, useHook: useStore_FetchedPluginsInfo } = createAtomWithHook([], "FetchedPluginsInfo"); +export const { atomInstance: Atom_LoadedPlugins, useHook: useStore_LoadedPlugins } = createAtomWithHook([], "LoadedPlugins"); export const { atomInstance: Atom_SavedPluginsStatus, useHook: useStore_SavedPluginsStatus } = createAtomWithHook([], "SavedPluginsStatus"); export const { atomInstance: Atom_PluginsData, useHook: useStore_PluginsData } = createAtomWithHook([], "PluginsData");