From a6ac75e97bc8057de0e2a98cd8a059b4937f2718 Mon Sep 17 00:00:00 2001 From: GyDi Date: Wed, 26 Oct 2022 01:11:02 +0800 Subject: [PATCH] feat: display delay check result timely --- src/components/proxy/proxy-global.tsx | 22 ++--- src/components/proxy/proxy-group.tsx | 22 ++--- src/components/proxy/proxy-head.tsx | 2 +- src/components/proxy/proxy-item.tsx | 13 ++- src/components/proxy/use-filter-proxy.ts | 49 ---------- src/components/proxy/use-filter-sort.ts | 114 +++++++++++++++++++++++ src/components/proxy/use-head-state.ts | 2 +- src/components/proxy/use-sort-proxy.ts | 37 -------- src/services/delay.ts | 56 +++++++---- 9 files changed, 179 insertions(+), 138 deletions(-) delete mode 100644 src/components/proxy/use-filter-proxy.ts create mode 100644 src/components/proxy/use-filter-sort.ts delete mode 100644 src/components/proxy/use-sort-proxy.ts diff --git a/src/components/proxy/proxy-global.tsx b/src/components/proxy/proxy-global.tsx index a6f5d46..d741fed 100644 --- a/src/components/proxy/proxy-global.tsx +++ b/src/components/proxy/proxy-global.tsx @@ -5,9 +5,8 @@ import { Virtuoso } from "react-virtuoso"; import { providerHealthCheck, updateProxy } from "@/services/api"; import { getProfiles, patchProfile } from "@/services/cmds"; import delayManager from "@/services/delay"; -import useSortProxy from "./use-sort-proxy"; import useHeadState from "./use-head-state"; -import useFilterProxy from "./use-filter-proxy"; +import useFilterSort from "./use-filter-sort"; import ProxyHead from "./proxy-head"; import ProxyItem from "./proxy-item"; @@ -27,14 +26,10 @@ const ProxyGlobal = (props: Props) => { const [headState, setHeadState] = useHeadState(groupName); const virtuosoRef = useRef(); - const filterProxies = useFilterProxy( + const sortedProxies = useFilterSort( proxies, groupName, - headState.filterText - ); - const sortedProxies = useSortProxy( - filterProxies, - groupName, + headState.filterText, headState.sortType ); @@ -85,13 +80,12 @@ const ProxyGlobal = (props: Props) => { } await delayManager.checkListDelay( - { - names: sortedProxies.filter((p) => !p.provider).map((p) => p.name), - groupName, - skipNum: 16, - }, - () => mutate("getProxies") + sortedProxies.filter((p) => !p.provider).map((p) => p.name), + groupName, + 16 ); + + mutate("getProxies"); }); useEffect(() => onLocation(false), [groupName]); diff --git a/src/components/proxy/proxy-group.tsx b/src/components/proxy/proxy-group.tsx index eb1b4c0..e21a6ee 100644 --- a/src/components/proxy/proxy-group.tsx +++ b/src/components/proxy/proxy-group.tsx @@ -18,9 +18,8 @@ import { import { providerHealthCheck, updateProxy } from "@/services/api"; import { getProfiles, patchProfile } from "@/services/cmds"; import delayManager from "@/services/delay"; -import useSortProxy from "./use-sort-proxy"; import useHeadState from "./use-head-state"; -import useFilterProxy from "./use-filter-proxy"; +import useFilterSort from "./use-filter-sort"; import ProxyHead from "./proxy-head"; import ProxyItem from "./proxy-item"; @@ -35,14 +34,10 @@ const ProxyGroup = ({ group }: Props) => { const [headState, setHeadState] = useHeadState(group.name); const virtuosoRef = useRef(); - const filterProxies = useFilterProxy( + const sortedProxies = useFilterSort( group.all, group.name, - headState.filterText - ); - const sortedProxies = useSortProxy( - filterProxies, - group.name, + headState.filterText, headState.sortType ); @@ -105,13 +100,12 @@ const ProxyGroup = ({ group }: Props) => { } await delayManager.checkListDelay( - { - names: sortedProxies.filter((p) => !p.provider).map((p) => p.name), - groupName: group.name, - skipNum: 16, - }, - () => mutate("getProxies") + sortedProxies.filter((p) => !p.provider).map((p) => p.name), + group.name, + 16 ); + + mutate("getProxies"); }); // auto scroll to current index diff --git a/src/components/proxy/proxy-head.tsx b/src/components/proxy/proxy-head.tsx index f906b15..b91f090 100644 --- a/src/components/proxy/proxy-head.tsx +++ b/src/components/proxy/proxy-head.tsx @@ -16,7 +16,7 @@ import { } from "@mui/icons-material"; import delayManager from "@/services/delay"; import type { HeadState } from "./use-head-state"; -import type { ProxySortType } from "./use-sort-proxy"; +import type { ProxySortType } from "./use-filter-sort"; interface Props { sx?: SxProps; diff --git a/src/components/proxy/proxy-item.tsx b/src/components/proxy/proxy-item.tsx index b51681d..7ad6fbd 100644 --- a/src/components/proxy/proxy-item.tsx +++ b/src/components/proxy/proxy-item.tsx @@ -49,6 +49,14 @@ const ProxyItem = (props: Props) => { // -2 为 loading const [delay, setDelay] = useState(-1); + useEffect(() => { + delayManager.setListener(proxy.name, groupName, setDelay); + + return () => { + delayManager.removeListener(proxy.name, groupName); + }; + }, [proxy.name, groupName]); + useEffect(() => { if (!proxy) return; @@ -66,10 +74,7 @@ const ProxyItem = (props: Props) => { const onDelay = useLockFn(async () => { setDelay(-2); - return delayManager - .checkDelay(proxy.name, groupName) - .then((result) => setDelay(result)) - .catch(() => setDelay(1e6)); + setDelay(await delayManager.checkDelay(proxy.name, groupName)); }); return ( diff --git a/src/components/proxy/use-filter-proxy.ts b/src/components/proxy/use-filter-proxy.ts deleted file mode 100644 index 9ec8d3c..0000000 --- a/src/components/proxy/use-filter-proxy.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { useMemo } from "react"; -import delayManager from "@/services/delay"; - -const regex1 = /delay([=<>])(\d+|timeout|error)/i; -const regex2 = /type=(.*)/i; - -/** - * filter the proxy - * according to the regular conditions - */ -export default function useFilterProxy( - proxies: ApiType.ProxyItem[], - groupName: string, - filterText: string -) { - return useMemo(() => { - if (!proxies) return []; - if (!filterText) return proxies; - - const res1 = regex1.exec(filterText); - if (res1) { - const symbol = res1[1]; - const symbol2 = res1[2].toLowerCase(); - const value = - symbol2 === "error" ? 1e5 : symbol2 === "timeout" ? 3000 : +symbol2; - - return proxies.filter((p) => { - const delay = delayManager.getDelay(p.name, groupName); - - if (delay < 0) return false; - if (symbol === "=" && symbol2 === "error") return delay >= 1e5; - if (symbol === "=" && symbol2 === "timeout") - return delay < 1e5 && delay >= 3000; - if (symbol === "=") return delay == value; - if (symbol === "<") return delay <= value; - if (symbol === ">") return delay >= value; - return false; - }); - } - - const res2 = regex2.exec(filterText); - if (res2) { - const type = res2[1].toLowerCase(); - return proxies.filter((p) => p.type.toLowerCase().includes(type)); - } - - return proxies.filter((p) => p.name.includes(filterText.trim())); - }, [proxies, groupName, filterText]); -} diff --git a/src/components/proxy/use-filter-sort.ts b/src/components/proxy/use-filter-sort.ts new file mode 100644 index 0000000..3171b0f --- /dev/null +++ b/src/components/proxy/use-filter-sort.ts @@ -0,0 +1,114 @@ +import { useEffect, useMemo, useState } from "react"; +import delayManager from "@/services/delay"; + +// default | delay | alphabet +export type ProxySortType = 0 | 1 | 2; + +export default function useFilterSort( + proxies: ApiType.ProxyItem[], + groupName: string, + filterText: string, + sortType: ProxySortType +) { + const [refresh, setRefresh] = useState({}); + + useEffect(() => { + let last = 0; + + delayManager.setGroupListener(groupName, () => { + // 简单节流 + const now = Date.now(); + if (now - last > 666) { + last = now; + setRefresh({}); + } + }); + + return () => { + delayManager.removeGroupListener(groupName); + }; + }, [groupName]); + + return useMemo(() => { + const fp = filterProxies(proxies, groupName, filterText); + const sp = sortProxies(fp, groupName, sortType); + return sp; + }, [proxies, groupName, filterText, sortType, refresh]); +} + +/** + * 可以通过延迟数/节点类型 过滤 + */ +const regex1 = /delay([=<>])(\d+|timeout|error)/i; +const regex2 = /type=(.*)/i; + +/** + * filter the proxy + * according to the regular conditions + */ +function filterProxies( + proxies: ApiType.ProxyItem[], + groupName: string, + filterText: string +) { + if (!filterText) return proxies; + + const res1 = regex1.exec(filterText); + if (res1) { + const symbol = res1[1]; + const symbol2 = res1[2].toLowerCase(); + const value = + symbol2 === "error" ? 1e5 : symbol2 === "timeout" ? 3000 : +symbol2; + + return proxies.filter((p) => { + const delay = delayManager.getDelay(p.name, groupName); + + if (delay < 0) return false; + if (symbol === "=" && symbol2 === "error") return delay >= 1e5; + if (symbol === "=" && symbol2 === "timeout") + return delay < 1e5 && delay >= 3000; + if (symbol === "=") return delay == value; + if (symbol === "<") return delay <= value; + if (symbol === ">") return delay >= value; + return false; + }); + } + + const res2 = regex2.exec(filterText); + if (res2) { + const type = res2[1].toLowerCase(); + return proxies.filter((p) => p.type.toLowerCase().includes(type)); + } + + return proxies.filter((p) => p.name.includes(filterText.trim())); +} + +/** + * sort the proxy + */ +function sortProxies( + proxies: ApiType.ProxyItem[], + groupName: string, + sortType: ProxySortType +) { + if (!proxies) return []; + if (sortType === 0) return proxies; + + const list = proxies.slice(); + + if (sortType === 1) { + list.sort((a, b) => { + const ad = delayManager.getDelay(a.name, groupName); + const bd = delayManager.getDelay(b.name, groupName); + + if (ad === -1 || ad === -2) return 1; + if (bd === -1 || bd === -2) return -1; + + return ad - bd; + }); + } else { + list.sort((a, b) => a.name.localeCompare(b.name)); + } + + return list; +} diff --git a/src/components/proxy/use-head-state.ts b/src/components/proxy/use-head-state.ts index 20e839e..5e2a677 100644 --- a/src/components/proxy/use-head-state.ts +++ b/src/components/proxy/use-head-state.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { useRecoilValue } from "recoil"; import { atomCurrentProfile } from "@/services/states"; -import { ProxySortType } from "./use-sort-proxy"; +import { ProxySortType } from "./use-filter-sort"; export interface HeadState { open?: boolean; diff --git a/src/components/proxy/use-sort-proxy.ts b/src/components/proxy/use-sort-proxy.ts deleted file mode 100644 index 0ed49b4..0000000 --- a/src/components/proxy/use-sort-proxy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useMemo } from "react"; -import delayManager from "@/services/delay"; - -// default | delay | alpha -export type ProxySortType = 0 | 1 | 2; - -/** - * sort the proxy - */ -export default function useSortProxy( - proxies: ApiType.ProxyItem[], - groupName: string, - sortType: ProxySortType -) { - return useMemo(() => { - if (!proxies) return []; - if (sortType === 0) return proxies; - - const list = proxies.slice(); - - if (sortType === 1) { - list.sort((a, b) => { - const ad = delayManager.getDelay(a.name, groupName); - const bd = delayManager.getDelay(b.name, groupName); - - if (ad === -1 || ad === -2) return 1; - if (bd === -1 || bd === -2) return -1; - - return ad - bd; - }); - } else { - list.sort((a, b) => a.name.localeCompare(b.name)); - } - - return list; - }, [proxies, groupName, sortType]); -} diff --git a/src/services/delay.ts b/src/services/delay.ts index ce7fcc4..7c24b02 100644 --- a/src/services/delay.ts +++ b/src/services/delay.ts @@ -6,6 +6,12 @@ class DelayManager { private cache = new Map(); private urlMap = new Map(); + // 每个item的监听 + private listenerMap = new Map void>(); + + // 每个分组的监听 + private groupListenerMap = new Map void>(); + setUrl(group: string, url: string) { this.urlMap.set(group, url); } @@ -14,8 +20,29 @@ class DelayManager { return this.urlMap.get(group); } + setListener(name: string, group: string, listener: (time: number) => void) { + const key = hashKey(name, group); + this.listenerMap.set(key, listener); + } + + removeListener(name: string, group: string) { + const key = hashKey(name, group); + this.listenerMap.delete(key); + } + + setGroupListener(group: string, listener: () => void) { + this.groupListenerMap.set(group, listener); + } + + removeGroupListener(group: string) { + this.groupListenerMap.delete(group); + } + setDelay(name: string, group: string, delay: number) { - this.cache.set(hashKey(name, group), [Date.now(), delay]); + const key = hashKey(name, group); + this.cache.set(key, [Date.now(), delay]); + this.listenerMap.get(key)?.(delay); + this.groupListenerMap.get(group)?.(); } getDelay(name: string, group: string) { @@ -44,19 +71,13 @@ class DelayManager { } async checkListDelay( - options: { - names: readonly string[]; - groupName: string; - skipNum: number; - }, - callback: Function + nameList: readonly string[], + groupName: string, + concurrency: number ) { - const { groupName, skipNum } = options; + const names = [...nameList]; - const names = [...options.names]; - const total = names.length; - - let count = 0; + let total = names.length; let current = 0; // 设置正在延迟测试中 @@ -64,7 +85,7 @@ class DelayManager { return new Promise((resolve) => { const help = async (): Promise => { - if (current >= skipNum) return; + if (current >= concurrency) return; const task = names.shift(); if (!task) return; @@ -72,14 +93,13 @@ class DelayManager { current += 1; await this.checkDelay(task, groupName); current -= 1; + total -= 1; - if (count++ % skipNum === 0 || count === total) callback(); - if (count === total) resolve(null); - - return help(); + if (total <= 0) resolve(null); + else return help(); }; - for (let i = 0; i < skipNum; ++i) help(); + for (let i = 0; i < concurrency; ++i) help(); }); } }