import Corestore from 'corestore'
import Hyperdrive from 'hyperdrive'
import b4a from 'b4a'
import { enumerateCores } from './core-enumerator.js'
import { join } from 'path'
export async function openDrive (storagePath, opts = {}) {
const { key = null } = opts
const store = new Corestore(storagePath)
await store.ready()
const drive = key
? new Hyperdrive(store, b4a.isBuffer(key) ? key : b4a.from(key, 'hex'))
: new Hyperdrive(store)
await drive.ready()
return { store, drive }
}
// Discover all Hyperdrives in a corestore by finding cores with Hyperbee headers
export async function discoverDrives (storagePath, opts = {}) {
const dbPath = join(storagePath, 'db')
const cores = await enumerateCores(dbPath, opts)
const store = new Corestore(storagePath)
await store.ready()
const drives = []
for (const c of cores) {
if (!c.publicKey) continue
const keyBuf = b4a.from(c.publicKey, 'hex')
const core = store.get({ key: keyBuf })
await core.ready()
if (core.length > 0) {
try {
const block0 = await core.get(0, { wait: false })
if (block0) {
// Check for Hyperbee header signature: starts with protobuf field for "hyperbee"
const hexStart = b4a.toString(block0.subarray(0, 12), 'hex')
if (hexStart.startsWith('0a0868797065726265')) {
// Extract content key from header
let contentKey = null
if (block0.length > 12) {
try {
// The content key is at offset 12 (tag 0x12, length 0x22=34, then 0x0a 0x20 + 32 bytes key)
const contentKeyStart = block0.indexOf(Buffer.from([0x0a, 0x20]), 10)
if (contentKeyStart >= 0 && contentKeyStart + 34 <= block0.length) {
contentKey = b4a.toString(block0.subarray(contentKeyStart + 2, contentKeyStart + 34), 'hex')
}
} catch {}
}
// Scan some blocks for embedded file paths
const filePaths = []
const scanLimit = Math.min(core.length, 200)
for (let i = 0; i < scanLimit; i++) {
try {
const block = await core.get(i, { wait: false })
if (!block) continue
const str = b4a.toString(block, 'utf8')
const paths = str.match(/\/[a-zA-Z0-9._\-/]+\.[a-zA-Z0-9]+/g)
if (paths) {
for (const p of paths) {
if (!filePaths.includes(p) && filePaths.length < 100) filePaths.push(p)
}
}
} catch { break }
}
drives.push({
key: c.publicKey,
discoveryKey: c.discoveryKey,
corePointer: c.corePointer,
alias: c.alias,
version: core.length,
byteLength: core.byteLength,
writable: core.writable,
contentKey,
filePaths
})
}
}
} catch {}
}
await core.close()
}
await store.close()
return drives
}
export async function listDriveFiles (drive, folder = '/', opts = {}) {
const { recursive = true } = opts
const files = []
const stream = drive.list(folder, { recursive })
return new Promise((resolve, reject) => {
stream.on('data', (entry) => {
const blob = entry.value?.blob
files.push({
path: entry.key,
executable: entry.value?.executable || false,
linkname: entry.value?.linkname || null,
metadata: entry.value?.metadata || null,
blobOffset: blob?.blockOffset,
blobLength: blob?.blockLength,
byteOffset: blob?.byteOffset,
byteLength: blob?.byteLength
})
})
stream.on('end', () => resolve(files))
stream.on('error', reject)
})
}
export async function getDriveStats (drive) {
const files = await listDriveFiles(drive)
let totalBytes = 0
let blobBlocks = 0
const extensions = new Map()
const topDirs = new Map()
for (const f of files) {
const bytes = f.byteLength || 0
totalBytes += bytes
blobBlocks += f.blobLength || 0
// Count by extension
const ext = f.path.includes('.') ? f.path.split('.').pop() : '(none)'
const extInfo = extensions.get(ext) || { count: 0, bytes: 0 }
extInfo.count++
extInfo.bytes += bytes
extensions.set(ext, extInfo)
// Count by top-level directory
const parts = f.path.split('/')
const topDir = parts.length > 2 ? '/' + parts[1] : '/'
const dirInfo = topDirs.get(topDir) || { count: 0, bytes: 0 }
dirInfo.count++
dirInfo.bytes += bytes
topDirs.set(topDir, dirInfo)
}
return {
fileCount: files.length,
totalBytes,
blobBlocks,
driveKey: b4a.toString(drive.key, 'hex'),
driveVersion: drive.version,
writable: drive.writable,
extensions: [...extensions.entries()]
.map(([ext, info]) => ({ ext, ...info }))
.sort((a, b) => b.bytes - a.bytes),
topDirs: [...topDirs.entries()]
.map(([dir, info]) => ({ dir, ...info }))
.sort((a, b) => b.bytes - a.bytes),
files
}
}
export async function readDriveFile (drive, path) {
return drive.get(path)
}
export async function driveSummary (drive) {
const stats = await getDriveStats(drive)
return {
key: stats.driveKey,
version: stats.driveVersion,
writable: stats.writable,
files: stats.fileCount,
totalBytes: stats.totalBytes,
topExtensions: stats.extensions.slice(0, 10),
topDirs: stats.topDirs.slice(0, 10)
}
}