Skip to content

Commit 5774f59

Browse files
authored
fix: Update models file (#3158)
* 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
1 parent 72a57ce commit 5774f59

File tree

10 files changed

+80
-48
lines changed

10 files changed

+80
-48
lines changed

package-lock.json

+5-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"dependencies": {
66
"@babylonjs/core": "^4.2.0",
77
"@babylonjs/loaders": "^4.2.0",
8-
"@dcl/builder-client": "^6.0.0",
8+
"@dcl/builder-client": "^6.0.1",
99
"@dcl/builder-templates": "^0.2.0",
1010
"@dcl/content-hash-tree": "^1.1.3",
1111
"@dcl/crypto": "^3.4.5",

src/components/Modals/CreateSingleItemModal/CreateSingleItemModal.tsx

+11-5
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ import {
6767
getBodyShapeTypeFromContents,
6868
isSmart,
6969
isWearable,
70-
buildItemMappings
70+
buildItemMappings,
71+
isEmoteFileSizeValid,
72+
isSkinFileSizeValid,
73+
isSmartWearableFileSizeValid,
74+
isWearableFileSizeValid
7175
} from 'modules/item/utils'
7276
import { EngineType, getItemData, getModelData } from 'lib/getModelData'
7377
import { getExtension, toMB } from 'lib/file'
@@ -817,6 +821,7 @@ export default class CreateSingleItemModal extends React.PureComponent<Props, St
817821
const modelSize = await calculateModelFinalSize(
818822
item?.contents ?? {},
819823
contents ?? {},
824+
type ?? ItemType.WEARABLE,
820825
new BuilderAPI(BUILDER_SERVER_URL, new Authorization(() => this.props.address))
821826
)
822827

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

10541060
if (isThirdPartyV2Enabled && ((!mappings && linkedContract) || (mappings && !areMappingsValid(mappings)))) {
10551061
return false
10561062
}
10571063

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

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

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

1088-
if (isRequirementMet && !isSkin && !isSmartWearable && modelSize && modelSize > MAX_WEARABLE_FILE_SIZE) {
1094+
if (isRequirementMet && !isSkin && !isSmartWearable && finalSize && !isWearableFileSizeValid(finalSize)) {
10891095
this.setState({
10901096
error: t('create_single_item_modal.error.item_too_big', {
10911097
size: `${toMB(MAX_WEARABLE_FILE_SIZE)}MB`,

src/modules/item/export.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,18 @@ function getUniqueFiles(hashes: Record<string, string>, blobs: Record<string, Bl
7373
export async function calculateModelFinalSize(
7474
oldContents: Record<string, string>,
7575
newContents: Record<string, Blob>,
76+
itemType: ItemType,
7677
legacyBuilderClient: BuilderAPI
7778
): Promise<number> {
7879
const newHashes = await computeHashes(newContents)
7980
const newContentHasModel = Object.keys(newContents).some(key => key.endsWith('.glb'))
8081
const filesToDownload: Record<string, string> = {}
8182
// If the file to calculate the size is a smart wearable, we don't need to download the old files
8283
const isSmartWearable = Object.keys(newContents).some(path => path.endsWith('.js'))
83-
if (!isSmartWearable) {
84+
// Download old files to calculate the final model size for wearables.
85+
// Note: For Smart Wearables and Emotes, the entire content will be overwritten,
86+
// so downloading the old files is unnecessary.
87+
if (itemType === ItemType.WEARABLE && !isSmartWearable) {
8488
for (const fileName in oldContents) {
8589
const isModel = fileName.endsWith('.glb')
8690
const isVideo = fileName.endsWith('.mp4') // do not take into account the video size for the smart wearables

src/modules/item/sagas.spec.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ describe('when handling the save item request action', () => {
170170
})
171171
})
172172

173-
describe('and file size of the wearable is larger than 2 MB', () => {
173+
describe('and file size of the wearable is larger than 3 MB', () => {
174174
beforeEach(() => {
175175
item.name = 'valid name'
176176
item.description = 'valid description'
@@ -187,7 +187,7 @@ describe('when handling the save item request action', () => {
187187
[matchers.call.fn(calculateModelFinalSize), Promise.resolve(MAX_WEARABLE_FILE_SIZE + 1)],
188188
[matchers.call.fn(calculateFileSize), MAX_THUMBNAIL_FILE_SIZE]
189189
])
190-
.put(saveItemFailure(item, contents, 'The max file size is 2MB for Wearables and 8MB for Skins.'))
190+
.put(saveItemFailure(item, contents, 'The max file size is 3MB for Wearables and 8MB for Skins.'))
191191
.dispatch(saveItemRequest(item, contents))
192192
.run({ silenceTimeout: true })
193193
})
@@ -211,13 +211,13 @@ describe('when handling the save item request action', () => {
211211
[matchers.call.fn(calculateModelFinalSize), Promise.resolve(MAX_SKIN_FILE_SIZE + 1)],
212212
[matchers.call.fn(calculateFileSize), MAX_THUMBNAIL_FILE_SIZE]
213213
])
214-
.put(saveItemFailure(item, contents, 'The max file size is 2MB for Wearables and 8MB for Skins.'))
214+
.put(saveItemFailure(item, contents, 'The max file size is 3MB for Wearables and 8MB for Skins.'))
215215
.dispatch(saveItemRequest(item, contents))
216216
.run({ silenceTimeout: true })
217217
})
218218
})
219219

220-
describe('and file size of the emote is larger than 2 MB', () => {
220+
describe('and file size of the emote is larger than 3 MB', () => {
221221
beforeEach(() => {
222222
item = { ...item, type: ItemType.EMOTE }
223223
})
@@ -232,7 +232,7 @@ describe('when handling the save item request action', () => {
232232
[matchers.call.fn(calculateModelFinalSize), Promise.resolve(MAX_EMOTE_FILE_SIZE + 1)],
233233
[matchers.call.fn(calculateFileSize), MAX_THUMBNAIL_FILE_SIZE]
234234
])
235-
.put(saveItemFailure(item, contents, 'The item is too large to be uploaded. The maximum file size for emote is 2MB.'))
235+
.put(saveItemFailure(item, contents, 'The item is too large to be uploaded. The maximum file size for emote is 3MB.'))
236236
.dispatch(saveItemRequest(item, contents))
237237
.run({ silenceTimeout: true })
238238
})
@@ -379,7 +379,7 @@ describe('when handling the save item request action', () => {
379379
}),
380380
Promise.resolve({ hash: catalystImageHash, content: blob })
381381
],
382-
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
382+
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
383383
[call(calculateFileSize, thumbnailContent), 1],
384384
[call([builderAPI, 'saveItem'], itemWithCatalystImage, contentsToSave), Promise.resolve(itemWithCatalystImage)],
385385
[put(saveItemSuccess(itemWithCatalystImage, contentsToSave)), undefined]
@@ -406,7 +406,7 @@ describe('when handling the save item request action', () => {
406406
[select(getItem, item.id), undefined],
407407
[select(getAddress), mockAddress],
408408
[select(getIsLinkedWearablesV2Enabled), true],
409-
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
409+
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
410410
[call(calculateFileSize, thumbnailContent), 1],
411411
[call([builderAPI, 'saveItem'], item, contents), Promise.resolve(item)],
412412
[put(saveItemSuccess(item, contents)), undefined]
@@ -447,7 +447,7 @@ describe('when handling the save item request action', () => {
447447
}),
448448
Promise.resolve({ hash: catalystImageHash, content: blob })
449449
],
450-
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
450+
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
451451
[call(calculateFileSize, thumbnailContent), 1],
452452
[
453453
call([builderAPI, 'saveItem'], itemWithCatalystImage, newContentsContainingNewCatalystImage),
@@ -477,7 +477,7 @@ describe('when handling the save item request action', () => {
477477
[select(getItem, item.id), undefined],
478478
[select(getAddress), mockAddress],
479479
[select(getIsLinkedWearablesV2Enabled), true],
480-
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
480+
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
481481
[call(calculateFileSize, thumbnailContent), 1],
482482
[call([builderAPI, 'saveItem'], item, contents), Promise.resolve(item)],
483483
[put(saveItemSuccess(item, contents)), undefined]
@@ -504,7 +504,7 @@ describe('when handling the save item request action', () => {
504504
[select(getItem, item.id), undefined],
505505
[select(getAddress), mockAddress],
506506
[select(getIsLinkedWearablesV2Enabled), true],
507-
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
507+
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
508508
[call(calculateFileSize, thumbnailContent), 1],
509509
[call([builderAPI, 'saveItem'], item, contents), Promise.resolve(item)],
510510
[put(saveItemSuccess(item, contents)), undefined]
@@ -564,7 +564,7 @@ describe('when handling the save item request action', () => {
564564
[select(getItem, item.id), item],
565565
[select(getAddress), mockAddress],
566566
[select(getIsLinkedWearablesV2Enabled), true],
567-
[call(calculateModelFinalSize, itemContents, modelContents, builderAPI), Promise.resolve(1)],
567+
[call(calculateModelFinalSize, itemContents, modelContents, item.type, builderAPI), Promise.resolve(1)],
568568
[call(calculateFileSize, thumbnailContent), 1],
569569
[call([builderAPI, 'saveItem'], itemWithNewHashes, newContents), Promise.resolve(itemWithNewHashes)],
570570
[put(saveItemSuccess(itemWithNewHashes, newContents)), undefined]

src/modules/item/sagas.ts

+14-15
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,7 @@ import { Toast } from 'decentraland-dapps/dist/modules/toast/types'
1717
import { RENDER_TOAST, hideToast, showToast, RenderToastAction } from 'decentraland-dapps/dist/modules/toast/actions'
1818
import { ToastType } from 'decentraland-ui'
1919
import { getChainIdByNetwork, getNetworkProvider } from 'decentraland-dapps/dist/lib/eth'
20-
import {
21-
BuilderClient,
22-
RemoteItem,
23-
MAX_THUMBNAIL_FILE_SIZE,
24-
MAX_WEARABLE_FILE_SIZE,
25-
MAX_SKIN_FILE_SIZE,
26-
MAX_EMOTE_FILE_SIZE,
27-
MAX_SMART_WEARABLE_FILE_SIZE
28-
} from '@dcl/builder-client'
20+
import { BuilderClient, RemoteItem, MAX_THUMBNAIL_FILE_SIZE } from '@dcl/builder-client'
2921
import {
3022
FetchItemsRequestAction,
3123
fetchItemsSuccess,
@@ -151,7 +143,11 @@ import {
151143
MAX_VIDEO_FILE_SIZE,
152144
isSmart,
153145
isWearable,
154-
isEmote
146+
isEmote,
147+
isWearableFileSizeValid,
148+
isEmoteFileSizeValid,
149+
isSkinFileSizeValid,
150+
isSmartWearableFileSizeValid
155151
} from './utils'
156152
import { ItemPaginationData } from './reducer'
157153
import { getSuccessfulDeletedItemToast, getSuccessfulMoveItemToAnotherCollectionToast } from './toasts'
@@ -407,7 +403,7 @@ export function* itemSaga(legacyBuilder: LegacyBuilderAPI, builder: BuilderClien
407403
const { [THUMBNAIL_PATH]: thumbnailContent, [VIDEO_PATH]: videoContent, ...modelContents } = contents
408404
const { [THUMBNAIL_PATH]: _thumbnailContent, [VIDEO_PATH]: _videoContent, ...itemContents } = item.contents
409405
// This will calculate the model's final size without the thumbnail with a limit of 2MB for wearables/emotes and 8MB for skins
410-
const finalModelSize: number = yield call(calculateModelFinalSize, itemContents, modelContents, legacyBuilder)
406+
const finalModelSize: number = yield call(calculateModelFinalSize, itemContents, modelContents, item.type, legacyBuilder)
411407
let finalThumbnailSize = 0
412408
let finalVideoSize = 0
413409

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

434-
if (isEmoteItem && finalModelSize > MAX_EMOTE_FILE_SIZE) {
430+
// Use the model size + thumbnail size until there's a clear definition of how the catalyst will handle the thumbnail size calculation
431+
const finalSize = finalModelSize + finalThumbnailSize
432+
433+
if (isEmoteItem && !isEmoteFileSizeValid(finalSize)) {
435434
throw new ItemEmoteTooBigError()
436435
}
437436

438-
if (isSkin && finalModelSize > MAX_SKIN_FILE_SIZE) {
437+
if (isSkin && !isSkinFileSizeValid(finalSize)) {
439438
throw new ItemSkinTooBigError()
440439
}
441440

442-
if (isSmartWearable && finalModelSize + finalThumbnailSize > MAX_SMART_WEARABLE_FILE_SIZE) {
441+
if (isSmartWearable && !isSmartWearableFileSizeValid(finalSize)) {
443442
throw new ItemSmartWearableTooBigError()
444443
}
445444

446-
if (!isSkin && !isSmartWearable && !isEmoteItem && finalModelSize > MAX_WEARABLE_FILE_SIZE) {
445+
if (!isSkin && !isSmartWearable && !isEmoteItem && !isWearableFileSizeValid(finalSize)) {
447446
throw new ItemWearableTooBigError()
448447
}
449448
}

src/modules/item/utils.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { constants } from 'ethers'
2-
import { LocalItem } from '@dcl/builder-client'
2+
import {
3+
LocalItem,
4+
MAX_EMOTE_FILE_SIZE,
5+
MAX_SKIN_FILE_SIZE,
6+
MAX_SMART_WEARABLE_FILE_SIZE,
7+
MAX_WEARABLE_FILE_SIZE
8+
} from '@dcl/builder-client'
39
import {
410
BodyPartCategory,
511
BodyShape,
@@ -756,3 +762,19 @@ export const buildItemMappings = (mapping: Mapping, contract: LinkedContract): M
756762
}
757763
}
758764
}
765+
766+
export const isWearableFileSizeValid = (fileSize: number): boolean => {
767+
return fileSize < MAX_WEARABLE_FILE_SIZE
768+
}
769+
770+
export const isEmoteFileSizeValid = (fileSize: number): boolean => {
771+
return fileSize < MAX_EMOTE_FILE_SIZE
772+
}
773+
774+
export const isSkinFileSizeValid = (fileSize: number): boolean => {
775+
return fileSize < MAX_SKIN_FILE_SIZE
776+
}
777+
778+
export const isSmartWearableFileSizeValid = (fileSize: number): boolean => {
779+
return fileSize < MAX_SMART_WEARABLE_FILE_SIZE
780+
}

src/modules/translation/languages/en.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -328,15 +328,15 @@
328328
"wrong_video_format": "The provided video is not a MP4 video",
329329
"thumbnail_too_big": {
330330
"title": "The file is too large and can't be uploaded",
331-
"message": "The max file size is 2MB for thumbnails."
331+
"message": "The max file size is 1MB for thumbnails."
332332
},
333333
"wearable_too_big": {
334334
"title": "The file is too large and can't be uploaded",
335-
"message": "The max file size is 2MB for Wearables and 8MB for Skins."
335+
"message": "The max file size is 3MB for Wearables and 8MB for Skins."
336336
},
337337
"emote_too_big": {
338338
"title": "The file is too large and can't be uploaded",
339-
"message": "The max file size is 2MB for Emotes."
339+
"message": "The max file size is 3MB for Emotes."
340340
},
341341
"smart_wearable_too_big": {
342342
"title": "The file is too large and can't be uploaded",

src/modules/translation/languages/es.json

+4-4
Original file line numberDiff line numberDiff line change
@@ -327,19 +327,19 @@
327327
"wrong_video_format": "El video provisto no es un video MP4",
328328
"thumbnail_too_big": {
329329
"title": "El item es demasiado grande para cargarlo",
330-
"message": "El tamaño máximo de archivo es de 2 MB para miniaturas."
330+
"message": "El tamaño máximo de archivo es de 1MB para miniaturas."
331331
},
332332
"wearable_too_big": {
333333
"title": "El item es demasiado grande para cargarlo",
334-
"message": "El tamaño máximo de archivo es de 2 MB para Wearables y de 8 MB para Skins."
334+
"message": "El tamaño máximo de archivo es de 3MB para Wearables y de 8MB para Skins."
335335
},
336336
"emote_too_big": {
337337
"title": "El item es demasiado grande para cargarlo",
338-
"message": "El tamaño máximo de archivo es de 2MB para Emotes."
338+
"message": "El tamaño máximo de archivo es de 3MB para Emotes."
339339
},
340340
"smart_wearable_too_big": {
341341
"title": "El archivo es demasiado grande para cargarlo",
342-
"message": "La suma total del tamaño de los archivos descomprimidos del archivo .zip de Smart Wearable debe ser inferior a 3 MB."
342+
"message": "La suma total del tamaño de los archivos descomprimidos del archivo .zip de Smart Wearable debe ser inferior a 3MB."
343343
},
344344
"unknown_required_permissions": {
345345
"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'.",

src/modules/translation/languages/zh.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -321,15 +321,15 @@
321321
"wrong_video_format": "提供的视频不是MP4视频",
322322
"thumbnail_too_big": {
323323
"title": "文件太大,无法上传",
324-
"message": "缩略图的最大文件大小为 2MB"
324+
"message": "缩略图的最大文件大小为 1MB"
325325
},
326326
"wearable_too_big": {
327327
"title": "文件太大,无法上传",
328-
"message": "可穿戴设备的最大文件大小为 2MB,皮肤的最大文件大小为 8MB。"
328+
"message": "可穿戴设备的最大文件大小为 3MB,皮肤的最大文件大小为 8MB。"
329329
},
330330
"emote_too_big": {
331331
"title": "文件太大,无法上传",
332-
"message": "表情的最大文件大小为 2MB"
332+
"message": "表情的最大文件大小为 3MB"
333333
},
334334
"smart_wearable_too_big": {
335335
"title": "文件太大,无法上传",

0 commit comments

Comments
 (0)