Service worker changes:
Moved whole apiFileManager TO-DO: Preloader progress File downloads Webp converter in SW
This commit is contained in:
parent
dad42da803
commit
23c91473f6
@ -1,5 +1,4 @@
|
||||
import resizeableImage from "../lib/cropper";
|
||||
import apiFileManager from "../lib/mtproto/apiFileManager";
|
||||
|
||||
export class PopupAvatar {
|
||||
private container = document.getElementById('popup-avatar');
|
||||
@ -77,7 +76,11 @@ export class PopupAvatar {
|
||||
|
||||
private resolve() {
|
||||
this.onCrop(() => {
|
||||
return apiFileManager.uploadFile(this.blob);
|
||||
//return apiFileManager.uploadFile(this.blob);
|
||||
return fetch('/upload', {
|
||||
method: 'POST',
|
||||
body: this.blob
|
||||
}).then(res => res.json());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ import { mediaSizes } from '../lib/config';
|
||||
import { MTDocument, MTPhotoSize } from '../types';
|
||||
import animationIntersector from './animationIntersector';
|
||||
import AudioElement from './audio';
|
||||
import MP4Source from '../lib/MP4Source';
|
||||
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue}: {
|
||||
doc: MTDocument,
|
||||
@ -90,26 +89,18 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
container.append(video);
|
||||
}
|
||||
|
||||
let loadVideo = async() => {
|
||||
let url: string;
|
||||
const loadVideo = async() => {
|
||||
if(message.media.preloader) { // means upload
|
||||
(message.media.preloader as ProgressivePreloader).attach(container, undefined, undefined, false);
|
||||
} else if(!doc.downloaded) {
|
||||
const promise = appDocsManager.downloadVideo(doc.id);
|
||||
const promise = appDocsManager.downloadDoc(doc.id);
|
||||
|
||||
//if(!doc.supportsStreaming) {
|
||||
const preloader = new ProgressivePreloader(container, true);
|
||||
preloader.attach(container, true, promise, false);
|
||||
//}
|
||||
|
||||
const mp4Source: MP4Source = await (promise as Promise<MP4Source>);
|
||||
if(mp4Source instanceof MP4Source) {
|
||||
url = mp4Source.getURL();
|
||||
}
|
||||
}
|
||||
|
||||
if(!url) {
|
||||
url = doc.url;
|
||||
await promise;
|
||||
}
|
||||
|
||||
if(middleware && !middleware()) {
|
||||
@ -131,7 +122,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
renderImageFromUrl(source, url);
|
||||
renderImageFromUrl(source, doc.url);
|
||||
source.type = doc.mime_type;
|
||||
video.append(source);
|
||||
video.setAttribute('playsinline', '');
|
||||
|
@ -1,24 +1,15 @@
|
||||
import apiFileManager from '../mtproto/apiFileManager';
|
||||
import FileManager from '../filemanager';
|
||||
import {RichTextProcessor} from '../richtextprocessor';
|
||||
import { CancellablePromise, deferredPromise } from '../polyfill';
|
||||
import { isObject } from '../utils';
|
||||
import { isObject, getFileURL } from '../utils';
|
||||
import opusDecodeController from '../opusDecodeController';
|
||||
import { MTDocument } from '../../types';
|
||||
import MP4Source from '../MP4Source';
|
||||
import { bufferConcat } from '../bin_utils';
|
||||
import { MTDocument, inputDocumentFileLocation } from '../../types';
|
||||
|
||||
class AppDocsManager {
|
||||
private docs: {[docID: string]: MTDocument} = {};
|
||||
private thumbs: {[docIDAndSize: string]: Promise<string>} = {};
|
||||
private downloadPromises: {[docID: string]: CancellablePromise<Blob>} = {};
|
||||
|
||||
private videoChunks: {[docID: string]: CancellablePromise<ArrayBuffer>[]} = {};
|
||||
private videoChunksQueue: {[docID: string]: {offset: number}[]} = {};
|
||||
|
||||
private loadedMP4Box: Promise<void>;
|
||||
private mp4Source: MP4Source;
|
||||
|
||||
public saveDoc(apiDoc: MTDocument, context?: any) {
|
||||
//console.log('saveDoc', apiDoc, this.docs[apiDoc.id]);
|
||||
if(this.docs[apiDoc.id]) {
|
||||
@ -41,16 +32,6 @@ class AppDocsManager {
|
||||
|
||||
this.docs[apiDoc.id] = apiDoc;
|
||||
|
||||
if(apiDoc.thumb && apiDoc.thumb._ == 'photoCachedSize') {
|
||||
console.warn('this will happen!!!');
|
||||
apiFileManager.saveSmallFile(apiDoc.thumb.location, apiDoc.thumb.bytes);
|
||||
|
||||
// Memory
|
||||
apiDoc.thumb.size = apiDoc.thumb.bytes.length;
|
||||
delete apiDoc.thumb.bytes;
|
||||
apiDoc.thumb._ = 'photoSize';
|
||||
}
|
||||
|
||||
if(apiDoc.thumb && apiDoc.thumb._ == 'photoSizeEmpty') {
|
||||
delete apiDoc.thumb;
|
||||
}
|
||||
@ -156,8 +137,8 @@ class AppDocsManager {
|
||||
return apiDoc;
|
||||
}
|
||||
|
||||
public getDoc(docID: any): MTDocument {
|
||||
return isObject(docID) ? docID : this.docs[docID];
|
||||
public getDoc(docID: string | MTDocument): MTDocument {
|
||||
return isObject(docID) && typeof(docID) !== 'string' ? docID : this.docs[docID as string];
|
||||
}
|
||||
|
||||
public getMediaInputByID(docID: any) {
|
||||
@ -175,7 +156,7 @@ class AppDocsManager {
|
||||
};
|
||||
}
|
||||
|
||||
public getInputByID(docID: any, thumbSize?: string) {
|
||||
public getInputByID(docID: any, thumbSize?: string): inputDocumentFileLocation {
|
||||
let doc = this.getDoc(docID);
|
||||
|
||||
return {
|
||||
@ -200,189 +181,40 @@ class AppDocsManager {
|
||||
return 't_' + (doc.type || 'file') + doc.id + fileExt;
|
||||
}
|
||||
|
||||
private loadMP4Box() {
|
||||
if(this.loadedMP4Box) return this.loadedMP4Box;
|
||||
|
||||
return this.loadedMP4Box = new Promise((resolve, reject) => {
|
||||
(window as any).mp4BoxLoaded = () => {
|
||||
//console.log('webpHero loaded');
|
||||
this.mp4Source = (window as any).MP4Source;
|
||||
resolve();
|
||||
};
|
||||
|
||||
let sc = document.createElement('script');
|
||||
sc.src = 'mp4box.all.min.js';
|
||||
sc.async = true;
|
||||
sc.onload = (window as any).mp4BoxLoaded;
|
||||
|
||||
document.body.appendChild(sc);
|
||||
});
|
||||
public getFileURLByDoc(doc: MTDocument) {
|
||||
const inputFileLocation = this.getInputByID(doc);
|
||||
return getFileURL('document', {dcID: doc.dc_id, location: inputFileLocation, size: doc.size, mimeType: doc.mime_type || 'application/octet-stream'});
|
||||
}
|
||||
|
||||
private createMP4Stream(doc: MTDocument) {
|
||||
const limitPart = 524288;
|
||||
const chunks = this.videoChunks[doc.id];
|
||||
const queue = this.videoChunksQueue[doc.id];
|
||||
|
||||
//let mp4Source = new MP4Source({duration: doc.duration, video: {expected_size: doc.size}}, (offset: number, end: number) => {
|
||||
let mp4Source = new (this.mp4Source as any)({duration: doc.duration, video: {expected_size: doc.size}}, (offset: number, end: number) => {
|
||||
const chunkStart = offset - (offset % limitPart);
|
||||
|
||||
const sorted: typeof queue = [];
|
||||
const lower: typeof queue = [];
|
||||
for(let i = 0; i < queue.length; ++i) {
|
||||
if(queue[i].offset >= chunkStart) {
|
||||
sorted.push(queue[i]);
|
||||
} else {
|
||||
lower.push(queue[i]);
|
||||
}
|
||||
}
|
||||
sorted.sort((a, b) => a.offset - b.offset).concat(lower).forEach((q, i) => {
|
||||
queue[i] = q;
|
||||
});
|
||||
|
||||
const index1 = offset / limitPart | 0;
|
||||
const index2 = end / limitPart | 0;
|
||||
|
||||
const p = chunks.slice(index1, index2 + 1);
|
||||
|
||||
//console.log('MP4Source getBuffer:', offset, end, index1, index2, doc.size, JSON.stringify(queue));
|
||||
|
||||
if(offset % limitPart == 0) {
|
||||
return p[0];
|
||||
} else {
|
||||
return Promise.all(p).then(buffers => {
|
||||
const buffer = buffers.length > 1 ? bufferConcat(buffers[0], buffers[1]) : buffers[0];
|
||||
const start = (offset % limitPart);
|
||||
const _end = start + (end - offset);
|
||||
|
||||
const sliced = buffer.slice(start, _end);
|
||||
//console.log('slice buffer:', sliced);
|
||||
return sliced;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return mp4Source;
|
||||
}
|
||||
|
||||
private mp4Stream(doc: MTDocument, deferred: CancellablePromise<Blob>) {
|
||||
const limitPart = 524288;
|
||||
const promises = this.videoChunks[doc.id] ?? (this.videoChunks[doc.id] = []);
|
||||
if(!promises.length) {
|
||||
for(let offset = 0; offset < doc.size; offset += limitPart) {
|
||||
const deferred = deferredPromise<ArrayBuffer>();
|
||||
promises.push(deferred);
|
||||
}
|
||||
}
|
||||
|
||||
let good = false;
|
||||
return async(bytes: Uint8Array, offset: number, queue: {offset: number}[]) => {
|
||||
if(!deferred.isFulfilled && !deferred.isRejected/* && offset == 0 */) {
|
||||
this.videoChunksQueue[doc.id] = queue;
|
||||
console.log('stream:', doc, doc.url, deferred);
|
||||
//doc.url = mp4Source.getURL();
|
||||
//deferred.resolve(mp4Source);
|
||||
deferred.resolve();
|
||||
good = true;
|
||||
} else if(!good) {
|
||||
//mp4Source.stop();
|
||||
//mp4Source = null;
|
||||
promises.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const index = offset % limitPart == 0 ? offset / limitPart : promises.length - 1;
|
||||
promises[index].resolve(bytes.slice().buffer);
|
||||
//console.log('i wont believe in you', doc, bytes, offset, promises, bytes.length, bytes.buffer.byteLength, bytes.slice().buffer);
|
||||
//console.log('i wont believe in you', bytes, doc, bytes.length, offset);
|
||||
};
|
||||
}
|
||||
|
||||
public downloadVideo(docID: string): CancellablePromise<MP4Source | Blob> {
|
||||
const doc = this.getDoc(docID);
|
||||
if(!doc.supportsStreaming || doc.url) {
|
||||
return this.downloadDoc(docID);
|
||||
}
|
||||
|
||||
const deferred = deferredPromise<Blob>();
|
||||
let canceled = false;
|
||||
deferred.cancel = () => {
|
||||
canceled = true;
|
||||
};
|
||||
|
||||
this.loadMP4Box().then(() => {
|
||||
if(canceled) {
|
||||
throw 'canceled';
|
||||
}
|
||||
|
||||
const promise = this.downloadDoc(docID);
|
||||
|
||||
deferred.cancel = () => {
|
||||
promise.cancel();
|
||||
};
|
||||
|
||||
promise.notify = (...args) => {
|
||||
deferred.notify && deferred.notify(...args);
|
||||
};
|
||||
|
||||
promise.then(() => {
|
||||
if(doc.url) { // может быть уже загружен из кэша
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.resolve(this.createMP4Stream(doc));
|
||||
}
|
||||
});
|
||||
}, deferred.reject);
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
public downloadDoc(docID: any, toFileEntry?: any): CancellablePromise<Blob> {
|
||||
public downloadDoc(docID: string | MTDocument, toFileEntry?: any): CancellablePromise<Blob> {
|
||||
const doc = this.getDoc(docID);
|
||||
|
||||
if(doc._ == 'documentEmpty') {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
const inputFileLocation = this.getInputByID(doc);
|
||||
if(doc.downloaded && !toFileEntry) {
|
||||
if(doc.url) return Promise.resolve(null);
|
||||
|
||||
const cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
|
||||
/* const cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
|
||||
if(cachedBlob) {
|
||||
return Promise.resolve(cachedBlob);
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
if(this.downloadPromises[doc.id]) {
|
||||
return this.downloadPromises[doc.id];
|
||||
}
|
||||
|
||||
//historyDoc.progress = {enabled: !historyDoc.downloaded, percent: 1, total: doc.size};
|
||||
|
||||
const deferred = deferredPromise<Blob>();
|
||||
deferred.cancel = () => {
|
||||
downloadPromise.cancel();
|
||||
};
|
||||
|
||||
const processPart = doc.supportsStreaming ? this.mp4Stream(doc, deferred) : undefined;
|
||||
|
||||
// нет смысла делать объект с выполняющимися промисами, нижняя строка и так вернёт загружающийся
|
||||
const downloadPromise = apiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
|
||||
mimeType: doc.mime_type || 'application/octet-stream',
|
||||
toFileEntry: toFileEntry,
|
||||
stickerType: doc.sticker,
|
||||
processPart
|
||||
});
|
||||
|
||||
downloadPromise.notify = (...args) => {
|
||||
deferred.notify && deferred.notify(...args);
|
||||
};
|
||||
|
||||
//deferred.notify = downloadPromise.notify;
|
||||
/* if(doc.supportsStreaming) {
|
||||
doc.url = '/stream/' + '';
|
||||
} */
|
||||
|
||||
downloadPromise.then((blob) => {
|
||||
const url = this.getFileURLByDoc(doc);
|
||||
fetch(url).then(res => res.blob())
|
||||
/* downloadPromise */.then((blob) => {
|
||||
if(blob) {
|
||||
doc.downloaded = true;
|
||||
|
||||
@ -405,11 +237,7 @@ class AppDocsManager {
|
||||
|
||||
return;
|
||||
} else if(doc.type && doc.sticker != 2) {
|
||||
/* if(processPart) {
|
||||
console.log('stream after:', doc, doc.url, deferred);
|
||||
} */
|
||||
|
||||
doc.url = URL.createObjectURL(blob);
|
||||
doc.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,23 +245,10 @@ class AppDocsManager {
|
||||
}, (e) => {
|
||||
console.log('document download failed', e);
|
||||
deferred.reject(e);
|
||||
//historyDoc.progress.enabled = false;
|
||||
}).finally(() => {
|
||||
deferred.notify = downloadPromise.notify = deferred.cancel = downloadPromise.cancel = null;
|
||||
//deferred.notify = downloadPromise.notify = deferred.cancel = downloadPromise.cancel = null;
|
||||
});
|
||||
|
||||
/* downloadPromise.notify = (progress) => {
|
||||
console.log('dl progress', progress);
|
||||
historyDoc.progress.enabled = true;
|
||||
historyDoc.progress.done = progress.done;
|
||||
historyDoc.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total));
|
||||
$rootScope.$broadcast('history_update');
|
||||
}; */
|
||||
|
||||
//historyDoc.progress.cancel = downloadPromise.cancel;
|
||||
|
||||
//console.log('return downloadPromise:', downloadPromise);
|
||||
|
||||
return this.downloadPromises[doc.id] = deferred;
|
||||
}
|
||||
|
||||
@ -451,16 +266,8 @@ class AppDocsManager {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
let mimeType = doc.sticker ? 'image/webp' : doc.mime_type;
|
||||
let promise = apiFileManager.downloadSmallFile(input, {
|
||||
dcID: doc.dc_id,
|
||||
stickerType: doc.sticker ? 1 : undefined,
|
||||
mimeType: mimeType
|
||||
});
|
||||
|
||||
return this.thumbs[key] = promise.then((blob) => {
|
||||
return URL.createObjectURL(blob);
|
||||
});
|
||||
const url = getFileURL('thumb', {dcID: doc.dc_id, location: input, mimeType: doc.sticker ? 'image/webp' : doc.mime_type});
|
||||
return this.thumbs[key] = Promise.resolve(url);
|
||||
}
|
||||
|
||||
public hasDownloadedThumb(docID: string, thumbSize: string) {
|
||||
|
@ -549,7 +549,7 @@ export class AppImManager {
|
||||
private closeBtn = this.topbar.querySelector('.sidebar-close-button') as HTMLButtonElement;
|
||||
|
||||
constructor() {
|
||||
this.log = logger('IM', LogLevels.log | LogLevels.error | LogLevels.warn | LogLevels.debug);
|
||||
this.log = logger('IM', /* LogLevels.log | LogLevels.warn | LogLevels.debug | */ LogLevels.error);
|
||||
this.chatInputC = new ChatInput();
|
||||
this.preloader = new ProgressivePreloader(null, false);
|
||||
this.selectTab(0);
|
||||
@ -2641,7 +2641,7 @@ export class AppImManager {
|
||||
|
||||
avatarElem.setAttribute('peer', '' + ((message.fwd_from && this.peerID == this.myID ? message.fwdFromID : message.fromID) || 0));
|
||||
|
||||
this.log('exec loadDialogPhoto', message);
|
||||
//this.log('exec loadDialogPhoto', message);
|
||||
|
||||
bubbleContainer.append(avatarElem);
|
||||
}
|
||||
|
@ -826,7 +826,7 @@ export class AppMediaViewer {
|
||||
|
||||
if(!source.src || (media.url && media.url != source.src)) {
|
||||
const load = () => {
|
||||
const promise = appDocsManager.downloadVideo(media.id);
|
||||
const promise = appDocsManager.downloadDoc(media.id);
|
||||
|
||||
const streamable = media.supportsStreaming && !media.url;
|
||||
//if(!streamable) {
|
||||
|
@ -11,7 +11,6 @@ import appPhotosManager from "./appPhotosManager";
|
||||
import AppStorage from '../storage';
|
||||
import appPeersManager from "./appPeersManager";
|
||||
import ServerTimeManager from "../mtproto/serverTimeManager";
|
||||
import apiFileManager from "../mtproto/apiFileManager";
|
||||
import appDocsManager from "./appDocsManager";
|
||||
import ProgressivePreloader from "../../components/preloader";
|
||||
import serverTimeManager from "../mtproto/serverTimeManager";
|
||||
@ -22,7 +21,8 @@ import { CancellablePromise, deferredPromise } from "../polyfill";
|
||||
import appPollsManager from "./appPollsManager";
|
||||
import searchIndexManager from '../searchIndexManager';
|
||||
import { MTDocument, MTPhotoSize } from "../../types";
|
||||
import { logger } from "../logger";
|
||||
import { logger, LogLevels } from "../logger";
|
||||
import type {ApiFileManager} from '../mtproto/apiFileManager';
|
||||
|
||||
//console.trace('include');
|
||||
|
||||
@ -595,7 +595,7 @@ export class AppMessagesManager {
|
||||
dialogs: []
|
||||
};
|
||||
|
||||
private log = logger('MESSAGES'/* , LogLevels.error */);
|
||||
private log = logger('MESSAGES', LogLevels.error);
|
||||
|
||||
public dialogsStorage = new DialogsStorage();
|
||||
public filtersStorage = new FiltersStorage();
|
||||
@ -1179,7 +1179,7 @@ export class AppMessagesManager {
|
||||
};
|
||||
|
||||
var uploaded = false,
|
||||
uploadPromise: ReturnType<typeof apiFileManager.uploadFile> = null;
|
||||
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
|
||||
|
||||
let invoke = (flags: number, inputMedia: any) => {
|
||||
this.setTyping('sendMessageCancelAction');
|
||||
@ -1241,7 +1241,11 @@ export class AppMessagesManager {
|
||||
this.sendFilePromise.then(() => {
|
||||
if(!uploaded || message.error) {
|
||||
uploaded = false;
|
||||
uploadPromise = apiFileManager.uploadFile(file);
|
||||
//uploadPromise = apiFileManager.uploadFile(file);
|
||||
uploadPromise = fetch('/upload', {
|
||||
method: 'POST',
|
||||
body: file
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
uploadPromise && uploadPromise.then((inputFile) => {
|
||||
@ -1485,7 +1489,7 @@ export class AppMessagesManager {
|
||||
};
|
||||
|
||||
let uploaded = false,
|
||||
uploadPromise: ReturnType<typeof apiFileManager.uploadFile> = null;
|
||||
uploadPromise: ReturnType<ApiFileManager['uploadFile']> = null;
|
||||
|
||||
let inputPeer = appPeersManager.getInputPeerByID(peerID);
|
||||
let invoke = (multiMedia: any[]) => {
|
||||
@ -1517,7 +1521,11 @@ export class AppMessagesManager {
|
||||
|
||||
if(!uploaded || message.error) {
|
||||
uploaded = false;
|
||||
uploadPromise = apiFileManager.uploadFile(file);
|
||||
//uploadPromise = apiFileManager.uploadFile(file);
|
||||
uploadPromise = fetch('/upload', {
|
||||
method: 'POST',
|
||||
body: file
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
uploadPromise.notify = (progress: {done: number, total: number}) => {
|
||||
|
@ -1,11 +1,10 @@
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import { calcImageInBox, isObject } from "../utils";
|
||||
import { calcImageInBox, isObject, getFileURL } from "../utils";
|
||||
import fileManager from '../filemanager';
|
||||
import { bytesFromHex } from "../bin_utils";
|
||||
import apiFileManager from "../mtproto/apiFileManager";
|
||||
//import apiManager from '../mtproto/apiManager';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import { MTPhotoSize } from "../../types";
|
||||
import { MTPhotoSize, inputPhotoFileLocation, inputDocumentFileLocation, InputFileLocation, FileLocation } from "../../types";
|
||||
|
||||
export type MTPhoto = {
|
||||
_: 'photo' | 'photoEmpty' | string,
|
||||
@ -250,17 +249,17 @@ export class AppPhotosManager {
|
||||
}
|
||||
|
||||
public preloadPhoto(photoID: any, photoSize?: MTPhotoSize): Promise<Blob | void> {
|
||||
let photo = this.getPhoto(photoID);
|
||||
const photo = this.getPhoto(photoID);
|
||||
|
||||
if(!photoSize) {
|
||||
let fullWidth = this.windowW;
|
||||
let fullHeight = this.windowH;
|
||||
const fullWidth = this.windowW;
|
||||
const fullHeight = this.windowH;
|
||||
|
||||
photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
|
||||
}
|
||||
|
||||
let isDocument = photo._ == 'document';
|
||||
let cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo;
|
||||
const isDocument = photo._ == 'document';
|
||||
const cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo;
|
||||
|
||||
if(cacheContext.downloaded >= photoSize.size && cacheContext.url) {
|
||||
return Promise.resolve();
|
||||
@ -273,7 +272,7 @@ export class AppPhotosManager {
|
||||
|
||||
// maybe it's a thumb
|
||||
let isPhoto = photoSize.size && photo.access_hash && photo.file_reference;
|
||||
let location = isPhoto ? {
|
||||
let location: inputPhotoFileLocation | inputDocumentFileLocation | FileLocation = isPhoto ? {
|
||||
_: isDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
|
||||
id: photo.id,
|
||||
access_hash: photo.access_hash,
|
||||
@ -281,19 +280,20 @@ export class AppPhotosManager {
|
||||
thumb_size: photoSize.type
|
||||
} : photoSize.location;
|
||||
|
||||
const url = getFileURL('photo', {dcID: photo.dc_id, location, size: isPhoto ? photoSize.size : undefined});
|
||||
let promise: Promise<Blob>;
|
||||
if(isPhoto/* && photoSize.size >= 1e6 */) {
|
||||
//console.log('Photos downloadFile exec', photo);
|
||||
promise = apiFileManager.downloadFile(photo.dc_id, location, photoSize.size);
|
||||
promise = fetch(url).then(res => res.blob());
|
||||
} else {
|
||||
//console.log('Photos downloadSmallFile exec', photo, location);
|
||||
promise = apiFileManager.downloadSmallFile(location);
|
||||
promise = fetch(url).then(res => res.blob());
|
||||
}
|
||||
|
||||
promise.then(blob => {
|
||||
if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) {
|
||||
cacheContext.downloaded = blob.size;
|
||||
cacheContext.url = URL.createObjectURL(blob);
|
||||
//cacheContext.url = URL.createObjectURL(blob);
|
||||
cacheContext.url = url;
|
||||
|
||||
//console.log('wrote photo:', photo, photoSize, cacheContext, blob);
|
||||
}
|
||||
@ -333,7 +333,7 @@ export class AppPhotosManager {
|
||||
var fullWidth = this.windowW;
|
||||
var fullHeight = this.windowH;
|
||||
var fullPhotoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
|
||||
var inputFileLocation = {
|
||||
var inputFileLocation: inputDocumentFileLocation | inputPhotoFileLocation = {
|
||||
// @ts-ignore
|
||||
_: photo._ == 'document' ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
|
||||
id: photo.id,
|
||||
@ -346,7 +346,7 @@ export class AppPhotosManager {
|
||||
let writer = fileManager.chooseSaveFile(fileName, ext, mimeType, fullPhotoSize.size);
|
||||
writer.ready.then(() => {
|
||||
console.log('ready');
|
||||
apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {
|
||||
apiManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {
|
||||
mimeType: mimeType,
|
||||
toFileEntry: writer
|
||||
}).then(() => {
|
||||
@ -360,12 +360,12 @@ export class AppPhotosManager {
|
||||
} catch(err) {
|
||||
console.error('err', err);
|
||||
|
||||
var cachedBlob = apiFileManager.getCachedFile(inputFileLocation)
|
||||
/* var cachedBlob = apiFileManager.getCachedFile(inputFileLocation)
|
||||
if (cachedBlob) {
|
||||
return fileManager.download(cachedBlob, mimeType, fileName);
|
||||
}
|
||||
} */
|
||||
|
||||
apiFileManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {mimeType: mimeType})
|
||||
apiManager.downloadFile(photo.dc_id, inputFileLocation, fullPhotoSize.size, {mimeType: mimeType})
|
||||
.then((blob: Blob) => {
|
||||
fileManager.download(blob, mimeType, fileName);
|
||||
}, (e: any) => {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import AppStorage from '../storage';
|
||||
//import apiManager from '../mtproto/apiManager';
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import apiFileManager from '../mtproto/apiFileManager';
|
||||
import appDocsManager from './appDocsManager';
|
||||
import { MTDocument } from '../../types';
|
||||
import { $rootScope } from '../utils';
|
||||
import { MTDocument, inputStickerSetThumb } from '../../types';
|
||||
import { $rootScope, getFileURL } from '../utils';
|
||||
|
||||
export type MTStickerSet = {
|
||||
_: 'stickerSet',
|
||||
@ -224,17 +223,17 @@ class AppStickersManager {
|
||||
|
||||
const isAnimated = stickerSet.pFlags?.animated;
|
||||
|
||||
const promise = apiFileManager.downloadFile(dcID, {
|
||||
const input: inputStickerSetThumb = {
|
||||
_: 'inputStickerSetThumb',
|
||||
stickerset: this.getStickerSetInput(stickerSet),
|
||||
volume_id: thumb.location.volume_id,
|
||||
local_id: thumb.location.local_id
|
||||
}, thumb.size, {
|
||||
stickerType: isAnimated ? 2 : 1,
|
||||
mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'
|
||||
});
|
||||
};
|
||||
|
||||
return promise;
|
||||
const url = getFileURL('document', {dcID, location: input, size: thumb.size, mimeType: isAnimated ? "application/x-tgsticker" : 'image/webp'});
|
||||
return fetch(url).then(res => res.blob());
|
||||
|
||||
//return promise;
|
||||
}
|
||||
|
||||
public getStickerSetInput(set: {id: string, access_hash: string}) {
|
||||
|
@ -71,6 +71,7 @@ class AppWebpManager {
|
||||
if(this.testPromise) return this.testPromise;
|
||||
|
||||
return this.testPromise = new Promise((resolve, reject) => {
|
||||
return resolve(this.webpSupport = true);
|
||||
let webP = new Image();
|
||||
webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' +
|
||||
'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA';
|
||||
@ -95,8 +96,8 @@ class AppWebpManager {
|
||||
}
|
||||
|
||||
const appWebpManager = new AppWebpManager();
|
||||
// @ts-ignore
|
||||
/* // @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).appWebpManager = appWebpManager;
|
||||
}
|
||||
} */
|
||||
export default appWebpManager;
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
// @ts-ignore
|
||||
import {BigInteger, SecureRandom} from 'jsbn';
|
||||
import { InputFileLocation, FileLocation } from '../types';
|
||||
|
||||
/// #if !MTPROTO_WORKER
|
||||
// @ts-ignore
|
||||
@ -380,3 +381,30 @@ export function addPadding(bytes: any, blockSize: number = 16, zeroes?: boolean,
|
||||
export function nextRandomInt(maxValue: number) {
|
||||
return Math.floor(Math.random() * maxValue);
|
||||
}
|
||||
|
||||
export function getFileNameByLocation(location: InputFileLocation | FileLocation, options?: Partial<{
|
||||
fileName: string
|
||||
}>) {
|
||||
const fileName = (options?.fileName || '').split('.');
|
||||
const ext = fileName[fileName.length - 1] || '';
|
||||
|
||||
switch(location._) {
|
||||
case 'inputPhotoFileLocation':
|
||||
case 'inputDocumentFileLocation': {
|
||||
const thumbPart = location.thumb_size ? '_' + location.thumb_size : '';
|
||||
return (fileName[0] ? fileName[0] + '_' : '') + location.id + thumbPart + (ext ? '.' + ext : ext);
|
||||
}
|
||||
|
||||
case 'fileLocationToBeDeprecated':
|
||||
case 'inputPeerPhotoFileLocation':
|
||||
case 'inputStickerSetThumb':
|
||||
case 'inputFileLocation': {
|
||||
return location.volume_id + '_' + location.local_id + (ext ? '.' + ext : ext);
|
||||
}
|
||||
|
||||
default: {
|
||||
console.error('Unrecognized location:', location);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +95,8 @@ class CacheStorageController {
|
||||
}
|
||||
|
||||
const cacheStorage = new CacheStorageController();
|
||||
// @ts-ignore
|
||||
/* // @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).cacheStorage = cacheStorage;
|
||||
}
|
||||
} */
|
||||
export default cacheStorage;
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { nextRandomInt } from "../bin_utils";
|
||||
import { nextRandomInt, getFileNameByLocation } from "../bin_utils";
|
||||
|
||||
//import IdbFileStorage from "../idb";
|
||||
import cacheStorage from "../cacheStorage";
|
||||
import FileManager from "../filemanager";
|
||||
//import apiManager from "./apiManager";
|
||||
import apiManager from "./mtprotoworker";
|
||||
import apiManager from "./apiManager";
|
||||
import { deferredPromise, CancellablePromise } from "../polyfill";
|
||||
import appWebpManager from "../appManagers/appWebpManager";
|
||||
import { logger } from "../logger";
|
||||
import { InputFileLocation, FileLocation } from "../../types";
|
||||
|
||||
type Delayed = {
|
||||
offset: number,
|
||||
@ -15,19 +14,14 @@ type Delayed = {
|
||||
writeFileDeferred: CancellablePromise<unknown>
|
||||
};
|
||||
|
||||
type DownloadOptions = Partial<{
|
||||
|
||||
}>;
|
||||
|
||||
export class ApiFileManager {
|
||||
public cachedSavePromises: {
|
||||
public cachedDownloadPromises: {
|
||||
[fileName: string]: Promise<Blob>
|
||||
} = {};
|
||||
public cachedDownloadPromises: {
|
||||
[fileName: string]: any
|
||||
} = {};
|
||||
public cachedDownloads: {
|
||||
[fileName: string]: Blob
|
||||
} = {};
|
||||
|
||||
/* public indexedKeys: Set<string> = new Set();
|
||||
private keysLoaded = false; */
|
||||
|
||||
public downloadPulls: {
|
||||
[x: string]: Array<{
|
||||
@ -95,194 +89,26 @@ export class ApiFileManager {
|
||||
});
|
||||
}
|
||||
|
||||
public getFileName(location: any, options?: Partial<{
|
||||
stickerType: number
|
||||
}>) {
|
||||
switch(location._) {
|
||||
case 'inputDocumentFileLocation': {
|
||||
let fileName = (location.file_name as string || '').split('.');
|
||||
let ext = fileName[fileName.length - 1] || '';
|
||||
|
||||
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
ext += '.png'
|
||||
}
|
||||
|
||||
let thumbPart = location.thumb_size ? '_' + location.thumb_size : '';
|
||||
return (fileName[0] ? fileName[0] + '_' : '') + location.id + thumbPart + (ext ? '.' + ext : ext);
|
||||
}
|
||||
|
||||
default: {
|
||||
if(!location.volume_id && !location.file_reference) {
|
||||
this.log.trace('Empty location', location);
|
||||
}
|
||||
|
||||
let ext = 'jpg';
|
||||
if(options?.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
ext += '.png'
|
||||
}
|
||||
|
||||
if(location.volume_id) {
|
||||
return location.volume_id + '_' + location.local_id + '.' + ext;
|
||||
} else {
|
||||
return location.id + '_' + location.access_hash + '.' + ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getTempFileName(file: any) {
|
||||
const size = file.size || -1;
|
||||
const random = nextRandomInt(0xFFFFFFFF);
|
||||
return '_temp' + random + '_' + size;
|
||||
}
|
||||
|
||||
public getCachedFile(location: any) {
|
||||
if(!location) {
|
||||
return false;
|
||||
}
|
||||
const fileName = this.getFileName(location);
|
||||
|
||||
return this.cachedDownloads[fileName] || false;
|
||||
}
|
||||
|
||||
public getFileStorage() {
|
||||
return cacheStorage;
|
||||
}
|
||||
|
||||
/* public isFileExists(location: any) {
|
||||
var fileName = this.getFileName(location);
|
||||
|
||||
return this.cachedDownloads[fileName] || this.indexedKeys.has(fileName);
|
||||
//return this.cachedDownloads[fileName] || this.indexedKeys.has(fileName) ? Promise.resolve(true) : this.getFileStorage().isFileExists(fileName);
|
||||
} */
|
||||
|
||||
public saveSmallFile(location: any, bytes: Uint8Array) {
|
||||
var fileName = this.getFileName(location);
|
||||
|
||||
if(!this.cachedSavePromises[fileName]) {
|
||||
this.cachedSavePromises[fileName] = this.getFileStorage().saveFile(fileName, bytes).then((blob: any) => {
|
||||
return this.cachedDownloads[fileName] = blob;
|
||||
}, (error: any) => {
|
||||
delete this.cachedSavePromises[fileName];
|
||||
});
|
||||
}
|
||||
return this.cachedSavePromises[fileName];
|
||||
}
|
||||
|
||||
public downloadSmallFile(location: any, options: Partial<{
|
||||
mimeType: string,
|
||||
dcID: number,
|
||||
stickerType: number
|
||||
}> = {}): Promise<Blob> {
|
||||
public downloadFile(options: {
|
||||
dcID: number,
|
||||
location: InputFileLocation | FileLocation,
|
||||
size: number,
|
||||
mimeType?: string,
|
||||
toFileEntry?: any,
|
||||
limitPart?: number,
|
||||
stickerType?: number,
|
||||
processPart?: (bytes: Uint8Array, offset: number, queue: Delayed[]) => Promise<any>
|
||||
}): CancellablePromise<Blob> {
|
||||
if(!FileManager.isAvailable()) {
|
||||
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
|
||||
}
|
||||
|
||||
/* if(!this.keysLoaded) {
|
||||
this.getIndexedKeys();
|
||||
} */
|
||||
|
||||
//this.log('downloadSmallFile', location, options);
|
||||
|
||||
let processSticker = false;
|
||||
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
processSticker = true;
|
||||
options.mimeType = 'image/png';
|
||||
}
|
||||
|
||||
let dcID = options.dcID || location.dc_id;
|
||||
let mimeType = options.mimeType || 'image/jpeg';
|
||||
let fileName = this.getFileName(location, options);
|
||||
let cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
|
||||
|
||||
//this.log('downloadSmallFile!', location, options, fileName, cachedPromise);
|
||||
|
||||
if(cachedPromise) {
|
||||
return cachedPromise;
|
||||
}
|
||||
|
||||
let fileStorage = this.getFileStorage();
|
||||
|
||||
return this.cachedDownloadPromises[fileName] = fileStorage.getFile(fileName).then((blob) => {
|
||||
//throw '';
|
||||
//this.log('downloadSmallFile found photo by fileName:', fileName);
|
||||
return this.cachedDownloads[fileName] = blob;
|
||||
}).catch(() => {
|
||||
//this.log.warn('downloadSmallFile found no photo by fileName:', fileName);
|
||||
let downloadPromise = this.downloadRequest(dcID, () => {
|
||||
let inputLocation = location;
|
||||
if(!inputLocation._ || inputLocation._ == 'fileLocation') {
|
||||
inputLocation = Object.assign({}, location, {_: 'inputFileLocation'});
|
||||
}
|
||||
|
||||
let params = {
|
||||
flags: 0,
|
||||
location: inputLocation,
|
||||
offset: 0,
|
||||
limit: 1024 * 1024
|
||||
};
|
||||
|
||||
//this.log('next small promise', params);
|
||||
return apiManager.invokeApi('upload.getFile', params, {
|
||||
dcID: dcID,
|
||||
fileDownload: true,
|
||||
noErrorBox: true
|
||||
});
|
||||
}, dcID);
|
||||
|
||||
let processDownloaded = (bytes: Uint8Array) => {
|
||||
//this.log('processDownloaded', location, bytes);
|
||||
|
||||
if(processSticker) {
|
||||
return appWebpManager.convertToPng(bytes);
|
||||
}
|
||||
|
||||
return Promise.resolve(bytes);
|
||||
};
|
||||
|
||||
return fileStorage.getFileWriter(fileName, mimeType).then(fileWriter => {
|
||||
return downloadPromise.then((result: any) => {
|
||||
return processDownloaded(result.bytes).then((proccessedResult) => {
|
||||
return FileManager.write(fileWriter, proccessedResult).then(() => {
|
||||
return this.cachedDownloads[fileName] = fileWriter.finalize();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getDownloadedFile(location: any) {
|
||||
var fileStorage = this.getFileStorage();
|
||||
var fileName = typeof(location) !== 'string' ? this.getFileName(location) : location;
|
||||
|
||||
//console.log('getDownloadedFile', location, fileName);
|
||||
|
||||
return fileStorage.getFile(fileName);
|
||||
}
|
||||
|
||||
/* public getIndexedKeys() {
|
||||
this.keysLoaded = true;
|
||||
this.getFileStorage().getAllKeys().then(keys => {
|
||||
this.indexedKeys.clear();
|
||||
this.indexedKeys = new Set(keys);
|
||||
});
|
||||
} */
|
||||
|
||||
public downloadFile(dcID: number, location: any, size: number, options: Partial<{
|
||||
mimeType: string,
|
||||
toFileEntry: any,
|
||||
limitPart: number,
|
||||
stickerType: number,
|
||||
processPart: (bytes: Uint8Array, offset: number, queue: Delayed[]) => Promise<any>
|
||||
}> = {}): CancellablePromise<Blob> {
|
||||
if(!FileManager.isAvailable()) {
|
||||
return Promise.reject({type: 'BROWSER_BLOB_NOT_SUPPORTED'});
|
||||
}
|
||||
|
||||
/* if(!this.keysLoaded) {
|
||||
this.getIndexedKeys();
|
||||
} */
|
||||
let size = options.size ?? 0;
|
||||
let {dcID, location} = options;
|
||||
|
||||
let processSticker = false;
|
||||
if(options.stickerType == 1 && !appWebpManager.isSupported()) {
|
||||
@ -295,12 +121,12 @@ export class ApiFileManager {
|
||||
}
|
||||
|
||||
// this.log('Dload file', dcID, location, size)
|
||||
const fileName = this.getFileName(location, options);
|
||||
const fileName = getFileNameByLocation(location);
|
||||
const toFileEntry = options.toFileEntry || null;
|
||||
const cachedPromise = this.cachedSavePromises[fileName] || this.cachedDownloadPromises[fileName];
|
||||
const cachedPromise = this.cachedDownloadPromises[fileName];
|
||||
const fileStorage = this.getFileStorage();
|
||||
|
||||
//this.log('downloadFile', fileStorage.name, fileName, fileName.length, location, arguments);
|
||||
//this.log('downloadFile', fileName, fileName.length, location, arguments);
|
||||
|
||||
if(cachedPromise) {
|
||||
if(toFileEntry) {
|
||||
@ -317,9 +143,9 @@ export class ApiFileManager {
|
||||
this.log('downloadFile need to deleteFile, wrong size:', blob.size, size);
|
||||
|
||||
return this.deleteFile(fileName).then(() => {
|
||||
return this.downloadFile(dcID, location, size, options);
|
||||
return this.downloadFile(options);
|
||||
}).catch(() => {
|
||||
return this.downloadFile(dcID, location, size, options);
|
||||
return this.downloadFile(options);
|
||||
});
|
||||
} else {
|
||||
return blob;
|
||||
@ -346,7 +172,7 @@ export class ApiFileManager {
|
||||
};
|
||||
|
||||
fileStorage.getFile(fileName).then(async(blob: Blob) => {
|
||||
//this.log('is that i wanted');
|
||||
//this.log('maybe cached', fileName);
|
||||
//throw '';
|
||||
|
||||
if(blob.size < size) {
|
||||
@ -358,10 +184,10 @@ export class ApiFileManager {
|
||||
if(toFileEntry) {
|
||||
FileManager.copy(blob, toFileEntry).then(deferred.resolve, errorHandler);
|
||||
} else {
|
||||
deferred.resolve(this.cachedDownloads[fileName] = blob);
|
||||
deferred.resolve(blob);
|
||||
}
|
||||
}).catch(() => {
|
||||
//this.log('not i wanted');
|
||||
//this.log('not cached', fileName);
|
||||
//var fileWriterPromise = toFileEntry ? FileManager.getFileWriter(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
|
||||
const fileWriterPromise = toFileEntry ? Promise.resolve(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType);
|
||||
|
||||
@ -374,6 +200,10 @@ export class ApiFileManager {
|
||||
writeFileDeferred: CancellablePromise<unknown>;
|
||||
const maxRequests = options.processPart ? 5 : 5;
|
||||
|
||||
if(!size) {
|
||||
size = limit;
|
||||
}
|
||||
|
||||
if(fileWriter.length) {
|
||||
startOffset = fileWriter.length;
|
||||
|
||||
@ -381,7 +211,7 @@ export class ApiFileManager {
|
||||
if(toFileEntry) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.resolve(this.cachedDownloads[fileName] = fileWriter.finalize());
|
||||
deferred.resolve(fileWriter.finalize());
|
||||
}
|
||||
|
||||
return;
|
||||
@ -475,7 +305,7 @@ export class ApiFileManager {
|
||||
if(toFileEntry) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.resolve(this.cachedDownloads[fileName] = fileWriter.finalize());
|
||||
deferred.resolve(fileWriter.finalize());
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
@ -511,11 +341,7 @@ export class ApiFileManager {
|
||||
|
||||
public deleteFile(fileName: string) {
|
||||
//this.log('will delete file:', fileName);
|
||||
|
||||
delete this.cachedDownloadPromises[fileName];
|
||||
delete this.cachedDownloads[fileName];
|
||||
delete this.cachedSavePromises[fileName];
|
||||
|
||||
return this.getFileStorage().deleteFile(fileName);
|
||||
}
|
||||
|
||||
@ -638,4 +464,9 @@ export class ApiFileManager {
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiFileManager();
|
||||
const apiFileManager = new ApiFileManager();
|
||||
// @ts-ignore
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(self as any).apiFileManager = apiFileManager;
|
||||
}
|
||||
export default apiFileManager;
|
||||
|
@ -6,6 +6,7 @@ import apiManager from "./apiManager";
|
||||
import AppStorage from '../storage';
|
||||
import cryptoWorker from "../crypto/cryptoworker";
|
||||
import networkerFactory from "./networkerFactory";
|
||||
import apiFileManager from './apiFileManager';
|
||||
|
||||
const ctx = self as any as ServiceWorkerGlobalScope;
|
||||
|
||||
@ -85,14 +86,16 @@ networkerFactory.setUpdatesProcessor((obj, bool) => {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners[0].postMessage({update: {obj, bool}});
|
||||
listeners.forEach(listener => {
|
||||
listener.postMessage({update: {obj, bool}});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ctx.addEventListener('message', async(e) => {
|
||||
const taskID = e.data.taskID;
|
||||
|
||||
console.log('[SW] Got message:', taskID, e, e.data);
|
||||
//console.log('[SW] Got message:', taskID, e, e.data);
|
||||
|
||||
if(e.data.useLs) {
|
||||
AppStorage.finishTask(e.data.taskID, e.data.args);
|
||||
@ -107,6 +110,24 @@ ctx.addEventListener('message', async(e) => {
|
||||
respond(e.source, {taskID: taskID, result: result});
|
||||
});
|
||||
|
||||
case 'downloadFile': {
|
||||
/* // @ts-ignore
|
||||
return apiFileManager.downloadFile(...e.data.args); */
|
||||
|
||||
try {
|
||||
// @ts-ignore
|
||||
let result = apiFileManager[e.data.task].apply(apiFileManager, e.data.args);
|
||||
|
||||
if(result instanceof Promise) {
|
||||
result = await result;
|
||||
}
|
||||
|
||||
respond(e.source, {taskID: taskID, result: result});
|
||||
} catch(err) {
|
||||
respond(e.source, {taskID: taskID, error: err});
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
try {
|
||||
// @ts-ignore
|
||||
@ -151,3 +172,104 @@ ctx.addEventListener('activate', (event) => {
|
||||
|
||||
event.waitUntil(ctx.clients.claim());
|
||||
});
|
||||
|
||||
function timeout(delay: number): Promise<Response> {
|
||||
return new Promise(((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(new Response('', {
|
||||
status: 408,
|
||||
statusText: 'Request timed out.',
|
||||
}));
|
||||
}, delay);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch requests
|
||||
*/
|
||||
ctx.addEventListener('fetch', (event: FetchEvent): void => {
|
||||
const [, url, scope, fileName] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || [];
|
||||
|
||||
//console.log('[SW] fetch:', event, event.request, url, scope, fileName);
|
||||
|
||||
switch(scope) {
|
||||
case 'thumb':
|
||||
case 'document':
|
||||
case 'photo': {
|
||||
const info = JSON.parse(decodeURIComponent(fileName));
|
||||
|
||||
//console.log('[SW] fetch cachedDownloadPromises:', info/* apiFileManager.cachedDownloadPromises, apiFileManager.cachedDownloadPromises.hasOwnProperty(fileName) */);
|
||||
|
||||
const promise = apiFileManager.downloadFile(info).then(b => new Response(b));
|
||||
event.respondWith(promise);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'upload': {
|
||||
if(event.request.method == 'POST') {
|
||||
event.respondWith(event.request.blob().then(blob => {
|
||||
return apiFileManager.uploadFile(blob).then(v => new Response(JSON.stringify(v), {headers: {'Content-Type': 'application/json'}}));
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* default: {
|
||||
|
||||
break;
|
||||
}
|
||||
case 'documents':
|
||||
case 'photos':
|
||||
case 'profiles':
|
||||
// direct download
|
||||
if (event.request.method === 'POST') {
|
||||
event.respondWith(// download(url, 'unknown file.txt', getFilePartRequest));
|
||||
event.request.text()
|
||||
.then((text) => {
|
||||
const [, filename] = text.split('=');
|
||||
return download(url, filename ? filename.toString() : 'unknown file', getFilePartRequest);
|
||||
}),
|
||||
);
|
||||
|
||||
// inline
|
||||
} else {
|
||||
event.respondWith(
|
||||
ctx.cache.match(url).then((cached) => {
|
||||
if (cached) return cached;
|
||||
|
||||
return Promise.race([
|
||||
timeout(45 * 1000), // safari fix
|
||||
new Promise<Response>((resolve) => {
|
||||
fetchRequest(url, resolve, getFilePartRequest, ctx.cache, fileProgress);
|
||||
}),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'stream': {
|
||||
const [offset, end] = parseRange(event.request.headers.get('Range') || '');
|
||||
|
||||
log('stream', url, offset, end);
|
||||
|
||||
event.respondWith(new Promise((resolve) => {
|
||||
fetchStreamRequest(url, offset, end, resolve, getFilePartRequest);
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'stripped':
|
||||
case 'cached': {
|
||||
const bytes = getThumb(url) || null;
|
||||
event.respondWith(new Response(bytes, { headers: { 'Content-Type': 'image/jpg' } }));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (url && url.endsWith('.tgs')) event.respondWith(fetchTGS(url));
|
||||
else event.respondWith(fetch(event.request.url)); */
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import {dT, isObject, $rootScope} from '../utils';
|
||||
import AppStorage from '../storage';
|
||||
import CryptoWorkerMethods from '../crypto/crypto_methods';
|
||||
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
|
||||
import { InputFileLocation, FileLocation } from '../../types';
|
||||
|
||||
type Task = {
|
||||
taskID: number,
|
||||
@ -45,14 +46,14 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
this.releasePending();
|
||||
});
|
||||
|
||||
navigator.serviceWorker.oncontrollerchange = () => {
|
||||
console.error('oncontrollerchange');
|
||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||
console.warn(dT(), 'ApiManagerProxy controllerchange');
|
||||
this.releasePending();
|
||||
|
||||
navigator.serviceWorker.controller.addEventListener('error', (e) => {
|
||||
console.error('controller error:', e);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Message resolver
|
||||
@ -75,41 +76,6 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
|
||||
}
|
||||
});
|
||||
|
||||
/* if(window.Worker) {
|
||||
import('./mtproto_service.js').then((worker: any) => {
|
||||
var tmpWorker = new worker.default();
|
||||
tmpWorker.onmessage = (e: any) => {
|
||||
if(!this.webWorker) {
|
||||
this.webWorker = tmpWorker;
|
||||
console.info(dT(), 'ApiManagerProxy set webWorker');
|
||||
this.releasePending();
|
||||
}
|
||||
|
||||
if(!isObject(e.data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(e.data.useLs) {
|
||||
// @ts-ignore
|
||||
AppStorage[e.data.task](...e.data.args).then(res => {
|
||||
(this.webWorker as Worker).postMessage({useLs: true, taskID: e.data.taskID, args: res});
|
||||
});
|
||||
} else if(e.data.update) {
|
||||
if(this.updatesProcessor) {
|
||||
this.updatesProcessor(e.data.update.obj, e.data.update.bool);
|
||||
}
|
||||
} else {
|
||||
this.finalizeTask(e.data.taskID, e.data.result, e.data.error);
|
||||
}
|
||||
};
|
||||
|
||||
tmpWorker.onerror = (error: any) => {
|
||||
console.error('ApiManagerProxy error', error);
|
||||
this.webWorker = false;
|
||||
};
|
||||
});
|
||||
} */
|
||||
}
|
||||
|
||||
private finalizeTask(taskID: number, result: any, error: any) {
|
||||
@ -127,13 +93,12 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.awaiting[this.taskID] = {resolve, reject, taskName: task};
|
||||
|
||||
let params = {
|
||||
const params = {
|
||||
task,
|
||||
taskID: this.taskID,
|
||||
args
|
||||
};
|
||||
|
||||
//(this.webWorker as Worker).postMessage(params);
|
||||
this.pending.push(params);
|
||||
this.releasePending();
|
||||
|
||||
@ -194,6 +159,15 @@ class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
public logOut(): Promise<void> {
|
||||
return this.performTaskWorker('logOut');
|
||||
}
|
||||
|
||||
public downloadFile(dcID: number, location: InputFileLocation | FileLocation, size: number = 0, options: Partial<{
|
||||
mimeType: string,
|
||||
toFileEntry: any,
|
||||
limitPart: number,
|
||||
stickerType: number
|
||||
}> = {}): Promise<Blob> {
|
||||
return this.performTaskWorker('downloadFile', dcID, location, size, options);
|
||||
}
|
||||
}
|
||||
|
||||
const apiManagerProxy = new ApiManagerProxy();
|
||||
|
@ -4,6 +4,9 @@
|
||||
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
|
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import { InputFileLocation, FileLocation } from "../types";
|
||||
|
||||
var _logTimer = Date.now();
|
||||
export function dT () {
|
||||
return '[' + ((Date.now() - _logTimer) / 1000).toFixed(3) + ']';
|
||||
@ -519,3 +522,17 @@ export function getEmojiToneIndex(input: string) {
|
||||
let match = input.match(/[\uDFFB-\uDFFF]/);
|
||||
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
|
||||
}
|
||||
|
||||
export function getFileURL(type: 'photo' | 'thumb' | 'document', options: {
|
||||
dcID: number,
|
||||
location: InputFileLocation | FileLocation,
|
||||
size?: number,
|
||||
mimeType?: string
|
||||
}) {
|
||||
//console.log('getFileURL', location);
|
||||
//const perf = performance.now();
|
||||
const encoded = encodeURIComponent(JSON.stringify(options));
|
||||
//console.log('getFileURL encode:', performance.now() - perf, encoded);
|
||||
|
||||
return '/' + type + '/' + encoded;
|
||||
}
|
||||
|
46
src/types.d.ts
vendored
46
src/types.d.ts
vendored
@ -41,7 +41,7 @@ export type MTPhotoSize = {
|
||||
h?: number,
|
||||
size?: number,
|
||||
type?: string, // i, m, x, y, w by asc
|
||||
location?: any,
|
||||
location?: FileLocation,
|
||||
bytes?: Uint8Array // if type == 'i'
|
||||
};
|
||||
|
||||
@ -87,4 +87,46 @@ export type AccountPassword = {
|
||||
srp_B?: Uint8Array,
|
||||
srp_id?: string,
|
||||
secure_random: Uint8Array,
|
||||
};
|
||||
};
|
||||
|
||||
export type FileLocation = {
|
||||
_: 'fileLocationToBeDeprecated',
|
||||
volume_id: string,
|
||||
local_id: number
|
||||
};
|
||||
|
||||
export type inputFileLocation = {
|
||||
_: 'inputFileLocation',
|
||||
volume_id: string,
|
||||
local_id: number,
|
||||
secret: string,
|
||||
file_reference: Uint8Array | number[]
|
||||
};
|
||||
|
||||
export type inputDocumentFileLocation = {
|
||||
_: 'inputDocumentFileLocation',
|
||||
id: string,
|
||||
access_hash: string,
|
||||
file_reference: Uint8Array | number[],
|
||||
thumb_size: string
|
||||
};
|
||||
|
||||
export type inputPhotoFileLocation = Omit<inputDocumentFileLocation, '_'> & {_: 'inputPhotoFileLocation'};
|
||||
|
||||
export type inputPeerPhotoFileLocation = {
|
||||
_: 'inputPeerPhotoFileLocation',
|
||||
flags: number,
|
||||
big?: true,
|
||||
peer: any,
|
||||
volume_id: string,
|
||||
local_id: number
|
||||
};
|
||||
|
||||
export type inputStickerSetThumb = {
|
||||
_: 'inputStickerSetThumb',
|
||||
stickerset: any,
|
||||
volume_id: string,
|
||||
local_id: number
|
||||
};
|
||||
|
||||
export type InputFileLocation = inputFileLocation | inputDocumentFileLocation | inputPhotoFileLocation | inputPeerPhotoFileLocation | inputStickerSetThumb;
|
Loading…
x
Reference in New Issue
Block a user