123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- import BN from 'bn.js';
- import { MediaTransport, ChannelValidationConstraints } from './transport';
- import { ClassId, Class, EntityId, Entity, ClassName } from '@joystream/types/lib/versioned-store';
- import { InputValidationLengthConstraint } from '@joystream/types/lib/forum';
- import { PlainEntity, EntityCodecResolver } from '@joystream/types/lib/versioned-store/EntityCodec';
- import { MusicTrackType } from './schemas/music/MusicTrack';
- import { MusicAlbumType } from './schemas/music/MusicAlbum';
- import { VideoType } from './schemas/video/Video';
- import { ContentLicenseType } from './schemas/general/ContentLicense';
- import { CurationStatusType } from './schemas/general/CurationStatus';
- import { LanguageType } from './schemas/general/Language';
- import { MediaObjectType } from './schemas/general/MediaObject';
- import { MusicGenreType } from './schemas/music/MusicGenre';
- import { MusicMoodType } from './schemas/music/MusicMood';
- import { MusicThemeType } from './schemas/music/MusicTheme';
- import { PublicationStatusType } from './schemas/general/PublicationStatus';
- import { VideoCategoryType } from './schemas/video/VideoCategory';
- import { ChannelEntity } from './entities/ChannelEntity';
- import { ChannelId, Channel } from '@joystream/types/lib/content-working-group';
- import { ApiPromise } from '@polkadot/api/index';
- import { ApiProps } from '@polkadot/react-api/types';
- import { Vec } from '@polkadot/types';
- import { LinkageResult } from '@polkadot/types/codec/Linkage';
- import { ChannelCodec } from './schemas/channel/Channel';
- import { FeaturedContentType } from './schemas/general/FeaturedContent';
- import { AnyChannelId, asChannelId, AnyClassId, AnyEntityId } from './common/TypeHelpers';
- import { SimpleCache } from '@polkadot/joy-utils/SimpleCache';
- import { ValidationConstraint } from '@polkadot/joy-utils/ValidationConstraint';
- const FIRST_CHANNEL_ID = 1;
- const FIRST_CLASS_ID = 1;
- const FIRST_ENTITY_ID = 1;
- const ClassNamesThatRequireLoadingInternals: ClassName[] = [
- 'Video',
- 'MusicTrack',
- 'MusicAlbum'
- ]
- const ClassNamesThatCanBeCached: ClassName[] = [
- 'ContentLicense',
- 'CurationStatus',
- 'Language',
- 'MusicGenre',
- 'MusicMood',
- 'MusicTheme',
- 'PublicationStatus',
- 'VideoCategory',
- ]
- export class SubstrateTransport extends MediaTransport {
- protected api: ApiPromise
- private entityCodecResolver: EntityCodecResolver | undefined
- private channelCache: SimpleCache<ChannelId, ChannelEntity>
- private entityCache: SimpleCache<EntityId, PlainEntity>
- private classCache: SimpleCache<ClassId, Class>
-
-
- private idsOfEntitiesToKeepInCache: Set<string> = new Set()
- constructor(api: ApiProps) {
- super();
- console.log('Create new SubstrateTransport')
- if (!api) {
- throw new Error('Cannot create SubstrateTransport: Substrate API is required');
- } else if (!api.isApiReady) {
- throw new Error('Cannot create SubstrateTransport: Substrate API is not ready yet');
- }
- this.api = api.api
- const loadChannelsByIds = this.loadChannelsByIds.bind(this)
- const loadEntitiesByIds = this.loadPlainEntitiesByIds.bind(this)
- const loadClassesByIds = this.loadClassesByIds.bind(this)
- this.channelCache = new SimpleCache('Channel Cache', loadChannelsByIds)
- this.entityCache = new SimpleCache('Entity Cache', loadEntitiesByIds)
- this.classCache = new SimpleCache('Class Cache', loadClassesByIds)
- }
- protected notImplementedYet<T> (): T {
- throw new Error('Substrate transport: Requested function is not implemented yet')
- }
-
- cwgQuery() {
- return this.api.query.contentWorkingGroup
- }
-
- vsQuery() {
- return this.api.query.versionedStore
- }
- clearSessionCache() {
- console.info(`Clear cache of Substrate Transport`)
- this.channelCache.clear()
- this.entityCache.clearExcept(
- this.idsOfEntitiesToKeepInCache
- )
-
-
- }
-
-
- async nextChannelId(): Promise<ChannelId> {
- return await this.cwgQuery().nextChannelId<ChannelId>()
- }
- async allChannelIds(): Promise<ChannelId[]> {
- let nextId = (await this.nextChannelId()).toNumber()
- if (nextId < 1) nextId = 1
- const allIds: ChannelId[] = []
- for (let id = FIRST_CHANNEL_ID; id < nextId; id++) {
- allIds.push(new ChannelId(id))
- }
- return allIds
- }
- async loadChannelsByIds(ids: AnyChannelId[]): Promise<ChannelEntity[]> {
- const channelTuples = await this.cwgQuery().channelById.multi<LinkageResult>(ids)
- return channelTuples.map((tuple, i) => {
- const channel = tuple[0] as Channel
- const id = asChannelId(ids[i])
- const plain = ChannelCodec.fromSubstrate(id, channel)
-
- return {
- ...plain,
- rewardEarned: new BN(0),
- contentItemsCount: 0,
- }
- })
- }
- async allChannels(): Promise<ChannelEntity[]> {
- const ids = await this.allChannelIds()
- return await this.channelCache.getOrLoadByIds(ids)
- }
- protected async getValidationConstraint(constraintName: string): Promise<ValidationConstraint> {
- const constraint = await this.cwgQuery()[constraintName]<InputValidationLengthConstraint>()
- return {
- min: constraint.min.toNumber(),
- max: constraint.max.toNumber()
- }
- }
- async channelValidationConstraints(): Promise<ChannelValidationConstraints> {
- const [
- handle,
- title,
- description,
- avatar,
- banner
- ] = await Promise.all([
- this.getValidationConstraint('channelHandleConstraint'),
- this.getValidationConstraint('channelTitleConstraint'),
- this.getValidationConstraint('channelDescriptionConstraint'),
- this.getValidationConstraint('channelAvatarConstraint'),
- this.getValidationConstraint('channelBannerConstraint')
- ])
- return {
- handle,
- title,
- description,
- avatar,
- banner
- }
- }
-
-
- async nextClassId(): Promise<ClassId> {
- return await this.vsQuery().nextClassId<ClassId>()
- }
- async allClassIds(): Promise<ClassId[]> {
- let nextId = (await this.nextClassId()).toNumber()
- const allIds: ClassId[] = []
- for (let id = FIRST_CLASS_ID; id < nextId; id++) {
- allIds.push(new ClassId(id))
- }
- return allIds
- }
- async loadClassesByIds(ids: AnyClassId[]): Promise<Class[]> {
- return await this.vsQuery().classById.multi<Vec<Class>>(ids) as unknown as Class[]
- }
- async allClasses(): Promise<Class[]> {
- const ids = await this.allClassIds()
- return await this.classCache.getOrLoadByIds(ids)
- }
- async getEntityCodecResolver(): Promise<EntityCodecResolver> {
- if (!this.entityCodecResolver) {
- const classes = await this.allClasses()
- this.entityCodecResolver = new EntityCodecResolver(classes)
- }
- return this.entityCodecResolver
- }
- async classNamesToIdSet(classNames: ClassName[]): Promise<Set<string>> {
- const classNameToIdMap = await this.classIdByNameMap()
- return new Set(classNames
- .map(name => {
- const classId = classNameToIdMap[name]
- return classId ? classId.toString() : undefined
- })
- .filter(classId => typeof classId !== 'undefined') as string[]
- )
- }
-
-
- async nextEntityId(): Promise<EntityId> {
- return await this.vsQuery().nextEntityId<EntityId>()
- }
- async allEntityIds(): Promise<EntityId[]> {
- let nextId = (await this.nextEntityId()).toNumber()
- const allIds: EntityId[] = []
- for (let id = FIRST_ENTITY_ID; id < nextId; id++) {
- allIds.push(new EntityId(id))
- }
- return allIds
- }
- private async loadEntitiesByIds(ids: AnyEntityId[]): Promise<Entity[]> {
- if (!ids || ids.length === 0) return []
- return await this.vsQuery().entityById.multi<Vec<Entity>>(ids) as unknown as Entity[]
- }
-
- private async loadPlainEntitiesByIds(ids: AnyEntityId[]): Promise<PlainEntity[]> {
- const entities = await this.loadEntitiesByIds(ids)
- const cacheClassIds = await this.classNamesToIdSet(ClassNamesThatCanBeCached)
- entities.forEach(e => {
- if (cacheClassIds.has(e.class_id.toString())) {
- this.idsOfEntitiesToKeepInCache.add(e.id.toString())
- }
- })
-
-
-
- return await this.toPlainEntitiesAndResolveInternals(entities)
- }
- async allPlainEntities(): Promise<PlainEntity[]> {
- const ids = await this.allEntityIds()
- return await this.entityCache.getOrLoadByIds(ids)
- }
- async findPlainEntitiesByClassName<T extends PlainEntity> (className: ClassName): Promise<T[]> {
- const res: T[] = []
- const clazz = await this.classByName(className)
- if (!clazz) {
- console.warn(`No class found by name '${className}'`)
- return res
- }
- const allIds = await this.allEntityIds()
- const filteredEntities = (await this.entityCache.getOrLoadByIds(allIds))
- .filter(entity => clazz.id.eq(entity.classId)) as T[]
- console.log(`Found ${filteredEntities.length} plain entities by class name '${className}'`)
-
- return filteredEntities
- }
- async toPlainEntitiesAndResolveInternals(entities: Entity[]): Promise<PlainEntity[]> {
-
- const loadEntityById = this.entityCache.getOrLoadById.bind(this.entityCache)
- const loadChannelById = this.channelCache.getOrLoadById.bind(this.channelCache)
- const entityCodecResolver = await this.getEntityCodecResolver()
- const loadableClassIds = await this.classNamesToIdSet(ClassNamesThatRequireLoadingInternals)
- const convertions: Promise<PlainEntity>[] = []
- for (const entity of entities) {
- const classIdStr = entity.class_id.toString()
- const codec = entityCodecResolver.getCodecByClassId(entity.class_id)
-
- if (!codec) {
- console.warn(`No entity codec found by class id: ${classIdStr}`)
- continue
- }
- const loadInternals = loadableClassIds.has(classIdStr)
- convertions.push(
- codec.toPlainObject(
- entity, {
- loadInternals,
- loadEntityById,
- loadChannelById
- }))
- }
- return Promise.all(convertions)
- }
-
-
- async featuredContent(): Promise<FeaturedContentType | undefined> {
- const arr = await this.findPlainEntitiesByClassName('FeaturedContent')
- return arr && arr.length ? arr[0] : undefined
- }
- async allMediaObjects(): Promise<MediaObjectType[]> {
- return await this.findPlainEntitiesByClassName('MediaObject')
- }
- async allVideos(): Promise<VideoType[]> {
- return await this.findPlainEntitiesByClassName('Video')
- }
- async allMusicTracks(): Promise<MusicTrackType[]> {
- return await this.findPlainEntitiesByClassName('MusicTrack')
- }
- async allMusicAlbums(): Promise<MusicAlbumType[]> {
- return await this.findPlainEntitiesByClassName('MusicAlbum')
- }
- async allContentLicenses (): Promise<ContentLicenseType[]> {
- return await this.findPlainEntitiesByClassName('ContentLicense')
- }
- async allCurationStatuses(): Promise<CurationStatusType[]> {
- return await this.findPlainEntitiesByClassName('CurationStatus')
- }
- async allLanguages(): Promise<LanguageType[]> {
- return await this.findPlainEntitiesByClassName('Language')
- }
- async allMusicGenres(): Promise<MusicGenreType[]> {
- return await this.findPlainEntitiesByClassName('MusicGenre')
- }
- async allMusicMoods(): Promise<MusicMoodType[]> {
- return await this.findPlainEntitiesByClassName('MusicMood')
- }
- async allMusicThemes(): Promise<MusicThemeType[]> {
- return await this.findPlainEntitiesByClassName('MusicTheme')
- }
- async allPublicationStatuses(): Promise<PublicationStatusType[]> {
- return await this.findPlainEntitiesByClassName('PublicationStatus')
- }
- async allVideoCategories(): Promise<VideoCategoryType[]> {
- return await this.findPlainEntitiesByClassName('VideoCategory')
- }
- }
|