From 273079d09f9a331cca6bbd2ae6980f01099ac877 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sat, 1 May 2021 22:49:32 +0400 Subject: [PATCH] Use cache context for photos too Cache thumbs --- src/components/appMediaPlaybackController.ts | 4 +- src/components/appMediaViewer.ts | 15 +-- src/components/chat/replyContainer.ts | 9 +- src/components/gifsMasonry.ts | 6 +- src/components/popups/newMedia.ts | 23 +++-- src/components/sidebarLeft/tabs/background.ts | 21 ++-- .../sidebarRight/tabs/sharedMedia.ts | 7 +- src/components/wrappers.ts | 44 +++++---- src/helpers/blur.ts | 2 +- src/layer.d.ts | 16 +-- src/lib/appManagers/appDocsManager.ts | 39 ++++---- src/lib/appManagers/appDownloadManager.ts | 39 +++++++- src/lib/appManagers/appMessagesManager.ts | 79 +++++++-------- src/lib/appManagers/appPhotosManager.ts | 97 +++++++------------ src/scripts/in/schema_additional_params.json | 24 ----- 15 files changed, 212 insertions(+), 213 deletions(-) diff --git a/src/components/appMediaPlaybackController.ts b/src/components/appMediaPlaybackController.ts index baa1d688..5563b23c 100644 --- a/src/components/appMediaPlaybackController.ts +++ b/src/components/appMediaPlaybackController.ts @@ -10,6 +10,7 @@ import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager"; import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise"; import { isSafari } from "../helpers/userAgent"; import { MOUNT_CLASS_TO } from "../config/debug"; +import appDownloadManager from "../lib/appManagers/appDownloadManager"; // TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда @@ -125,7 +126,8 @@ class AppMediaPlaybackController { this.handleSafariStreamable(media); } - media.src = doc.url; + const cacheContext = appDownloadManager.getCacheContext(doc); + media.src = cacheContext.url; }); }, onError); diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index f8282670..c0e065e9 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -41,6 +41,7 @@ import findUpClassName from "../helpers/dom/findUpClassName"; import renderImageFromUrl from "../helpers/dom/renderImageFromUrl"; import findUpAsChild from "../helpers/dom/findUpAsChild"; import getVisibleRect from "../helpers/dom/getVisibleRect"; +import appDownloadManager from "../lib/appManagers/appDownloadManager"; // TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию // TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода) @@ -952,8 +953,9 @@ class AppMediaViewerBase = Promise.resolve(); + const size = appPhotosManager.setAttachmentSize(media, container, maxWidth, maxHeight, mediaSizes.isMobile ? false : true); if(useContainerAsTarget) { - const cacheContext = appPhotosManager.getCacheContext(media); + const cacheContext = appDownloadManager.getCacheContext(media, size.type); let img: HTMLImageElement; if(cacheContext.downloaded) { img = new Image(); @@ -971,7 +973,6 @@ class AppMediaViewerBase { + const cacheContext = appDownloadManager.getCacheContext(media); const promise = media.supportsStreaming ? Promise.resolve() : appDocsManager.downloadDoc(media); if(!media.supportsStreaming) { onAnimationEnd.then(() => { - if(!media.url) { + if(!cacheContext.url) { preloader.attach(mover, true, promise); } }); @@ -1107,7 +1109,7 @@ class AppMediaViewerBase { + const cacheContext = appDownloadManager.getCacheContext(media, size.type); const cancellablePromise = appPhotosManager.preloadPhoto(media.id, size); onAnimationEnd.then(() => { - if(!media.url) { + if(!cacheContext.url) { this.preloader.attachPromise(cancellablePromise); //this.preloader.attach(mover, true, cancellablePromise); } @@ -1153,7 +1156,7 @@ class AppMediaViewerBase { - renderImageFromUrl(mediaEl, photo._ === 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url); + renderImageFromUrl(mediaEl, cacheContext.url); }); } } diff --git a/src/components/gifsMasonry.ts b/src/components/gifsMasonry.ts index 4c822195..c5191357 100644 --- a/src/components/gifsMasonry.ts +++ b/src/components/gifsMasonry.ts @@ -199,9 +199,9 @@ export default class GifsMasonry { if(willBeAPoster) { img = new Image(); - if(!gotThumb.thumb.url) { + if(!gotThumb.cacheContext.url) { gotThumb.promise.then(() => { - img.src = gotThumb.thumb.url; + img.src = gotThumb.cacheContext.url; }); } } @@ -213,6 +213,6 @@ export default class GifsMasonry { } }; - (gotThumb?.thumb?.url ? renderImageFromUrl(img, gotThumb.thumb.url, afterRender) : afterRender()); + (gotThumb?.cacheContext?.url ? renderImageFromUrl(img, gotThumb.cacheContext.url, afterRender) : afterRender()); } } diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index 45e59616..031cb87f 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -16,6 +16,7 @@ import SendContextMenu from "../chat/sendContextMenu"; import { createPosterFromVideo, onVideoLoad } from "../../helpers/files"; import { MyDocument } from "../../lib/appManagers/appDocsManager"; import I18n, { i18n, LangPackKey } from "../../lib/langPack"; +import appDownloadManager from "../../lib/appManagers/appDownloadManager"; type SendFileParams = Partial<{ file: File, @@ -288,6 +289,18 @@ export default class PopupNewMedia extends PopupElement { params.objectURL = URL.createObjectURL(file); } + const doc = { + _: 'document', + file: file, + file_name: file.name || '', + size: file.size, + type: isPhoto ? 'photo' : 'doc' + } as MyDocument; + + const cacheContext = appDownloadManager.getCacheContext(doc); + cacheContext.url = params.objectURL; + cacheContext.downloaded = file.size; + const docDiv = wrapDocument({ message: { _: 'message', @@ -298,15 +311,7 @@ export default class PopupNewMedia extends PopupElement { peerId: 0, media: { _: 'messageMediaDocument', - document: { - _: 'document', - file: file, - file_name: file.name || '', - size: file.size, - type: isPhoto ? 'photo' : 'doc', - url: params.objectURL, - downloaded: true - } as MyDocument + document: doc } } as any }); diff --git a/src/components/sidebarLeft/tabs/background.ts b/src/components/sidebarLeft/tabs/background.ts index 3a3f45fd..28fa7c13 100644 --- a/src/components/sidebarLeft/tabs/background.ts +++ b/src/components/sidebarLeft/tabs/background.ts @@ -110,7 +110,6 @@ export default class AppBackgroundTab extends SliderSuperTab { location: {} as any, size: file.size, type: 'full', - url: URL.createObjectURL(file) } as PhotoSize.photoSize; let document: MyDocument = { _: 'document', @@ -121,9 +120,7 @@ export default class AppBackgroundTab extends SliderSuperTab { id, mime_type: file.type, size: file.size, - downloaded: true, date: Date.now() / 1000, - url: thumb.url, pFlags: {}, thumbs: [thumb], file_name: file.name @@ -131,9 +128,9 @@ export default class AppBackgroundTab extends SliderSuperTab { document = appDocsManager.saveDoc(document); - const docThumb = appPhotosManager.getDocumentCachedThumb(document.id); - docThumb.downloaded = thumb.size; - docThumb.url = thumb.url; + const cacheContext = appDownloadManager.getCacheContext(document); + cacheContext.downloaded = file.size; + cacheContext.url = URL.createObjectURL(file); let wallpaper: WallPaper.wallPaper = { _: 'wallPaper', @@ -159,8 +156,8 @@ export default class AppBackgroundTab extends SliderSuperTab { } }).then(_wallpaper => { const newDoc = (_wallpaper as WallPaper.wallPaper).document as MyDocument; - newDoc.downloaded = document.downloaded; - newDoc.url = document.url; + const newCacheContext = appDownloadManager.getCacheContext(newDoc); + Object.assign(newCacheContext, cacheContext); wallpaper = _wallpaper as WallPaper.wallPaper; wallpaper.document = appDocsManager.saveDoc(wallpaper.document); @@ -260,7 +257,8 @@ export default class AppBackgroundTab extends SliderSuperTab { const load = () => { const promise = this.setBackgroundDocument(slug, doc); - if(!doc.url || this.theme.background.blur) { + const cacheContext = appDownloadManager.getCacheContext(doc); + if(!cacheContext.url || this.theme.background.blur) { preloader.attach(target, true, promise); } }; @@ -325,9 +323,10 @@ export default class AppBackgroundTab extends SliderSuperTab { }); }; + const cacheContext = appDownloadManager.getCacheContext(doc); if(background.blur) { setTimeout(() => { - blur(doc.url, 12, 4) + blur(cacheContext.url, 12, 4) .then(url => { if(!middleware()) { deferred.resolve(); @@ -338,7 +337,7 @@ export default class AppBackgroundTab extends SliderSuperTab { }); }, 200); } else { - onReady(doc.url); + onReady(cacheContext.url); } }); diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index cef16511..543a6841 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -45,6 +45,7 @@ import PopupPeer from "../../popups/peer"; import Scrollable from "../../scrollable"; import { isTouchSupported } from "../../../helpers/touchSupport"; import { isFirefox } from "../../../helpers/userAgent"; +import appDownloadManager from "../../../lib/appManagers/appDownloadManager"; let setText = (text: string, row: Row) => { //fastRaf(() => { @@ -425,8 +426,10 @@ class PeerProfileAvatars { img.draggable = false; if(photo) { - appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 420, 420, false)).then(() => { - renderImageFromUrl(img, photo.url, () => { + const size = appPhotosManager.choosePhotoSize(photo, 420, 420, false); + appPhotosManager.preloadPhoto(photo, size).then(() => { + const cacheContext = appDownloadManager.getCacheContext(photo, size.type); + renderImageFromUrl(img, cacheContext.url, () => { avatar.append(img); }); }); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 0c984741..cfbaed86 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -38,6 +38,7 @@ import { animateSingle } from '../helpers/animation'; import renderImageFromUrl from '../helpers/dom/renderImageFromUrl'; import sequentialDom from '../helpers/sequentialDom'; import { fastRaf } from '../helpers/schedulers'; +import appDownloadManager from '../lib/appManagers/appDownloadManager'; const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB @@ -271,7 +272,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai const gotThumb = appDocsManager.getThumb(doc, false); if(gotThumb) { gotThumb.promise.then(() => { - video.poster = gotThumb.thumb.url; + video.poster = gotThumb.cacheContext.url; }); } } @@ -280,12 +281,14 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai container.append(video); } + const cacheContext = appDownloadManager.getCacheContext(doc); + let preloader: ProgressivePreloader; if(message?.media?.preloader) { // means upload preloader = message.media.preloader as ProgressivePreloader; preloader.attach(container, false); noAutoDownload = undefined; - } else if(!doc.downloaded && !doc.supportsStreaming) { + } else if(!cacheContext.downloaded && !doc.supportsStreaming) { preloader = new ProgressivePreloader({ attachMethod: 'prepend' }); @@ -305,13 +308,13 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai let loadPromise: Promise = Promise.resolve(); if(preloader) { - if(!doc.downloaded && !doc.supportsStreaming) { + if(!cacheContext.downloaded && !doc.supportsStreaming) { const promise = loadPromise = appDocsManager.downloadDoc(doc, lazyLoadQueue?.queueId, noAutoDownload); preloader.attach(container, false, promise); } else if(doc.supportsStreaming) { if(noAutoDownload) { loadPromise = Promise.reject(); - } else if(!doc.downloaded) { // * check for uploading video + } else if(!cacheContext.downloaded) { // * check for uploading video preloader.attach(container, false, null); video.addEventListener(isSafari ? 'timeupdate' : 'canplay', () => { preloader.detach(); @@ -361,7 +364,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai //video.play(); video.autoplay = true; - renderImageFromUrl(video, doc.url); + renderImageFromUrl(video, cacheContext.url); }, () => {}); return {download: loadPromise, render: deferred}; @@ -462,12 +465,13 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS const icoDiv = document.createElement('div'); icoDiv.classList.add('document-ico'); - if(doc.thumbs?.length || (message.pFlags.is_outgoing && doc.url && doc.type === 'photo')) { + const cacheContext = appDownloadManager.getCacheContext(doc); + if(doc.thumbs?.length || (message.pFlags.is_outgoing && cacheContext.url && doc.type === 'photo')) { docDiv.classList.add('document-with-thumb'); let imgs: HTMLImageElement[] = []; if(message.pFlags.is_outgoing) { - icoDiv.innerHTML = ``; + icoDiv.innerHTML = ``; imgs.push(icoDiv.firstElementChild as HTMLImageElement); } else { const wrapped = wrapPhoto({ @@ -507,7 +511,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS } docDiv.innerHTML = ` - ${doc.downloaded && !uploading ? '' : `
`} + ${cacheContext.downloaded && !uploading ? '' : `
`}
${fileName}${titleAdditionHTML}
${size}
`; @@ -546,7 +550,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS return {download}; }; - if(!(doc.downloaded && !uploading)) { + if(!(cacheContext.downloaded && !uploading)) { downloadDiv = docDiv.querySelector('.document-download'); preloader = message.media.preloader as ProgressivePreloader; @@ -695,7 +699,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT //console.log('wrapPhoto downloaded:', photo, photo.downloaded, container); - const cacheContext = appPhotosManager.getCacheContext(photo); + const cacheContext = appDownloadManager.getCacheContext(photo, size?.type/* photo._ === 'photo' ? size?.type : undefined */); const needFadeIn = (thumbImage || !cacheContext.downloaded) && rootScope.settings.animationsEnabled; if(needFadeIn) { @@ -727,7 +731,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT if(middleware && !middleware()) return Promise.resolve(); return new Promise((resolve) => { - renderImageFromUrl(image, cacheContext.url || photo.url, () => { + renderImageFromUrl(image, cacheContext.url, () => { sequentialDom.mutateElement(container, () => { container.append(image); @@ -840,8 +844,10 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o //console.log('wrap sticker', doc, div, onlyThumb); + const cacheContext = appDownloadManager.getCacheContext(doc); + const toneIndex = emoji ? getEmojiToneIndex(emoji) : -1; - const downloaded = doc.downloaded && !needFadeIn; + const downloaded = cacheContext.downloaded && !needFadeIn; let loadThumbPromise = deferredPromise(); let haveThumbCached = false; @@ -881,8 +887,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o if(thumb && thumb._ !== 'photoPathSize' && toneIndex <= 0) { thumbImage = new Image(); - if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || thumb.url)/* && false */) { - renderImageFromUrl(thumbImage, appPhotosManager.getPreviewURLFromThumb(thumb as PhotoSize.photoStrippedSize, true), afterRender); + if((webpWorkerController.isWebpSupported() || doc.pFlags.stickerThumbConverted || cacheContext.url)/* && false */) { + renderImageFromUrl(thumbImage, appPhotosManager.getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true), afterRender); haveThumbCached = true; } else { webpWorkerController.convert(doc.id, (thumb as PhotoSize.photoStrippedSize).bytes as Uint8Array).then(bytes => { @@ -892,7 +898,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o if(middleware && !middleware()) return; if(!div.childElementCount) { - renderImageFromUrl(thumbImage, appPhotosManager.getPreviewURLFromThumb(thumb as PhotoSize.photoStrippedSize, true), afterRender); + renderImageFromUrl(thumbImage, appPhotosManager.getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true), afterRender); } }).catch(() => {}); } @@ -905,10 +911,10 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o const r = () => { if(div.childElementCount || (middleware && !middleware())) return; - renderImageFromUrl(thumbImage, (thumb as PhotoSize.photoStrippedSize).url, afterRender); + renderImageFromUrl(thumbImage, cacheContext.url, afterRender); }; - if((thumb as PhotoSize.photoStrippedSize).url) { + if(cacheContext.url) { r(); return Promise.resolve(); } else { @@ -1038,7 +1044,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o const r = () => { if(middleware && !middleware()) return resolve(); - renderImageFromUrl(image, doc.url, () => { + renderImageFromUrl(image, cacheContext.url, () => { sequentialDom.mutateElement(div, () => { div.append(image); if(thumbImage) { @@ -1059,7 +1065,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o }); }; - if(doc.url) r(); + if(cacheContext.url) r(); else { appDocsManager.downloadDoc(doc, /* undefined, */lazyLoadQueue?.queueId).then(r, resolve); } diff --git a/src/helpers/blur.ts b/src/helpers/blur.ts index d2a5fd44..27fd8c20 100644 --- a/src/helpers/blur.ts +++ b/src/helpers/blur.ts @@ -11,7 +11,7 @@ import pushHeavyTask from './heavyQueue'; const RADIUS = 2; const ITERATIONS = 2; -const DEBUG = _DEBUG && false; +const DEBUG = _DEBUG && true; function processBlur(dataUri: string, radius: number, iterations: number) { return new Promise((resolve) => { diff --git a/src/layer.d.ts b/src/layer.d.ts index 2f13cb48..d94b72cb 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -1266,9 +1266,7 @@ export namespace Photo { date: number, sizes: Array, video_sizes?: Array, - dc_id: number, - downloaded?: boolean | number, - url?: string + dc_id: number }; } @@ -1289,8 +1287,7 @@ export namespace PhotoSize { location: FileLocation, w: number, h: number, - size: number, - url?: string + size: number }; export type photoCachedSize = { @@ -1299,15 +1296,13 @@ export namespace PhotoSize { location: FileLocation, w: number, h: number, - bytes: Uint8Array, - url?: string + bytes: Uint8Array }; export type photoStrippedSize = { _: 'photoStrippedSize', type: string, - bytes: Uint8Array, - url?: string + bytes: Uint8Array }; export type photoSizeProgressive = { @@ -1317,7 +1312,6 @@ export namespace PhotoSize { w: number, h: number, sizes: Array, - url?: string, size?: number }; @@ -3106,8 +3100,6 @@ export namespace Document { file_name?: string, file?: File, duration?: number, - downloaded?: boolean, - url?: string, audioTitle?: string, audioPerformer?: string, sticker?: number, diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index 1e1227fb..af5caadb 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -37,8 +37,12 @@ export class AppDocsManager { public onServiceWorkerFail = () => { for(const id in this.docs) { const doc = this.docs[id]; - delete doc.supportsStreaming; - delete doc.url; + + if(doc.supportsStreaming) { + delete doc.supportsStreaming; + const cacheContext = appDownloadManager.getCacheContext(doc); + delete cacheContext.url; + } } }; @@ -81,8 +85,7 @@ export class AppDocsManager { // 'file', 'duration', 'downloaded', 'url', 'audioTitle', // 'audioPerformer', 'sticker', 'stickerEmoji', 'stickerEmojiRaw', // 'stickerSetInput', 'stickerThumbConverted', 'animated', 'supportsStreaming']); - defineNotNumerableProperties(doc, ['downloaded', 'url']); - + doc.attributes.forEach(attribute => { switch(attribute._) { case 'documentAttributeFilename': @@ -175,8 +178,9 @@ export class AppDocsManager { if((doc.type === 'gif' && doc.size > 8e6) || doc.type === 'audio' || doc.type === 'video') { doc.supportsStreaming = true; - if(!doc.url) { - doc.url = this.getFileURL(doc); + const cacheContext = appDownloadManager.getCacheContext(doc); + if(!cacheContext.url) { + cacheContext.url = this.getFileURL(doc); } } } @@ -189,7 +193,7 @@ export class AppDocsManager { doc.file_name = ''; } - if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === "AnimatedSticker.tgs") { + if(doc.mime_type === 'application/x-tgsticker' && doc.file_name === 'AnimatedSticker.tgs') { doc.type = 'sticker'; doc.animated = true; doc.sticker = 2; @@ -268,12 +272,11 @@ export class AppDocsManager { public getThumbURL(doc: MyDocument, thumb: PhotoSize.photoSize | PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize) { let promise: Promise = Promise.resolve(); - if(!thumb.url) { + const cacheContext = appDownloadManager.getCacheContext(doc, thumb.type); + if(!cacheContext.url) { if('bytes' in thumb) { promise = blur(appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker)).then(url => { - defineNotNumerableProperties(thumb, ['url']); // * exclude from state - const cacheContext = appPhotosManager.getCacheContext(doc); - cacheContext.url = thumb.url = url; + cacheContext.url = url; }) as any; } else { //return this.getFileURL(doc, false, thumb); @@ -281,7 +284,7 @@ export class AppDocsManager { } } - return {thumb, promise}; + return {thumb, cacheContext, promise}; } public getThumb(doc: MyDocument, tryNotToUseBytes = true) { @@ -305,10 +308,11 @@ export class AppDocsManager { const downloadOptions = this.getFileDownloadOptions(doc, undefined, queueId, onlyCache); download = appDownloadManager.download(downloadOptions); + const cacheContext = appDownloadManager.getCacheContext(doc); const originalPromise = download; originalPromise.then((blob) => { - doc.url = URL.createObjectURL(blob); - doc.downloaded = true; + cacheContext.url = URL.createObjectURL(blob); + cacheContext.downloaded = blob.size; }, () => {}); if(doc.type === 'voice' && !opusDecodeController.isPlaySupported()) { @@ -320,10 +324,10 @@ export class AppDocsManager { const uint8 = new Uint8Array(e.target.result as ArrayBuffer); //console.log('sending uint8 to decoder:', uint8); opusDecodeController.decode(uint8).then(result => { - doc.url = result.url; + cacheContext.url = result.url; resolve(); }, (err) => { - delete doc.downloaded; + delete cacheContext.downloaded; reject(err); }); }; @@ -403,7 +407,8 @@ export class AppDocsManager { return appDownloadManager.downloadToDisc(options, doc.file_name); */ const promise = this.downloadDoc(doc, queueId); promise.then(() => { - appDownloadManager.createDownloadAnchor(doc.url, doc.file_name); + const cacheContext = appDownloadManager.getCacheContext(doc); + appDownloadManager.createDownloadAnchor(cacheContext.url, doc.file_name); }); return promise; } diff --git a/src/lib/appManagers/appDownloadManager.ts b/src/lib/appManagers/appDownloadManager.ts index b8ae6890..fdd09b85 100644 --- a/src/lib/appManagers/appDownloadManager.ts +++ b/src/lib/appManagers/appDownloadManager.ts @@ -4,15 +4,18 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import type { DownloadOptions } from "../mtproto/apiFileManager"; +import type { ApiError } from "../mtproto/apiManager"; +import type { MyDocument } from "./appDocsManager"; +import type { MyPhoto } from "./appPhotosManager"; import rootScope from "../rootScope"; import apiManager from "../mtproto/mtprotoworker"; import { deferredPromise, CancellablePromise } from "../../helpers/cancellablePromise"; -import type { DownloadOptions } from "../mtproto/apiFileManager"; import { InputFile } from "../../layer"; import referenceDatabase, {ReferenceBytes} from "../mtproto/referenceDatabase"; -import type { ApiError } from "../mtproto/apiManager"; import { getFileNameByLocation } from "../../helpers/fileName"; import CacheStorageController from "../cacheStorage"; +import { MOUNT_CLASS_TO } from "../../config/debug"; export type ResponseMethodBlob = 'blob'; export type ResponseMethodJson = 'json'; @@ -28,6 +31,17 @@ export type Download = DownloadBlob/* | DownloadJson */; export type Progress = {done: number, fileName: string, total: number, offset: number}; export type ProgressCallback = (details: Progress) => void; +export type ThumbCache = { + downloaded: number, + url: string +}; + +export type ThumbsCache = { + [id: string]: { + [size: string]: ThumbCache + } +}; + export class AppDownloadManager { public cacheStorage = new CacheStorageController('cachedFiles'); private downloads: {[fileName: string]: Download} = {}; @@ -36,6 +50,14 @@ export class AppDownloadManager { private uploadId = 0; + private thumbsCache: { + photo: ThumbsCache, + document: ThumbsCache + } = { + photo: {}, + document: {} + }; + constructor() { rootScope.on('download_progress', (e) => { const details = e as {done: number, fileName: string, total: number, offset: number}; @@ -247,6 +269,17 @@ export class AppDownloadManager { return download; } + + public getCacheContext(media: MyPhoto | MyDocument, thumbSize: string = ''): ThumbCache { + if(media._ === 'photo' && thumbSize !== 'i') { + thumbSize = ''; + } + + const cache = this.thumbsCache[media._][media.id] ?? (this.thumbsCache[media._][media.id] = {}); + return cache[thumbSize] ?? (cache[thumbSize] = {downloaded: 0, url: ''}); + } } -export default new AppDownloadManager(); +const appDownloadManager = new AppDownloadManager(); +MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appDownloadManager = appDownloadManager); +export default appDownloadManager; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index d2e76614..9e9594b2 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -628,26 +628,28 @@ export class AppMessagesManager { apiFileName = 'photo.' + fileType.split('/')[1]; actionName = 'sendMessageUploadPhotoAction'; + const photoSize = { + _: 'photoSize', + w: options.width, + h: options.height, + type: 'full', + location: null, + size: file.size + } as PhotoSize.photoSize; + photo = { _: 'photo', id: '' + message.id, - sizes: [{ - _: 'photoSize', - w: options.width, - h: options.height, - type: 'full', - location: null, - size: file.size - }], + sizes: [photoSize], w: options.width, h: options.height } as any; - defineNotNumerableProperties(photo, ['downloaded', 'url']); - photo.downloaded = file.size; - photo.url = options.objectURL || ''; + const cacheContext = appDownloadManager.getCacheContext(photo, photoSize.type); + cacheContext.downloaded = file.size; + cacheContext.url = options.objectURL || ''; - appPhotosManager.savePhoto(photo); + photo = appPhotosManager.savePhoto(photo); } else if(fileType.indexOf('video/') === 0) { attachType = 'video'; apiFileName = 'video.mp4'; @@ -686,11 +688,11 @@ export class AppMessagesManager { size: file.size } as any; - defineNotNumerableProperties(document, ['downloaded', 'url']); - // @ts-ignore - document.downloaded = file.size; - document.url = options.objectURL || ''; + const cacheContext = appDownloadManager.getCacheContext(document); + cacheContext.downloaded = file.size; + cacheContext.url = options.objectURL || ''; + let thumb: PhotoSize.photoSize; if(isPhoto) { attributes.push({ _: 'documentAttributeImageSize', @@ -698,32 +700,33 @@ export class AppMessagesManager { h: options.height }); - thumbs.push({ + thumb = { _: 'photoSize', w: options.width, h: options.height, type: 'full', location: null, - size: file.size, - url: options.objectURL - }); + size: file.size + }; } else if(attachType === 'video') { if(options.thumbURL) { - thumbs.push({ + thumb = { _: 'photoSize', w: options.width, h: options.height, type: 'full', location: null, - size: options.thumbBlob.size, - url: options.thumbURL - }); + size: options.thumbBlob.size + }; + + const thumbCacheContext = appDownloadManager.getCacheContext(document, thumb.type); + thumbCacheContext.downloaded = thumb.size; + thumbCacheContext.url = options.thumbURL; } + } - const thumb = thumbs[0] as PhotoSize.photoSize; - const docThumb = appPhotosManager.getDocumentCachedThumb(document.id); - docThumb.downloaded = thumb.size; - docThumb.url = thumb.url; + if(thumb) { + thumbs.push(thumb); } /* if(thumbs.length) { @@ -733,7 +736,7 @@ export class AppMessagesManager { docThumb.url = thumb.url; } */ - appDocsManager.saveDoc(document); + document = appDocsManager.saveDoc(document); } this.log('sendFile', attachType, apiFileName, file.type, options); @@ -4367,29 +4370,27 @@ export class AppMessagesManager { const photo = appPhotosManager.getPhoto('' + tempId); if(/* photo._ !== 'photoEmpty' */photo) { const newPhoto = message.media.photo as MyPhoto; - // костыль - defineNotNumerableProperties(newPhoto, ['downloaded', 'url']); - newPhoto.downloaded = photo.downloaded; - newPhoto.url = photo.url; + const cacheContext = appDownloadManager.getCacheContext(newPhoto); + const oldCacheContext = appDownloadManager.getCacheContext(photo, 'full'); + Object.assign(cacheContext, oldCacheContext); const photoSize = newPhoto.sizes[newPhoto.sizes.length - 1] as PhotoSize.photoSize; - defineNotNumerableProperties(photoSize, ['url']); - photoSize.url = photo.url; const downloadOptions = appPhotosManager.getPhotoDownloadOptions(newPhoto, photoSize); const fileName = getFileNameByLocation(downloadOptions.location); - appDownloadManager.fakeDownload(fileName, photo.url); + appDownloadManager.fakeDownload(fileName, oldCacheContext.url); } } else if(message.media.document) { const doc = appDocsManager.getDoc('' + tempId); if(doc) { if(/* doc._ !== 'documentEmpty' && */doc.type && doc.type !== 'sticker') { const newDoc = message.media.document; - newDoc.downloaded = doc.downloaded; - newDoc.url = doc.url; + const cacheContext = appDownloadManager.getCacheContext(newDoc); + const oldCacheContext = appDownloadManager.getCacheContext(doc); + Object.assign(cacheContext, oldCacheContext); const fileName = appDocsManager.getInputFileName(newDoc); - appDownloadManager.fakeDownload(fileName, doc.url); + appDownloadManager.fakeDownload(fileName, oldCacheContext.url); } } } else if(message.media.poll) { diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 9a0dd583..273d92cd 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -9,6 +9,7 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ +import type { DownloadOptions } from "../mtproto/apiFileManager"; import { bytesFromHex } from "../../helpers/bytes"; import { CancellablePromise } from "../../helpers/cancellablePromise"; import { getFileNameByLocation } from "../../helpers/fileName"; @@ -30,23 +31,16 @@ export type MyPhoto = Photo.photo; // TIMES = 2 DUE TO SIDEBAR AND CHAT //let TEST_FILE_REFERENCE = "5440692274120994569", TEST_FILE_REFERENCE_TIMES = 2; -type DocumentCacheThumb = { - downloaded: number, - url: string -}; - export class AppPhotosManager { private photos: { [id: string]: MyPhoto } = {}; - private documentThumbsCache: { - [docId: string]: DocumentCacheThumb - } = {}; + public windowW = 0; public windowH = 0; - public static jf = new Uint8Array(bytesFromHex('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00')); - public static Df = bytesFromHex('ffd9'); + private static jpegHeader = bytesFromHex('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00'); + private static jpegTail = bytesFromHex('ffd9'); constructor() { // @ts-ignore @@ -155,7 +149,7 @@ export class AppPhotosManager { public getPreviewURLFromBytes(bytes: Uint8Array | number[], isSticker = false) { let arr: Uint8Array; if(!isSticker) { - arr = AppPhotosManager.jf.concat(bytes.slice(3), AppPhotosManager.Df); + arr = new Uint8Array(AppPhotosManager.jpegHeader.concat(Array.from(bytes.slice(3)), AppPhotosManager.jpegTail)); arr[164] = bytes[1]; arr[166] = bytes[2]; } else { @@ -200,12 +194,13 @@ export class AppPhotosManager { return path; } - public getPreviewURLFromThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, isSticker = false) { - return thumb.url ?? (defineNotNumerableProperties(thumb, ['url']), thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); + public getPreviewURLFromThumb(photo: MyPhoto | MyDocument, thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, isSticker = false) { + const cacheContext = appDownloadManager.getCacheContext(photo, thumb.type); + return cacheContext.url || (cacheContext.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); } - public getImageFromStrippedThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, useBlur: boolean) { - const url = this.getPreviewURLFromThumb(thumb, false); + public getImageFromStrippedThumb(photo: MyPhoto | MyDocument, thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, useBlur: boolean) { + const url = this.getPreviewURLFromThumb(photo, thumb, false); const image = new Image(); image.classList.add('thumbnail'); @@ -253,26 +248,24 @@ export class AppPhotosManager { } public getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument, useBlur: boolean): ReturnType { - if(!photo.downloaded || (photo as MyDocument).type === 'video' || (photo as MyDocument).type === 'gif') { - if(photo._ === 'document') { - const cacheContext = this.getCacheContext(photo); - if(cacheContext.downloaded) { - return null; - } + const cacheContext = appDownloadManager.getCacheContext(photo); + if(!cacheContext.downloaded || (photo as MyDocument).type === 'video' || (photo as MyDocument).type === 'gif') { + if(photo._ === 'document' && cacheContext.downloaded) { + return null; } const sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs; const thumb = sizes?.length ? sizes.find(size => size._ === 'photoStrippedSize') : null; if(thumb && ('bytes' in thumb)) { - return appPhotosManager.getImageFromStrippedThumb(thumb as any, useBlur); + return appPhotosManager.getImageFromStrippedThumb(photo, thumb as any, useBlur); } } return null; } - public getPhotoDownloadOptions(photo: MyPhoto | MyDocument, photoSize: PhotoSize, queueId?: number, onlyCache?: boolean) { - const isMyDocument = photo._ === 'document'; + public getPhotoDownloadOptions(photo: MyPhoto | MyDocument, photoSize: PhotoSize, queueId?: number, onlyCache?: boolean): DownloadOptions { + const isDocument = photo._ === 'document'; if(!photoSize || photoSize._ === 'photoSizeEmpty') { //console.error('no photoSize by photo:', photo); @@ -282,14 +275,20 @@ export class AppPhotosManager { // maybe it's a thumb const isPhoto = (photoSize._ === 'photoSize' || photoSize._ === 'photoSizeProgressive') && photo.access_hash && photo.file_reference; const location: InputFileLocation.inputPhotoFileLocation | InputFileLocation.inputDocumentFileLocation | FileLocation = isPhoto ? { - _: isMyDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation', + _: isDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation', id: photo.id, access_hash: photo.access_hash, file_reference: photo.file_reference, thumb_size: photoSize.type } : (photoSize as PhotoSize.photoSize).location; - return {dcId: photo.dc_id, location, size: isPhoto ? (photoSize as PhotoSize.photoSize).size : undefined, queueId, onlyCache}; + return { + dcId: photo.dc_id, + location, + size: isPhoto ? (photoSize as PhotoSize.photoSize).size : undefined, + queueId, + onlyCache + }; } /* public getPhotoURL(photo: MTPhoto | MTMyDocument, photoSize: MTPhotoSize) { @@ -298,7 +297,7 @@ export class AppPhotosManager { return {url: getFileURL('photo', downloadOptions), location: downloadOptions.location}; } */ - public isDownloaded(media: any) { + /* public isDownloaded(media: any) { const isPhoto = media._ === 'photo'; const photo = isPhoto ? this.getPhoto(media.id) : null; let isDownloaded: boolean; @@ -310,9 +309,9 @@ export class AppPhotosManager { } return isDownloaded; - } + } */ - public preloadPhoto(photoId: any, photoSize?: PhotoSize, queueId?: number, onlyCache?: boolean): CancellablePromise { + public preloadPhoto(photoId: MyPhoto | MyDocument | string, photoSize?: PhotoSize, queueId?: number, onlyCache?: boolean): CancellablePromise { const photo = this.getPhoto(photoId); // @ts-ignore @@ -327,13 +326,12 @@ export class AppPhotosManager { photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight); } - const cacheContext = this.getCacheContext(photo); + const cacheContext = appDownloadManager.getCacheContext(photo, photoSize.type); if(cacheContext.downloaded >= ('size' in photoSize ? photoSize.size : 0) && cacheContext.url) { return Promise.resolve() as any; } const downloadOptions = this.getPhotoDownloadOptions(photo, photoSize, queueId, onlyCache); - const fileName = getFileNameByLocation(downloadOptions.location); let download = appDownloadManager.getDownload(fileName); @@ -343,35 +341,22 @@ export class AppPhotosManager { download = appDownloadManager.download(downloadOptions); download.then(blob => { - const url = URL.createObjectURL(blob); if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) { - defineNotNumerableProperties(cacheContext, ['downloaded', 'url']); - + const url = URL.createObjectURL(blob); cacheContext.downloaded = blob.size; cacheContext.url = url; //console.log('wrote photo:', photo, photoSize, cacheContext, blob); } - defineNotNumerableProperties(photoSize, ['url']); - (photoSize as any).url = url; - return blob; }).catch(() => {}); return download; } - - public getCacheContext(photo: any): DocumentCacheThumb { - return photo._ === 'document' ? this.getDocumentCachedThumb(photo.id) : photo; - } - - public getDocumentCachedThumb(docId: string) { - return this.documentThumbsCache[docId] ?? (this.documentThumbsCache[docId] = {downloaded: 0, url: ''}); - } - public getPhoto(photoId: any): MyPhoto { - return isObject(photoId) ? photoId : this.photos[photoId]; + public getPhoto(photoId: any/* MyPhoto | string */): MyPhoto { + return isObject(photoId) ? photoId as MyPhoto : this.photos[photoId as any as string]; } public getInput(photo: MyPhoto): InputMedia.inputMediaPhoto { @@ -393,21 +378,9 @@ export class AppPhotosManager { return; } - const location: InputFileLocation.inputDocumentFileLocation | InputFileLocation.inputPhotoFileLocation = { - _: photo._ === 'document' ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation', - id: photo.id, - access_hash: photo.access_hash, - file_reference: photo.file_reference, - thumb_size: fullPhotoSize.type - }; - - appDownloadManager.downloadToDisc({ - dcId: photo.dc_id, - location, - size: fullPhotoSize.size, - fileName: 'photo' + photo.id + '.jpg', - queueId - }, 'photo' + photo.id + '.jpg'); + const downloadOptions = this.getPhotoDownloadOptions(photo, fullPhotoSize, queueId); + downloadOptions.fileName = 'photo' + photo.id + '.jpg'; + appDownloadManager.downloadToDisc(downloadOptions, downloadOptions.fileName); } } diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index 38649b9b..f6fa7d9b 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -8,8 +8,6 @@ {"name": "file_name", "type": "string"}, {"name": "file", "type": "File"}, {"name": "duration", "type": "number"}, - {"name": "downloaded", "type": "boolean"}, - {"name": "url", "type": "string"}, {"name": "audioTitle", "type": "string"}, {"name": "audioPerformer", "type": "string"}, {"name": "sticker", "type": "number"}, @@ -21,31 +19,9 @@ {"name": "animated", "type": "boolean"}, {"name": "supportsStreaming", "type": "boolean"} ] -}, { - "predicate": "photo", - "params": [ - {"name": "downloaded", "type": "boolean | number"}, - {"name": "url", "type": "string"} - ] -}, { - "predicate": "photoSize", - "params": [ - {"name": "url", "type": "string"} - ] -}, { - "predicate": "photoCachedSize", - "params": [ - {"name": "url", "type": "string"} - ] -}, { - "predicate": "photoStrippedSize", - "params": [ - {"name": "url", "type": "string"} - ] }, { "predicate": "photoSizeProgressive", "params": [ - {"name": "url", "type": "string"}, {"name": "size", "type": "number"} ] }, {