Skip to content

Commit

Permalink
fix: Update models file (#3158)
Browse files Browse the repository at this point in the history
* feat: Add utils to validate when a model size is valid

* fix: Validates the model size including the thumbnail size

* fix: Filesize comparation logic

* chore: Update @dcl/builder-client to version 6.0.1

* fix: Calculate final model size for wearables correctly

The code changes in this commit fix a bug in the calculation of the final model size for wearables. Previously, the old files were unnecessarily downloaded for emotes, which was causing incorrect size calculations. This commit updates the logic to only download the old files for wearables, excluding smart wearables and emotes.

* fix: Update max file size for thumbnails, wearables, and emotes
  • Loading branch information
cyaiox authored Aug 22, 2024
1 parent 72a57ce commit 5774f59
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 48 deletions.
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"dependencies": {
"@babylonjs/core": "^4.2.0",
"@babylonjs/loaders": "^4.2.0",
"@dcl/builder-client": "^6.0.0",
"@dcl/builder-client": "^6.0.1",
"@dcl/builder-templates": "^0.2.0",
"@dcl/content-hash-tree": "^1.1.3",
"@dcl/crypto": "^3.4.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ import {
getBodyShapeTypeFromContents,
isSmart,
isWearable,
buildItemMappings
buildItemMappings,
isEmoteFileSizeValid,
isSkinFileSizeValid,
isSmartWearableFileSizeValid,
isWearableFileSizeValid
} from 'modules/item/utils'
import { EngineType, getItemData, getModelData } from 'lib/getModelData'
import { getExtension, toMB } from 'lib/file'
Expand Down Expand Up @@ -817,6 +821,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
const modelSize = await calculateModelFinalSize(
item?.contents ?? {},
contents ?? {},
type ?? ItemType.WEARABLE,
new BuilderAPI(BUILDER_SERVER_URL, new Authorization(() => this.props.address))
)

Expand Down Expand Up @@ -1050,12 +1055,13 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
const isEmote = type === ItemType.EMOTE
const isSmartWearable = isSmart({ type, contents: this.state.contents })
const isRequirementMet = required.every(prop => prop !== undefined)
const finalSize = modelSize ? modelSize + thumbnailSize : undefined

if (isThirdPartyV2Enabled && ((!mappings && linkedContract) || (mappings && !areMappingsValid(mappings)))) {
return false
}

if (isRequirementMet && isEmote && modelSize && modelSize > MAX_EMOTE_FILE_SIZE) {
if (isRequirementMet && isEmote && finalSize && !isEmoteFileSizeValid(finalSize)) {
this.setState({
error: t('create_single_item_modal.error.item_too_big', {
size: `${toMB(MAX_EMOTE_FILE_SIZE)}MB`,
Expand All @@ -1065,7 +1071,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
return false
}

if (isRequirementMet && isSkin && modelSize && modelSize > MAX_SKIN_FILE_SIZE) {
if (isRequirementMet && isSkin && finalSize && !isSkinFileSizeValid(finalSize)) {
this.setState({
error: t('create_single_item_modal.error.item_too_big', {
size: `${toMB(MAX_SKIN_FILE_SIZE)}MB`,
Expand All @@ -1075,7 +1081,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
return false
}

if (isRequirementMet && !isSkin && isSmartWearable && modelSize && modelSize > MAX_SMART_WEARABLE_FILE_SIZE) {
if (isRequirementMet && !isSkin && isSmartWearable && finalSize && !isSmartWearableFileSizeValid(finalSize)) {
this.setState({
error: t('create_single_item_modal.error.item_too_big', {
size: `${toMB(MAX_SMART_WEARABLE_FILE_SIZE)}MB`,
Expand All @@ -1085,7 +1091,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
return false
}

if (isRequirementMet && !isSkin && !isSmartWearable && modelSize && modelSize > MAX_WEARABLE_FILE_SIZE) {
if (isRequirementMet && !isSkin && !isSmartWearable && finalSize && !isWearableFileSizeValid(finalSize)) {
this.setState({
error: t('create_single_item_modal.error.item_too_big', {
size: `${toMB(MAX_WEARABLE_FILE_SIZE)}MB`,
Expand Down
6 changes: 5 additions & 1 deletion src/modules/item/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,18 @@ function getUniqueFiles(hashes: Record<string, string>, blobs: Record<string, Bl
export async function calculateModelFinalSize(
oldContents: Record<string, string>,
newContents: Record<string, Blob>,
itemType: ItemType,
legacyBuilderClient: BuilderAPI
): Promise<number> {
const newHashes = await computeHashes(newContents)
const newContentHasModel = Object.keys(newContents).some(key => key.endsWith('.glb'))
const filesToDownload: Record<string, string> = {}
// If the file to calculate the size is a smart wearable, we don't need to download the old files
const isSmartWearable = Object.keys(newContents).some(path => path.endsWith('.js'))
if (!isSmartWearable) {
// Download old files to calculate the final model size for wearables.
// Note: For Smart Wearables and Emotes, the entire content will be overwritten,
// so downloading the old files is unnecessary.
if (itemType === ItemType.WEARABLE && !isSmartWearable) {
for (const fileName in oldContents) {
const isModel = fileName.endsWith('.glb')
const isVideo = fileName.endsWith('.mp4') // do not take into account the video size for the smart wearables
Expand Down
22 changes: 11 additions & 11 deletions src/modules/item/sagas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe('when handling the save item request action', () => {
})
})

describe('and file size of the wearable is larger than 2 MB', () => {
describe('and file size of the wearable is larger than 3 MB', () => {
beforeEach(() => {
item.name = 'valid name'
item.description = 'valid description'
Expand All @@ -187,7 +187,7 @@ describe('when handling the save item request action', () => {
[matchers.call.fn(calculateModelFinalSize), Promise.resolve(MAX_WEARABLE_FILE_SIZE + 1)],
[matchers.call.fn(calculateFileSize), MAX_THUMBNAIL_FILE_SIZE]
])
.put(saveItemFailure(item, contents, 'The max file size is 2MB for Wearables and 8MB for Skins.'))
.put(saveItemFailure(item, contents, 'The max file size is 3MB for Wearables and 8MB for Skins.'))
.dispatch(saveItemRequest(item, contents))
.run({ silenceTimeout: true })
})
Expand All @@ -211,13 +211,13 @@ describe('when handling the save item request action', () => {
[matchers.call.fn(calculateModelFinalSize), Promise.resolve(MAX_SKIN_FILE_SIZE + 1)],
[matchers.call.fn(calculateFileSize), MAX_THUMBNAIL_FILE_SIZE]
])
.put(saveItemFailure(item, contents, 'The max file size is 2MB for Wearables and 8MB for Skins.'))
.put(saveItemFailure(item, contents, 'The max file size is 3MB for Wearables and 8MB for Skins.'))
.dispatch(saveItemRequest(item, contents))
.run({ silenceTimeout: true })
})
})

describe('and file size of the emote is larger than 2 MB', () => {
describe('and file size of the emote is larger than 3 MB', () => {
beforeEach(() => {
item = { ...item, type: ItemType.EMOTE }
})
Expand All @@ -232,7 +232,7 @@ describe('when handling the save item request action', () => {
[matchers.call.fn(calculateModelFinalSize), Promise.resolve(MAX_EMOTE_FILE_SIZE + 1)],
[matchers.call.fn(calculateFileSize), MAX_THUMBNAIL_FILE_SIZE]
])
.put(saveItemFailure(item, contents, 'The item is too large to be uploaded. The maximum file size for emote is 2MB.'))
.put(saveItemFailure(item, contents, 'The item is too large to be uploaded. The maximum file size for emote is 3MB.'))
.dispatch(saveItemRequest(item, contents))
.run({ silenceTimeout: true })
})
Expand Down Expand Up @@ -379,7 +379,7 @@ describe('when handling the save item request action', () => {
}),
Promise.resolve({ hash: catalystImageHash, content: blob })
],
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
[call(calculateFileSize, thumbnailContent), 1],
[call([builderAPI, 'saveItem'], itemWithCatalystImage, contentsToSave), Promise.resolve(itemWithCatalystImage)],
[put(saveItemSuccess(itemWithCatalystImage, contentsToSave)), undefined]
Expand All @@ -406,7 +406,7 @@ describe('when handling the save item request action', () => {
[select(getItem, item.id), undefined],
[select(getAddress), mockAddress],
[select(getIsLinkedWearablesV2Enabled), true],
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
[call(calculateFileSize, thumbnailContent), 1],
[call([builderAPI, 'saveItem'], item, contents), Promise.resolve(item)],
[put(saveItemSuccess(item, contents)), undefined]
Expand Down Expand Up @@ -447,7 +447,7 @@ describe('when handling the save item request action', () => {
}),
Promise.resolve({ hash: catalystImageHash, content: blob })
],
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
[call(calculateFileSize, thumbnailContent), 1],
[
call([builderAPI, 'saveItem'], itemWithCatalystImage, newContentsContainingNewCatalystImage),
Expand Down Expand Up @@ -477,7 +477,7 @@ describe('when handling the save item request action', () => {
[select(getItem, item.id), undefined],
[select(getAddress), mockAddress],
[select(getIsLinkedWearablesV2Enabled), true],
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
[call(calculateFileSize, thumbnailContent), 1],
[call([builderAPI, 'saveItem'], item, contents), Promise.resolve(item)],
[put(saveItemSuccess(item, contents)), undefined]
Expand All @@ -504,7 +504,7 @@ describe('when handling the save item request action', () => {
[select(getItem, item.id), undefined],
[select(getAddress), mockAddress],
[select(getIsLinkedWearablesV2Enabled), true],
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
[call(calculateFileSize, thumbnailContent), 1],
[call([builderAPI, 'saveItem'], item, contents), Promise.resolve(item)],
[put(saveItemSuccess(item, contents)), undefined]
Expand Down Expand Up @@ -564,7 +564,7 @@ describe('when handling the save item request action', () => {
[select(getItem, item.id), item],
[select(getAddress), mockAddress],
[select(getIsLinkedWearablesV2Enabled), true],
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
[call(calculateFileSize, thumbnailContent), 1],
[call([builderAPI, 'saveItem'], itemWithNewHashes, newContents), Promise.resolve(itemWithNewHashes)],
[put(saveItemSuccess(itemWithNewHashes, newContents)), undefined]
Expand Down
29 changes: 14 additions & 15 deletions src/modules/item/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,7 @@ import { Toast } from 'decentraland-dapps/dist/modules/toast/types'
import { RENDER_TOAST, hideToast, showToast, RenderToastAction } from 'decentraland-dapps/dist/modules/toast/actions'
import { ToastType } from 'decentraland-ui'
import { getChainIdByNetwork, getNetworkProvider } from 'decentraland-dapps/dist/lib/eth'
import {
BuilderClient,
RemoteItem,
MAX_THUMBNAIL_FILE_SIZE,
MAX_WEARABLE_FILE_SIZE,
MAX_SKIN_FILE_SIZE,
MAX_EMOTE_FILE_SIZE,
MAX_SMART_WEARABLE_FILE_SIZE
} from '@dcl/builder-client'
import { BuilderClient, RemoteItem, MAX_THUMBNAIL_FILE_SIZE } from '@dcl/builder-client'
import {
FetchItemsRequestAction,
fetchItemsSuccess,
Expand Down Expand Up @@ -151,7 +143,11 @@ import {
MAX_VIDEO_FILE_SIZE,
isSmart,
isWearable,
isEmote
isEmote,
isWearableFileSizeValid,
isEmoteFileSizeValid,
isSkinFileSizeValid,
isSmartWearableFileSizeValid
} from './utils'
import { ItemPaginationData } from './reducer'
import { getSuccessfulDeletedItemToast, getSuccessfulMoveItemToAnotherCollectionToast } from './toasts'
Expand Down Expand Up @@ -407,7 +403,7 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
const { [THUMBNAIL_PATH]: thumbnailContent, [VIDEO_PATH]: videoContent, ...modelContents } = contents
const { [THUMBNAIL_PATH]: _thumbnailContent, [VIDEO_PATH]: _videoContent, ...itemContents } = item.contents
// This will calculate the model's final size without the thumbnail with a limit of 2MB for wearables/emotes and 8MB for skins
const finalModelSize: number = yield call(calculateModelFinalSize, itemContents, modelContents, legacyBuilder)
const finalModelSize: number = yield call(calculateModelFinalSize, itemContents, modelContents, item.type, legacyBuilder)
let finalThumbnailSize = 0
let finalVideoSize = 0

Expand All @@ -431,19 +427,22 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
const isSkin = !isEmoteItem && item.data.category === WearableCategory.SKIN
const isSmartWearable = isSmart(item)

if (isEmoteItem && finalModelSize > MAX_EMOTE_FILE_SIZE) {
// Use the model size + thumbnail size until there's a clear definition of how the catalyst will handle the thumbnail size calculation
const finalSize = finalModelSize + finalThumbnailSize

if (isEmoteItem && !isEmoteFileSizeValid(finalSize)) {
throw new ItemEmoteTooBigError()
}

if (isSkin && finalModelSize > MAX_SKIN_FILE_SIZE) {
if (isSkin && !isSkinFileSizeValid(finalSize)) {
throw new ItemSkinTooBigError()
}

if (isSmartWearable && finalModelSize + finalThumbnailSize > MAX_SMART_WEARABLE_FILE_SIZE) {
if (isSmartWearable && !isSmartWearableFileSizeValid(finalSize)) {
throw new ItemSmartWearableTooBigError()
}

if (!isSkin && !isSmartWearable && !isEmoteItem && finalModelSize > MAX_WEARABLE_FILE_SIZE) {
if (!isSkin && !isSmartWearable && !isEmoteItem && !isWearableFileSizeValid(finalSize)) {
throw new ItemWearableTooBigError()
}
}
Expand Down
24 changes: 23 additions & 1 deletion src/modules/item/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { constants } from 'ethers'
import { LocalItem } from '@dcl/builder-client'
import {
LocalItem,
MAX_EMOTE_FILE_SIZE,
MAX_SKIN_FILE_SIZE,
MAX_SMART_WEARABLE_FILE_SIZE,
MAX_WEARABLE_FILE_SIZE
} from '@dcl/builder-client'
import {
BodyPartCategory,
BodyShape,
Expand Down Expand Up @@ -756,3 +762,19 @@ export const buildItemMappings = (mapping: Mapping, contract: LinkedContract): M
}
}
}

export const isWearableFileSizeValid = (fileSize: number): boolean => {
return fileSize < MAX_WEARABLE_FILE_SIZE
}

export const isEmoteFileSizeValid = (fileSize: number): boolean => {
return fileSize < MAX_EMOTE_FILE_SIZE
}

export const isSkinFileSizeValid = (fileSize: number): boolean => {
return fileSize < MAX_SKIN_FILE_SIZE
}

export const isSmartWearableFileSizeValid = (fileSize: number): boolean => {
return fileSize < MAX_SMART_WEARABLE_FILE_SIZE
}
6 changes: 3 additions & 3 deletions src/modules/translation/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,15 +328,15 @@
"wrong_video_format": "The provided video is not a MP4 video",
"thumbnail_too_big": {
"title": "The file is too large and can't be uploaded",
"message": "The max file size is 2MB for thumbnails."
"message": "The max file size is 1MB for thumbnails."
},
"wearable_too_big": {
"title": "The file is too large and can't be uploaded",
"message": "The max file size is 2MB for Wearables and 8MB for Skins."
"message": "The max file size is 3MB for Wearables and 8MB for Skins."
},
"emote_too_big": {
"title": "The file is too large and can't be uploaded",
"message": "The max file size is 2MB for Emotes."
"message": "The max file size is 3MB for Emotes."
},
"smart_wearable_too_big": {
"title": "The file is too large and can't be uploaded",
Expand Down
8 changes: 4 additions & 4 deletions src/modules/translation/languages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,19 +327,19 @@
"wrong_video_format": "El video provisto no es un video MP4",
"thumbnail_too_big": {
"title": "El item es demasiado grande para cargarlo",
"message": "El tamaño máximo de archivo es de 2 MB para miniaturas."
"message": "El tamaño máximo de archivo es de 1MB para miniaturas."
},
"wearable_too_big": {
"title": "El item es demasiado grande para cargarlo",
"message": "El tamaño máximo de archivo es de 2 MB para Wearables y de 8 MB para Skins."
"message": "El tamaño máximo de archivo es de 3MB para Wearables y de 8MB para Skins."
},
"emote_too_big": {
"title": "El item es demasiado grande para cargarlo",
"message": "El tamaño máximo de archivo es de 2MB para Emotes."
"message": "El tamaño máximo de archivo es de 3MB para Emotes."
},
"smart_wearable_too_big": {
"title": "El archivo es demasiado grande para cargarlo",
"message": "La suma total del tamaño de los archivos descomprimidos del archivo .zip de Smart Wearable debe ser inferior a 3 MB."
"message": "La suma total del tamaño de los archivos descomprimidos del archivo .zip de Smart Wearable debe ser inferior a 3MB."
},
"unknown_required_permissions": {
"title": "{count, plural, one {El permiso} other {Los permisos}} {wrong_configurations} de la vestimenta interactiva no {count, plural, one {existe} other {existen}}. Por favor, corrija el archivo 'scene.json'.",
Expand Down
6 changes: 3 additions & 3 deletions src/modules/translation/languages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -321,15 +321,15 @@
"wrong_video_format": "提供的视频不是MP4视频",
"thumbnail_too_big": {
"title": "文件太大,无法上传",
"message": "缩略图的最大文件大小为 2MB"
"message": "缩略图的最大文件大小为 1MB"
},
"wearable_too_big": {
"title": "文件太大,无法上传",
"message": "可穿戴设备的最大文件大小为 2MB,皮肤的最大文件大小为 8MB。"
"message": "可穿戴设备的最大文件大小为 3MB,皮肤的最大文件大小为 8MB。"
},
"emote_too_big": {
"title": "文件太大,无法上传",
"message": "表情的最大文件大小为 2MB"
"message": "表情的最大文件大小为 3MB"
},
"smart_wearable_too_big": {
"title": "文件太大,无法上传",
Expand Down

0 comments on commit 5774f59

Please sign in to comment.