v0.3.22: 存储刷新改用 /member API 秒级精准获取,删除文件遍历
根因: /1/clouddrive/capacity/detail 只返回总容量不含已用空间。
之前的方案遍历所有文件计算已用空间,根目录估算只有 2.38 GB,
全量遍历走后台回调但不被 refreshAllStorageInfo 接收。
修复: 发现 /1/clouddrive/member API 直接返回 use_capacity + total_capacity。
getStorageInfoQuick/getStorageInfo 统一改为 member API 单次调用,
返回 2.76 TB / 6 TB 精准值。删除昂贵的文件遍历逻辑。
refreshAllStorageInfo 移除后台回调复杂度
This commit is contained in:
@@ -1 +1 @@
|
|||||||
0.3.21
|
0.3.22
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ export async function refreshAllStorageInfo(): Promise<void> {
|
|||||||
|
|
||||||
const driver = new Driver({ cookie: cfg.cookie, nickname: cfg.nickname });
|
const driver = new Driver({ cookie: cfg.cookie, nickname: cfg.nickname });
|
||||||
|
|
||||||
// Try getStorageInfo first (quark cleanup API), fallback to getStorageInfoQuick
|
// Try getStorageInfo (now uses fast /member API for accurate data)
|
||||||
let storage: any;
|
let storage: any;
|
||||||
try {
|
try {
|
||||||
storage = await driver.getStorageInfo();
|
storage = await driver.getStorageInfo();
|
||||||
|
|||||||
@@ -16,76 +16,75 @@ const storageCache = { bytes: 0, hourBlock: -1 };
|
|||||||
* (夸克目录的 size 字段 = 该目录内所有文件总大小,无需递归).
|
* (夸克目录的 size 字段 = 该目录内所有文件总大小,无需递归).
|
||||||
* If the API fails (e.g. missing sign params), falls back to fallbackTotal if provided.
|
* If the API fails (e.g. missing sign params), falls back to fallbackTotal if provided.
|
||||||
*/
|
*/
|
||||||
export async function getStorageInfoQuick(cookie, fallbackTotal) {
|
/**
|
||||||
|
* Get storage info from /member API — single fast call returns both used & total capacity.
|
||||||
|
* Falls back to capacity/detail + root file sum if member API fails.
|
||||||
|
*/
|
||||||
|
export async function getStorageInfoQuick(cookie, fallbackTotal?) {
|
||||||
|
try {
|
||||||
|
const memberParams = new URLSearchParams({ pr: 'ucpro', fr: 'pc', uc_param_str: '', __t: String(Date.now()), __dt: '1000' });
|
||||||
|
const resp = await fetch(`https://pan.quark.cn/1/clouddrive/member?${memberParams.toString()}`, {
|
||||||
|
headers: quark_api.getHeaders(cookie),
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
});
|
||||||
|
if (resp.ok) {
|
||||||
|
const data = await resp.json();
|
||||||
|
if (data.status === 200 && data.data) {
|
||||||
|
const usedBytes = data.data.use_capacity ?? 0;
|
||||||
|
const totalBytes = data.data.total_capacity ?? 0;
|
||||||
|
if (totalBytes > 0) {
|
||||||
|
// Cache for calculateUsedSpace compatibility
|
||||||
|
const currentHourBlock = Math.floor(new Date().getHours() / 3);
|
||||||
|
storageCache.bytes = usedBytes;
|
||||||
|
storageCache.hourBlock = currentHourBlock;
|
||||||
|
return {
|
||||||
|
total: quark_api.formatBytes(totalBytes),
|
||||||
|
totalBytes,
|
||||||
|
used: quark_api.formatBytes(usedBytes),
|
||||||
|
usedBytes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
// Fallback: capacity/detail for total + root file sum for used
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams(quark_api.getCommonParams());
|
const params = new URLSearchParams(quark_api.getCommonParams());
|
||||||
const capResponse = await fetch(`${BASE_URL}/1/clouddrive/capacity/detail?${params.toString()}`, {
|
const capResp = await fetch(`${BASE_URL}/1/clouddrive/capacity/detail?${params.toString()}`, {
|
||||||
headers: quark_api.getHeaders(cookie),
|
headers: quark_api.getHeaders(cookie),
|
||||||
signal: AbortSignal.timeout(10000),
|
signal: AbortSignal.timeout(10000),
|
||||||
});
|
});
|
||||||
let totalBytes = 0;
|
let totalBytes = 0;
|
||||||
if (capResponse.ok) {
|
if (capResp.ok) {
|
||||||
const data = await capResponse.json();
|
const data = await capResp.json();
|
||||||
if (data.status === 200 && data.data) {
|
if (data.status === 200 && data.data) {
|
||||||
totalBytes = data.data.capacity_summary?.sum_capacity || 0;
|
totalBytes = data.data.capacity_summary?.sum_capacity || 0;
|
||||||
if (totalBytes === 0) {
|
if (totalBytes === 0) {
|
||||||
const memberships = [...(data.data.effect || []), ...(data.data.expired || [])];
|
const memberships = [...(data.data.effect || []), ...(data.data.expired || [])];
|
||||||
totalBytes = memberships.reduce((max, m) => Math.max(max, m.capacity || 0), 0);
|
totalBytes = memberships.reduce((max: number, m: any) => Math.max(max, m.capacity || 0), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Accurate used space via /member API (1 call, no full traversal needed)
|
|
||||||
// Ref: pan.quark.cn/1/clouddrive/member returns use_capacity + total_capacity
|
|
||||||
let usedBytes = 0;
|
let usedBytes = 0;
|
||||||
try {
|
try {
|
||||||
const memberParams = new URLSearchParams({ pr: 'ucpro', fr: 'pc', uc_param_str: '', __t: String(Date.now()), __dt: '1000' });
|
const rootFiles = await quark_api.listRootDir(cookie);
|
||||||
const memberResp = await fetch(`https://pan.quark.cn/1/clouddrive/member?${memberParams.toString()}`, {
|
for (const f of rootFiles) usedBytes += f.size || 0;
|
||||||
headers: quark_api.getHeaders(cookie),
|
} catch {}
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
});
|
|
||||||
if (memberResp.ok) {
|
|
||||||
const memberData = await memberResp.json();
|
|
||||||
if (memberData.status === 200 && memberData.data?.use_capacity != null) {
|
|
||||||
usedBytes = memberData.data.use_capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
// Fallback: sum root-level file sizes (夸克 folders return size=0)
|
|
||||||
if (usedBytes === 0) {
|
|
||||||
try {
|
|
||||||
const rootFiles = await quark_api.listRootDir(cookie);
|
|
||||||
for (const f of rootFiles) {
|
|
||||||
usedBytes += f.size || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
// Cache the result (3h window)
|
|
||||||
const currentHourBlock = Math.floor(new Date().getHours() / 3);
|
|
||||||
storageCache.bytes = usedBytes;
|
|
||||||
storageCache.hourBlock = currentHourBlock;
|
|
||||||
if (totalBytes > 0) {
|
if (totalBytes > 0) {
|
||||||
return {
|
return { total: quark_api.formatBytes(totalBytes), totalBytes, used: quark_api.formatBytes(usedBytes), usedBytes };
|
||||||
total: quark_api.formatBytes(totalBytes),
|
|
||||||
totalBytes,
|
|
||||||
used: quark_api.formatBytes(usedBytes),
|
|
||||||
usedBytes,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
} catch {}
|
||||||
catch { }
|
|
||||||
// Fallback: try to parse from a human-readable string like "6 TB"
|
// Last resort: parse fallbackTotal string
|
||||||
if (fallbackTotal) {
|
if (fallbackTotal) {
|
||||||
const match = fallbackTotal.match(/^([\d.]+)\s*([KMGT]B?)/i);
|
const match = String(fallbackTotal).match(/^([\d.]+)\s*([KMGT]B?)/i);
|
||||||
if (match) {
|
if (match) {
|
||||||
const num = parseFloat(match[1]);
|
const num = parseFloat(match[1]);
|
||||||
const unit = match[2].toUpperCase();
|
const unit = match[2].toUpperCase();
|
||||||
const multipliers = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3, TB: 1024 ** 4, PB: 1024 ** 5 };
|
const multipliers: Record<string,number> = { B:1, KB:1024, MB:1048576, GB:1073741824, TB:1099511627776 };
|
||||||
const multiplier = multipliers[unit] || multipliers[unit.replace('B', '') + 'B'] || 0;
|
const m = multipliers[unit] || multipliers[unit.replace('B','')+'B'] || 0;
|
||||||
if (multiplier > 0) {
|
if (m > 0) return { total: String(fallbackTotal), totalBytes: Math.round(num*m), used: '-', usedBytes: 0 };
|
||||||
return { total: fallbackTotal, totalBytes: Math.round(num * multiplier), used: '-', usedBytes: 0 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { total: '-', totalBytes: 0, used: '-', usedBytes: 0 };
|
return { total: '-', totalBytes: 0, used: '-', usedBytes: 0 };
|
||||||
@@ -98,52 +97,17 @@ export async function getStorageInfoQuick(cookie, fallbackTotal) {
|
|||||||
* First call returns quickly; full traversal runs async and updates DB later.
|
* First call returns quickly; full traversal runs async and updates DB later.
|
||||||
* `onBackgroundComplete` is called when traversal finishes.
|
* `onBackgroundComplete` is called when traversal finishes.
|
||||||
*/
|
*/
|
||||||
export async function getStorageInfo(cookie, onBackgroundComplete) {
|
/**
|
||||||
try {
|
* Get accurate storage info via /member API (fast, no file traversal needed).
|
||||||
const params = new URLSearchParams(quark_api.getCommonParams());
|
* Returns { total, totalBytes, used, usedBytes } in ~1s.
|
||||||
const response = await fetch(`${BASE_URL}/1/clouddrive/capacity/detail?${params.toString()}`, {
|
* onBackgroundComplete is kept for backward compat (called synchronously).
|
||||||
headers: quark_api.getHeaders(cookie),
|
*/
|
||||||
signal: AbortSignal.timeout(10000),
|
export async function getStorageInfo(cookie, onBackgroundComplete?) {
|
||||||
});
|
const result = await getStorageInfoQuick(cookie);
|
||||||
let totalBytes = 0;
|
if (onBackgroundComplete && result.used !== '-' && result.usedBytes > 0) {
|
||||||
if (response.ok) {
|
try { onBackgroundComplete(result.used, result.total); } catch {}
|
||||||
const data = await response.json();
|
|
||||||
if (data.status === 200 && data.data) {
|
|
||||||
totalBytes = data.data.capacity_summary?.sum_capacity || 0;
|
|
||||||
if (totalBytes === 0) {
|
|
||||||
const memberships = [...(data.data.effect || []), ...(data.data.expired || [])];
|
|
||||||
totalBytes = memberships.reduce((max, m) => Math.max(max, m.capacity || 0), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const totalFormatted = totalBytes > 0 ? quark_api.formatBytes(totalBytes) : '-';
|
|
||||||
// Quick estimation: sum root-level files only
|
|
||||||
let quickUsed = 0;
|
|
||||||
try {
|
|
||||||
const rootFiles = await quark_api.listRootDir(cookie);
|
|
||||||
for (const f of rootFiles) {
|
|
||||||
quickUsed += f.size || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
// Budget full traversal in background (no await)
|
|
||||||
calculateUsedSpace(cookie).then(fullUsed => {
|
|
||||||
if (onBackgroundComplete) {
|
|
||||||
onBackgroundComplete(quark_api.formatBytes(fullUsed), totalFormatted);
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[Storage] Background full traversal failed:', err.message);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
total: totalFormatted,
|
|
||||||
totalBytes,
|
|
||||||
used: quark_api.formatBytes(quickUsed),
|
|
||||||
usedBytes: quickUsed,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return { used: '-', total: '-', usedBytes: 0, totalBytes: 0 };
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Calculate total used space by recursively traversing all files
|
* Calculate total used space by recursively traversing all files
|
||||||
|
|||||||
Reference in New Issue
Block a user