Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 123 additions & 30 deletions src/command/upload.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { RedundancyLevel, Reference, Tag, Utils } from '@ethersphere/bee-js'
import { FileUploadOptions, RedundancyLevel, Reference, Tag, Utils } from '@ethersphere/bee-js'
import { Numbers, Optional, System } from 'cafe-utility'
import chalk from 'chalk'
import { Presets, SingleBar } from 'cli-progress'
import * as FS from 'fs'
import { Argument, LeafCommand, Option } from 'furious-commander'
import { join, parse } from 'path'
import { exit } from 'process'
import { setCurlStore } from '../curl'
import { AccessHistory } from '../service/access'
import { AccessHistoryOperation } from '../service/access/types/history-event'
import { History } from '../service/history'
import { pickStamp, printStamp } from '../service/stamp'
import { fileExists, readStdin } from '../utils'
Expand All @@ -14,7 +17,7 @@ import { getMime } from '../utils/mime'
import { stampProperties } from '../utils/option'
import { printQRCodeWithLabel } from '../utils/qr'
import { createSpinner } from '../utils/spinner'
import { createKeyValue, warningSymbol, warningText } from '../utils/text'
import { createKeyValue, deprecationWarningText, warningSymbol, warningText } from '../utils/text'
import { RootCommand } from './root-command'
import { VerbosityLevel } from './root-command/command-log'

Expand Down Expand Up @@ -56,6 +59,14 @@ export class Upload extends RootCommand implements LeafCommand {
})
public act!: boolean

@Option({
key: 'share-with',
type: 'string',
description: 'Name of the grantee list to share the uploaded content with',
conflicts: 'act',
})
public shareWith!: string

@Option({ key: 'act-history-address', type: 'string', description: 'ACT history address' })
public optHistoryAddress!: string

Expand Down Expand Up @@ -137,6 +148,14 @@ export class Upload extends RootCommand implements LeafCommand {
public async run(usedFromOtherCommand = false): Promise<void> {
super.init()

if (this.act || this.optHistoryAddress) {
this.console.log(
deprecationWarningText(
'--act and --act-history-address options are deprecated and will be removed in future versions. Please use --share-with option instead.',
),
)
}

if (await this.hasUnsupportedGatewayOptions()) {
exit(1)
}
Expand Down Expand Up @@ -179,7 +198,7 @@ export class Upload extends RootCommand implements LeafCommand {
const swarmHash = this.result.getOrThrow().toHex()
this.console.log(createKeyValue('Swarm hash', swarmHash))

if (this.act) {
if (this.usingACT()) {
this.console.log(createKeyValue('Swarm history address', this.historyAddress.getOrThrow().toHex()))
}
this.console.dim('Waiting for file chunks to be synced on Swarm network...')
Expand Down Expand Up @@ -213,6 +232,11 @@ export class Upload extends RootCommand implements LeafCommand {
if (this.qr) {
printQRCodeWithLabel(url, 'QR for URL', this.console)
}

if (this.shareWith) {
this.addNewAccessHistoryEvent()
await this.printShareInstructions()
}
}

private async uploadAnyWithSpinner(tag: Tag | undefined, isFolder: boolean): Promise<string> {
Expand Down Expand Up @@ -248,35 +272,42 @@ export class Upload extends RootCommand implements LeafCommand {
private async uploadStdin(tag?: Tag): Promise<string> {
if (this.fileName) {
const contentType = this.contentType || getMime(this.fileName) || undefined
const { reference, historyAddress } = await this.bee.uploadFile(this.stamp, this.stdinData, this.fileName, {
let uploadOptions = {
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
contentType,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
})
} as FileUploadOptions
uploadOptions = this.prepareACTUploadOptions(uploadOptions)

const { reference, historyAddress } = await this.bee.uploadFile(
this.stamp,
this.stdinData,
this.fileName,
uploadOptions,
)
this.result = Optional.of(reference)

if (this.act) {
if (this.usingACT()) {
this.historyAddress = historyAddress
}

return `${this.bee.url}/bzz/${reference.toHex()}/`
} else {
const { reference, historyAddress } = await this.bee.uploadData(this.stamp, this.stdinData, {
let uploadOptions = {
tag: tag?.uid,
deferred: this.deferred,
encrypt: this.encrypt,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
})
} as FileUploadOptions
uploadOptions = this.prepareACTUploadOptions(uploadOptions)

const { reference, historyAddress } = await this.bee.uploadData(this.stamp, this.stdinData, uploadOptions)
this.result = Optional.of(reference)

if (this.act) {
if (this.usingACT()) {
this.historyAddress = historyAddress
}

Expand All @@ -290,20 +321,20 @@ export class Upload extends RootCommand implements LeafCommand {
folder: true,
type: 'buffer',
})
const { reference, historyAddress } = await this.bee.uploadFilesFromDirectory(this.stamp, this.path, {
let uploadOptions = {
indexDocument: this.indexDocument,
errorDocument: this.errorDocument,
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
})
} as FileUploadOptions
uploadOptions = this.prepareACTUploadOptions(uploadOptions)
const { reference, historyAddress } = await this.bee.uploadFilesFromDirectory(this.stamp, this.path, uploadOptions)
this.result = Optional.of(reference)

if (this.act) {
if (this.usingACT()) {
this.historyAddress = historyAddress
}

Expand All @@ -319,24 +350,24 @@ export class Upload extends RootCommand implements LeafCommand {
})
const readable = FS.createReadStream(this.path)
const parsedPath = parse(this.path)
let uploadOptions = {
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
contentType,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
} as FileUploadOptions
uploadOptions = this.prepareACTUploadOptions(uploadOptions)
const { reference, historyAddress } = await this.bee.uploadFile(
this.stamp,
readable,
this.determineFileName(parsedPath.base),
{
tag: tag && tag.uid,
pin: this.pin,
encrypt: this.encrypt,
contentType,
deferred: this.deferred,
redundancyLevel: this.determineRedundancyLevel(),
act: this.act,
actHistoryAddress: this.optHistoryAddress,
},
uploadOptions,
)
this.result = Optional.of(reference)

if (this.act) {
if (this.usingACT()) {
this.historyAddress = historyAddress
}

Expand Down Expand Up @@ -450,7 +481,7 @@ export class Upload extends RootCommand implements LeafCommand {
return false
}

if (this.act) {
if (this.usingACT()) {
this.console.error('You are trying to upload to the gateway which does not support ACT.')
this.console.error('Please try again without the --act option.')

Expand Down Expand Up @@ -551,4 +582,66 @@ export class Upload extends RootCommand implements LeafCommand {
return 'file'
}
}

private usingACT(): boolean {
return this.act || Boolean(this.shareWith)
}

private addNewAccessHistoryEvent() {
const accessHistory = new AccessHistory(this.commandConfig, this.console)
const lastHistoryEvent = accessHistory.getLatestEvent(this.shareWith)

if (!lastHistoryEvent) {
this.console.error(`Grantee list with name '${this.shareWith}' does not exist!`)
exit(1)
}
accessHistory.addEvent(this.shareWith, {
stampId: this.stamp,
historyAddress: this.historyAddress.getOrThrow().toHex(),
granteeListRef: lastHistoryEvent.granteeListRef,
operation: AccessHistoryOperation.Upload,
createdAt: Date.now(),
})
}

private async printShareInstructions() {
const { publicKey } = await this.bee.getNodeAddresses()
this.console.log(
'\nTo share the uploaded content with your grantees, please provide them with the following information:\n',
)
const token = `${publicKey}:${this.historyAddress.getOrThrow().toHex()}`
this.console.log(chalk.bold(token))
this.console.log(
'\nThey can use this information, when using the download command, providing it in the --access option.',
)
this.console.log('Example:\n')
this.console.log(chalk.bold(`> swarm-cli download ${this.result.getOrThrow().toHex()} --access ${token}\n`))
}

private prepareACTUploadOptions(uploadOptions: FileUploadOptions): FileUploadOptions {
const options = { ...uploadOptions }

if (this.act) {
options.act = this.act

if (this.optHistoryAddress) {
options.actHistoryAddress = this.optHistoryAddress
}
}

if (this.shareWith) {
const accessHistory = new AccessHistory(this.commandConfig, this.console)
const lastHistoryEvent = accessHistory.getLatestEvent(this.shareWith)

if (!lastHistoryEvent) {
this.console.error(`Grantee list with name '${this.shareWith}' does not exist!`)
exit(1)
}

options.act = true
options.actHistoryAddress = lastHistoryEvent.historyAddress
}

return options
}
}
6 changes: 6 additions & 0 deletions src/service/access/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export class AccessHistory {
return history[granteeListName]
}

public getLatestEvent(granteeListName: string): AccessHistoryEvent | null {
const events = this.getEvents(granteeListName).sort((a, b) => b.createdAt - a.createdAt)

return events.length > 0 ? events[0] : null
}

public getEventsByType(granteeListName: string, eventType: AccessHistoryOperation): AccessHistoryEvent[] {
return this.getEvents(granteeListName).filter(event => event.operation === eventType)
}
Expand Down
1 change: 1 addition & 0 deletions src/service/access/types/history-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum AccessHistoryOperation {
Init = 'init',
Grant = 'grant',
Revoke = 'revoke',
Upload = 'upload',
}

export type AccessHistoryEvent = {
Expand Down
4 changes: 4 additions & 0 deletions src/utils/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export function warningText(string: string): string {
return chalk.yellow(string)
}

export function deprecationWarningText(string: string): string {
return chalk.yellow(`DEPRECATED: ${string}`)
}

export function errorText(string: string): string {
return chalk.red(string)
}
Expand Down
Loading
Loading