|
@@ -16,13 +16,16 @@ import { ContentHash } from './ContentHash'
|
|
import { promisify } from 'util'
|
|
import { promisify } from 'util'
|
|
import { createType } from '@joystream/types'
|
|
import { createType } from '@joystream/types'
|
|
import { Readable, pipeline } from 'stream'
|
|
import { Readable, pipeline } from 'stream'
|
|
|
|
+import { Logger } from 'winston'
|
|
import _ from 'lodash'
|
|
import _ from 'lodash'
|
|
|
|
+import { createLogger } from '../logging'
|
|
|
|
|
|
export type AssetsManagerConfig = {
|
|
export type AssetsManagerConfig = {
|
|
preferredDownloadSpEndpoints?: string[]
|
|
preferredDownloadSpEndpoints?: string[]
|
|
uploadSpBucketId: number
|
|
uploadSpBucketId: number
|
|
uploadSpEndpoint: string
|
|
uploadSpEndpoint: string
|
|
dataDir: string
|
|
dataDir: string
|
|
|
|
+ migrationStatePath: string
|
|
}
|
|
}
|
|
|
|
|
|
export type AssetsManagerParams = {
|
|
export type AssetsManagerParams = {
|
|
@@ -56,6 +59,8 @@ export class AssetsManager {
|
|
private resizer: ImageResizer
|
|
private resizer: ImageResizer
|
|
private queuedUploads: Set<string>
|
|
private queuedUploads: Set<string>
|
|
private isQueueProcessing = false
|
|
private isQueueProcessing = false
|
|
|
|
+ private queueFilePath: string
|
|
|
|
+ private logger: Logger
|
|
|
|
|
|
public get queueSize(): number {
|
|
public get queueSize(): number {
|
|
return this.queuedUploads.size
|
|
return this.queuedUploads.size
|
|
@@ -79,8 +84,11 @@ export class AssetsManager {
|
|
this.config = config
|
|
this.config = config
|
|
this.resizer = new ImageResizer()
|
|
this.resizer = new ImageResizer()
|
|
this.queuedUploads = new Set()
|
|
this.queuedUploads = new Set()
|
|
|
|
+ this.logger = createLogger('Assets Manager')
|
|
fs.mkdirSync(this.tmpAssetPath(''), { recursive: true })
|
|
fs.mkdirSync(this.tmpAssetPath(''), { recursive: true })
|
|
fs.mkdirSync(this.assetPath(''), { recursive: true })
|
|
fs.mkdirSync(this.assetPath(''), { recursive: true })
|
|
|
|
+ this.queueFilePath = path.join(this.config.migrationStatePath, `unprocessedUploads_${Date.now()}.json`)
|
|
|
|
+ this.logger.info(`Failed/pending uploads will be saved to ${this.queueFilePath}`)
|
|
}
|
|
}
|
|
|
|
|
|
private static async getSumerStorageProviderEndpoints(queryNodeApi: QueryNodeApi): Promise<string[]> {
|
|
private static async getSumerStorageProviderEndpoints(queryNodeApi: QueryNodeApi): Promise<string[]> {
|
|
@@ -116,26 +124,20 @@ export class AssetsManager {
|
|
targetSize?: [number, number]
|
|
targetSize?: [number, number]
|
|
): Promise<DataObjectCreationParameters | undefined> {
|
|
): Promise<DataObjectCreationParameters | undefined> {
|
|
if (data.liaisonJudgement !== 'ACCEPTED') {
|
|
if (data.liaisonJudgement !== 'ACCEPTED') {
|
|
- console.error(
|
|
|
|
|
|
+ this.logger.warn(
|
|
`Data object ${data.joystreamContentId} has invalid liason judgement: ${data.liaisonJudgement}. Skipping...`
|
|
`Data object ${data.joystreamContentId} has invalid liason judgement: ${data.liaisonJudgement}. Skipping...`
|
|
)
|
|
)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
let objectSize = new BN(data.size).toNumber()
|
|
let objectSize = new BN(data.size).toNumber()
|
|
- let path: string
|
|
|
|
- try {
|
|
|
|
- path = await this.fetchAssetWithRetry(data.joystreamContentId, objectSize)
|
|
|
|
- } catch (e) {
|
|
|
|
- console.error(`Data object ${data.joystreamContentId} was not fetched: ${(e as Error).message}`)
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
|
|
+ const path = await this.fetchAssetWithRetry(data.joystreamContentId, objectSize)
|
|
if (targetSize) {
|
|
if (targetSize) {
|
|
try {
|
|
try {
|
|
await this.resizer.resize(path, targetSize)
|
|
await this.resizer.resize(path, targetSize)
|
|
// Re-estabilish object size
|
|
// Re-estabilish object size
|
|
objectSize = fs.statSync(path).size
|
|
objectSize = fs.statSync(path).size
|
|
} catch (e) {
|
|
} catch (e) {
|
|
- console.error(
|
|
|
|
|
|
+ this.logger.error(
|
|
`Could not resize image ${path} to target size ${targetSize[0]}/${targetSize[1]}: ${(e as Error).message}`
|
|
`Could not resize image ${path} to target size ${targetSize[0]}/${targetSize[1]}: ${(e as Error).message}`
|
|
)
|
|
)
|
|
}
|
|
}
|
|
@@ -256,17 +258,11 @@ export class AssetsManager {
|
|
} catch (e) {
|
|
} catch (e) {
|
|
uploadSuccesful = false
|
|
uploadSuccesful = false
|
|
const msg = this.reqErrorMessage(e)
|
|
const msg = this.reqErrorMessage(e)
|
|
- console.error(`Upload of object ${dataObjectId} to ${uploadSpEndpoint} failed: ${msg}`)
|
|
|
|
|
|
+ this.logger.error(`Upload of object ${dataObjectId} to ${uploadSpEndpoint} failed: ${msg}`)
|
|
}
|
|
}
|
|
|
|
|
|
if (uploadSuccesful) {
|
|
if (uploadSuccesful) {
|
|
- // Remove asset from queuedUploads and temporary storage
|
|
|
|
- this.queuedUploads.delete(`${bagId}|${dataObjectId}`)
|
|
|
|
- try {
|
|
|
|
- fs.rmSync(dataPath)
|
|
|
|
- } catch (e) {
|
|
|
|
- console.error(`Could not remove file "${dataPath}" after succesful upload...`)
|
|
|
|
- }
|
|
|
|
|
|
+ this.finalizeUpload(bagId, dataObjectId, dataPath)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -275,7 +271,7 @@ export class AssetsManager {
|
|
throw new Error('Uploads queue is already beeing processed!')
|
|
throw new Error('Uploads queue is already beeing processed!')
|
|
}
|
|
}
|
|
this.isQueueProcessing = true
|
|
this.isQueueProcessing = true
|
|
- console.log(`Uploading ${this.queueSize} data objects...`)
|
|
|
|
|
|
+ this.logger.info(`Uploading ${this.queueSize} data objects...`)
|
|
await Promise.all(
|
|
await Promise.all(
|
|
Array.from(this.queuedUploads).map((queuedUpload) => {
|
|
Array.from(this.queuedUploads).map((queuedUpload) => {
|
|
const [bagId, objectId] = queuedUpload.split('|')
|
|
const [bagId, objectId] = queuedUpload.split('|')
|
|
@@ -290,19 +286,35 @@ export class AssetsManager {
|
|
this.queuedUploads = new Set(queue)
|
|
this.queuedUploads = new Set(queue)
|
|
}
|
|
}
|
|
|
|
|
|
- public saveQueue(queueFilePath: string): void {
|
|
|
|
- fs.writeFileSync(queueFilePath, JSON.stringify(Array.from(this.queuedUploads)))
|
|
|
|
|
|
+ public saveQueue(): void {
|
|
|
|
+ fs.writeFileSync(this.queueFilePath, JSON.stringify(Array.from(this.queuedUploads)))
|
|
|
|
+ this.logger.debug(`${this.queueFilePath} updated`, { queueSize: this.queuedUploads.size })
|
|
}
|
|
}
|
|
|
|
|
|
private queueUpload(bagId: BagId, objectId: DataObjectId): void {
|
|
private queueUpload(bagId: BagId, objectId: DataObjectId): void {
|
|
const bagIdStr = `dynamic:channel:${bagId.asType('Dynamic').asType('Channel').toString()}`
|
|
const bagIdStr = `dynamic:channel:${bagId.asType('Dynamic').asType('Channel').toString()}`
|
|
this.queuedUploads.add(`${bagIdStr}|${objectId.toString()}`)
|
|
this.queuedUploads.add(`${bagIdStr}|${objectId.toString()}`)
|
|
|
|
+ this.saveQueue()
|
|
}
|
|
}
|
|
|
|
|
|
- public async uploadFromEvents(events: IEvent<[Vec<DataObjectId>, UploadParameters, Balance]>[]): Promise<void> {
|
|
|
|
|
|
+ private finalizeUpload(bagId: string, dataObjectId: number, dataPath: string) {
|
|
|
|
+ this.queuedUploads.delete(`${bagId}|${dataObjectId}`)
|
|
|
|
+ this.saveQueue()
|
|
|
|
+ try {
|
|
|
|
+ fs.rmSync(dataPath)
|
|
|
|
+ } catch (e) {
|
|
|
|
+ this.logger.error(`Could not remove file "${dataPath}" after succesful upload...`)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public queueUploadsFromEvents(events: IEvent<[Vec<DataObjectId>, UploadParameters, Balance]>[]): void {
|
|
|
|
+ let queuedUploads = 0
|
|
events.map(({ data: [objectIds, uploadParams] }) => {
|
|
events.map(({ data: [objectIds, uploadParams] }) => {
|
|
- objectIds.forEach((objectId) => this.queueUpload(uploadParams.bagId, objectId))
|
|
|
|
|
|
+ objectIds.forEach((objectId) => {
|
|
|
|
+ this.queueUpload(uploadParams.bagId, objectId)
|
|
|
|
+ ++queuedUploads
|
|
|
|
+ })
|
|
})
|
|
})
|
|
- await this.processQueuedUploads()
|
|
|
|
|
|
+ this.logger.info(`Added ${queuedUploads} new data object uploads to the upload queue`)
|
|
}
|
|
}
|
|
}
|
|
}
|