Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve ipfs media download when file its in folder #7752

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion packages/shared/components/MediaDisplay.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</script>

<div class="h-full w-full object-cover">
{#if htmlTag === ParentMimeType.Image}
{#if htmlTag === ParentMimeType.Image || htmlTag === ParentMimeType.Text}
<img {src} {alt} loading="lazy" class="w-full h-full object-cover" />
{:else if htmlTag === ParentMimeType.Video}
<video
Expand Down
7 changes: 3 additions & 4 deletions packages/shared/components/NftImageOrIconBox.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script lang="ts">
import { INft } from '@core/nfts'
import { NftSize } from 'shared/components/enums'
import { INft, ParentMimeType } from '@core/nfts'
import { MediaPlaceholder, NftMedia } from 'shared/components'
import { ParentMimeType } from '@core/nfts'
import { NftSize } from 'shared/components/enums'

export let nft: INft | null = null
export let size: NftSize = NftSize.Medium
Expand All @@ -18,7 +17,7 @@
class:medium={size === NftSize.Medium}
class:large={size === NftSize.Large}
>
{#if parentType === ParentMimeType.Image && nft}
{#if (parentType === ParentMimeType.Image && nft) || (parentType === ParentMimeType.Text && nft)}
<NftMedia {nft} {useCaching}>
<placeholder-wrapper
slot="placeholder"
Expand Down
10 changes: 9 additions & 1 deletion packages/shared/lib/core/nfts/actions/downloadNextNftInQueue.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Platform } from '@core/app'
import { get } from 'svelte/store'
import { downloadingNftId, nftDownloadQueue, removeNftFromDownloadQueue } from '../stores'
import { getIpfsUri } from '../utils'

export async function downloadNextNftInQueue(): Promise<void> {
const nextDownload = get(nftDownloadQueue)?.[0]
Expand All @@ -9,8 +10,15 @@ export async function downloadNextNftInQueue(): Promise<void> {
}

try {
const { downloadUrl, path, nft, accountIndex } = nextDownload
// eslint-disable-next-line prefer-const
let { downloadUrl, path, nft, accountIndex } = nextDownload
downloadingNftId.set(nft.id)
const ipfsUri = await getIpfsUri({ hash: downloadUrl })

if (ipfsUri) {
downloadUrl = ipfsUri
}

await Platform.downloadNft(downloadUrl, path, nft.id, accountIndex)
} catch (error) {
downloadingNftId.set(undefined)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { allAccountNfts } from '../stores'
import { INft } from '../interfaces'
import { allAccountNfts } from '../stores'
import { getIpfsUri } from '../utils'

export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, partialNft: Partial<INft>): void {
allAccountNfts.update((state) => {
Expand All @@ -8,6 +9,15 @@ export function updateNftInAllAccountNfts(accountIndex: number, nftId: string, p
}
const nft = state[accountIndex].find((_nft) => _nft.id === nftId)
if (nft) {
const downloadUrl = nft.downloadUrl
if (downloadUrl) {
void getIpfsUri({ hash: downloadUrl }).then((ipfsUri) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking something along the lines like this check we have in explorer: https://github.com/iotaledger/explorer/blob/dev/client/src/helpers/hooks/useNftMetadataUri.ts#L22
Looks to me that we now try to getIpfsUri even if downloadUrl is not ipfs link.

if (ipfsUri) {
nft.downloadUrl = ipfsUri
nft.composedUrl = ipfsUri
}
})
}
Object.assign(nft, { ...nft, ...partialNft })
}
return state
Expand Down
149 changes: 149 additions & 0 deletions packages/shared/lib/core/nfts/utils/getIpfsUri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
interface IIpfsLink {
Hash: string
Name: string
Size: number
Target: string
Type: number
Mode?: string
Mtime?: number
MtimeNsecs?: number
}

interface IIPfsEntry {
readonly type: 'dir' | 'file'
readonly cid: string
readonly name: string
readonly path: string
mode?: number
mtime?: {
secs: number
nsecs?: number
}
size: number
}

interface IIpfsObject {
Hash: string
Links: IIpfsLink[]
}

enum typeOfLink {
Dir = 'dir',
File = 'file',
}

const IPFS_ENDPOINT = 'https://ipfs.io'
const IPFS_PATH = '/api/v0/ls'
const IPFS_PREFIX = '/ipfs/'

export async function getIpfsUri(link: { path?: string; hash: string }): Promise<string | undefined> {
try {
const slicedLink = link.hash.slice(IPFS_ENDPOINT.length)
const ipfsEntry = await ls(slicedLink)
if (ipfsEntry) {
if (ipfsEntry.type === 'dir') {
const path = `${link.path ?? ''}/${ipfsEntry.name}`
return await getIpfsUri({ hash: link.hash, path })
}
return `${link.hash}/${encodeURIComponent(ipfsEntry.name)}`
}
} catch (error) {
console.error('error', error)
}
}

async function ls(path: string): Promise<IIPfsEntry | undefined> {
let ipfsEntry: IIPfsEntry | undefined

try {
const baseUrl = IPFS_ENDPOINT
const method = 'get'
const payload = undefined
let headers = {}
const timeout = undefined

headers ??= {}

let controller: AbortController | undefined
let timerId: NodeJS.Timeout | undefined

if (timeout !== undefined) {
controller = new AbortController()
timerId = setTimeout(() => {
if (controller) {
controller.abort()
}
}, timeout)
}

try {
if (path.includes('ipfs')) {
const response = await fetch(`${baseUrl}${IPFS_PATH}?arg=/${path}`, {
method,
headers,
body: payload ? JSON.stringify(payload) : undefined,
signal: controller ? controller.signal : undefined,
})
const lsResponse = (await response.json()) as { Objects: IIpfsObject[] }
const result = lsResponse.Objects[0]
if (result) {
const links = result.Links
if (links.length > 0) {
ipfsEntry = mapLinkToIpfsEntry(links[0], path)
}
}
}
} catch (error) {
console.error('error', error)
} finally {
if (timerId) {
clearTimeout(timerId)
}
}
} catch (error) {
console.error('error', error)
}

return ipfsEntry
}

function mapLinkToIpfsEntry(link: IIpfsLink, path: string): IIPfsEntry {
const hash = link.Hash.startsWith(IPFS_PREFIX) ? link.Hash.slice(IPFS_PREFIX.length) : link.Hash
const entry: IIPfsEntry = {
name: link.Name,
path: path + (link.Name ? `/${link.Name}` : ''),
size: link.Size,
cid: hash,
type: typeOf(link),
}
if (link.Mode) {
entry.mode = Number.parseInt(link.Mode, 8)
}

if (link.Mtime !== undefined && link.Mtime !== null) {
entry.mtime = {
secs: link.Mtime,
}

if (link.MtimeNsecs !== undefined && link.MtimeNsecs !== null) {
entry.mtime.nsecs = link.MtimeNsecs
}
}

return entry
}

function typeOf(link: IIpfsLink): typeOfLink {
switch (link.Type) {
case 1:
case 5: {
return typeOfLink.Dir
}
case 2: {
return typeOfLink.File
}
default: {
return typeOfLink.File
}
}
}
5 changes: 3 additions & 2 deletions packages/shared/lib/core/nfts/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ export * from './buildNftFromNftOutput'
export * from './checkIfNftShouldBeDownloaded'
export * from './composeUrlFromNftUri'
export * from './convertAndFormatNftMetadata'
export * from './getSpendableStatusFromUnspentNftOutput'
export * from './fetchWithTimeout'
export * from './getIpfsUri'
export * from './getParentMimeType'
export * from './getSpendableStatusFromUnspentNftOutput'
export * from './isNftOwnedByAnyAccount'
export * from './parseNftMetadata'
export * from './rewriteIpfsUri'
export * from './getParentMimeType'
Loading