Skip to content

Commit

Permalink
feat: adds w3 rm <root-cid> cmd (#20)
Browse files Browse the repository at this point in the history
without `--shards`, `w3 rm` behaves like rm and silently removes the
thing or errors.

```sh
$ w3 rm bafybeidpouz7j6to2p7ps4j262ii32ov5wnp3rj4slavvsm4dfvonvrc6q

```

with `--shards`, as there might be many, we print each one with a
spinner as we delete them

```sh
$ ❯ w3 rm bafybeib5bn5onczh4n2qb25wqjvyemj6mwk6oshwiaibcef2testdboqra --shards
⁂ removing 4 shards
✔ bagbaieraxwxthv7ntlucs2lpghxv7wsrvoyca4gtna2ny4tg7mlkquqef7aq removed
✔ bagbaierawqhk2jn2qwzdm5ajdevwp2t5kari6d6qok7iu2jyfm6kndndemla removed
✔ bagbaierarihlji6ep47f42fv46atov4vh7kyhledn5shif5zwbpszsk3kltq removed
✔ bagbaieraz2bfnvj6rooqpjny2j3xjeqp2otndee5oylxl6jmdll7ydxa5lwq removed
```


License: MIT
Signed-off-by: Oli Evans <oli@protocol.ai>
  • Loading branch information
olizilla authored Dec 16, 2022
1 parent 19a2329 commit 899a4d4
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 1 deletion.
7 changes: 7 additions & 0 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
addProof,
listProofs,
upload,
remove,
list,
whoami
} from './index.js'
Expand Down Expand Up @@ -46,6 +47,12 @@ cli.command('ls')
.option('--shards', 'Pretty print with shards in output')
.action(list)

cli.command('rm <root-cid>')
.example('rm bafy...')
.describe('Remove an upload from the uploads listing. Pass --shards to delete the actual data if you are sure no other uploads need them')
.option('--shards', 'Remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards.')
.action(remove)

cli.command('whoami')
.describe('Print information about the current agent.')
.action(whoami)
Expand Down
52 changes: 51 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import fs from 'fs'
import ora from 'ora'
import ora, { oraPromise } from 'ora'
import tree from 'pretty-tree'
import { Readable } from 'stream'
import { CID } from 'multiformats/cid'
import * as DID from '@ipld/dag-ucan/did'
import { CarWriter } from '@ipld/car'
import { getClient, checkPathsExist, filesize, readProof, filesFromPaths } from './lib.js'
Expand Down Expand Up @@ -75,6 +76,55 @@ export async function list (opts) {
console.log('⁂ Try out `w3 up <path to files>` to upload some')
}
}
/**
* @param {string} rootCid
* @param {object} opts
* @param {boolean} [opts.shards]
*/
export async function remove (rootCid, opts) {
let root
try {
root = CID.parse(rootCid.trim())
} catch (err) {
console.error(`Error: ${rootCid} is not a CID`)
process.exit(1)
}
const client = await getClient()
let upload
try {
upload = await client.capability.upload.remove(root)
} catch (err) {
console.error(`Remove failed: ${err.message ?? err}`)
console.error(err)
process.exit(1)
}
if (!opts.shards) {
return
}
if (!upload) {
return console.log('⁂ upload not found. could not determine shards to remove.')
}
if (!upload.shards || !upload.shards.length) {
return console.log('⁂ no shards to remove.')
}

const { shards } = upload
console.log(`⁂ removing ${shards.length} shard${shards.length === 1 ? '' : 's'}`)

function removeShard (shard) {
return oraPromise(client.capability.store.remove(shard), {
text: `${shard}`,
successText: `${shard} removed`,
failText: `${shard} failed`
})
}

const results = await Promise.allSettled(shards.map(removeShard))

if (results.some(res => res.status === 'rejected')) {
process.exit(1)
}
}

/**
* @param {string} name
Expand Down
96 changes: 96 additions & 0 deletions test/bin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,102 @@ test('w3 ls', async (t) => {
t.notThrows(() => CID.parse(JSON.parse(list1.stdout).root))
})

test('w3 remove', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
upload: {
remove: provide(UploadCapabilities.remove, ({ invocation }) => {
const { nb } = invocation.capabilities[0]
return { root: nb.root }
})
}
})
t.context.setService(service)

t.throwsAsync(() => execa('./bin.js', ['rm', 'nope'], { env }), { message: /not a CID/ })

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm'], { env })
t.is(rm.exitCode, 0)
t.is(service.upload.remove.callCount, 1)
t.is(service.store.remove.callCount, 0)
t.is(rm.stdout, '')
})

test('w3 remove - no such upload', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
upload: {
remove: provide(UploadCapabilities.remove, () => {})
}
})
t.context.setService(service)

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', '--shards'], { env })
t.is(rm.exitCode, 0)
t.is(rm.stdout, '⁂ upload not found. could not determine shards to remove.')
})

test('w3 remove --shards', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
store: {
remove: provide(StoreCapabilities.remove, () => {})
},
upload: {
remove: provide(UploadCapabilities.remove, ({ invocation }) => {
const { nb } = invocation.capabilities[0]
return {
root: nb.root,
shards: [
CID.parse('bagbaiera7ciaeifwrn7oo35gxdalocfj23vkvqus2eup27wt2qcxlvta2wya'),
CID.parse('bagbaiera7ciaeifwrn7oo35gxdalocfj23vkvqus2eup27wt2qcxlvta2wya')
]
}
})
}
})
t.context.setService(service)

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', '--shards'], { env })
t.is(rm.exitCode, 0)
t.is(service.upload.remove.callCount, 1)
t.is(service.store.remove.callCount, 2)
})

test('w3 remove --shards - no shards to remove', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
store: {
remove: provide(StoreCapabilities.remove, () => {})
},
upload: {
remove: provide(UploadCapabilities.remove, ({ invocation }) => {
const { nb } = invocation.capabilities[0]
return { root: nb.root }
})
}
})
t.context.setService(service)

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', '--shards'], { env })
t.is(rm.exitCode, 0)
t.is(service.upload.remove.callCount, 1)
t.is(service.store.remove.callCount, 0)
t.is(rm.stdout, '⁂ no shards to remove.')
})

test('w3 delegation create', async t => {
const env = t.context.env.alice

Expand Down

0 comments on commit 899a4d4

Please sign in to comment.