UNPKG

@cessnetwork/api

Version:

CESS Chain Interface Implementation in TypeScript

151 lines 5.89 kB
import path from "path"; import { promises as fs } from "fs"; import { aesCbcDecrypt, rsRestore, writeFile } from "@cessnetwork/util"; const SEGMENT_SIZE = 1024 * 1024 * 8; // 8MB const FRAGMENT_SIZE = 1024 * 1024; // 1MB const DATA_SHARDS = 4; async function downloadFileFromSminer(cli, downloadFileReq) { const userfile = path.join(downloadFileReq.savePath, downloadFileReq.fid); try { const stat = await fs.stat(userfile); if (stat.size > 0) { return userfile; } } catch (e) { // file does not exist, which is what we want } await fs.mkdir(downloadFileReq.savePath, { recursive: true }); const segmentsDir = path.join(downloadFileReq.savePath, 'segments'); await fs.mkdir(segmentsDir, { recursive: true }); try { const segmentPaths = []; for (const segment of downloadFileReq.fmeta.segmentList) { const segmentPath = await downloadSegmentFromSminer(cli, { savePath: segmentsDir, fid: downloadFileReq.fid, segmentHash: segment.hash, fragments: segment.fragmentList, signData: { account: downloadFileReq.signData.account, message: downloadFileReq.signData.message, sign: downloadFileReq.signData.sign } }); segmentPaths.push(segmentPath); } const fd = await fs.open(userfile, 'w'); let writeCount = 0; for (const segmentPath of segmentPaths) { let data = await fs.readFile(segmentPath); if (downloadFileReq.cipher) { data = aesCbcDecrypt(data, Buffer.from(downloadFileReq.cipher)); } if (writeCount + 1 >= downloadFileReq.fmeta.segmentList.length) { await fd.write(data, 0, Number(downloadFileReq.fmeta.fileSize.toString()) - Number(writeCount * SEGMENT_SIZE)); } else { await fd.write(data); } writeCount++; } await fd.close(); return userfile; } finally { await fs.rm(segmentsDir, { recursive: true, force: true }); } } async function downloadSegmentFromSminer(cli, downloadSegmentReq) { const fragmentPaths = []; const segmentPath = path.join(downloadSegmentReq.savePath, downloadSegmentReq.segmentHash); for (const fragment of downloadSegmentReq.fragments) { if (fragmentPaths.length >= DATA_SHARDS) { break; } const fragmentPath = path.join(downloadSegmentReq.savePath, fragment.hash); try { const stat = await fs.stat(fragmentPath); if (stat.size === FRAGMENT_SIZE) { fragmentPaths.push(fragmentPath); continue; } } catch (e) { throw new Error(`Download failed with status code ${fragment.hash}: ${e}`); } try { const minerInfo = await cli.queryMinerByAccountId(fragment.miner); const endpoint = minerInfo?.endpoint ? minerInfo?.endpoint : ''; const data = await downloadFragmentFromSMiner({ minerEndpoint: endpoint, fid: downloadSegmentReq.fid, fragmentHash: fragment.hash, signData: { account: downloadSegmentReq.signData.account, message: downloadSegmentReq.signData.message, sign: downloadSegmentReq.signData.sign } }); await writeFile(data, fragmentPath); fragmentPaths.push(fragmentPath); } catch (e) { console.error(`Failed to download fragment ${fragment.hash} from miner ${fragment.miner}:`, e); } } await rsRestore(segmentPath, fragmentPaths); return segmentPath; } async function downloadFragmentFromSMiner(downloadFragmentReq) { let url = downloadFragmentReq.minerEndpoint; if (url.endsWith('/')) { url += 'fragment'; } else { url += '/fragment'; } if (!url.startsWith('http://')) { url = `http://${url}`; } // Create AbortController for timeout functionality const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout try { const response = await fetch(url, { method: 'GET', headers: { 'Fid': downloadFragmentReq.fid, 'Fragment': downloadFragmentReq.fragmentHash, 'Account': downloadFragmentReq.signData.account, 'Message': downloadFragmentReq.signData.message, 'Signature': downloadFragmentReq.signData.sign, }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`Download failed with status code ${response.status}: ${response.statusText}`); } // Get ArrayBuffer from response and convert to Buffer const arrayBuffer = await response.arrayBuffer(); return Buffer.from(arrayBuffer); } catch (error) { clearTimeout(timeoutId); if (error instanceof Error) { if (error.name === 'AbortError') { throw new Error('Request timeout: No response received within 30 seconds'); } else if (error.message.includes('fetch')) { throw new Error('Network error: No response received from server'); } else { throw new Error(`Request error: ${error.message}`); } } throw error; } } export { downloadFileFromSminer, downloadSegmentFromSminer, downloadFragmentFromSMiner }; //# sourceMappingURL=sminer.js.map