tdweb: multiple fixes

GitOrigin-RevId: 6ccde11c8f8f5b380dead450e7f68cd0639a7490
This commit is contained in:
Arseny Smirnov 2019-04-29 18:22:46 +03:00
parent ec849f86b2
commit 260d351f07
3 changed files with 308 additions and 119 deletions

View File

@ -70,6 +70,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.4.3", "@babel/runtime": "^7.4.3",
"broadcast-channel": "^2.1.12",
"localforage": "^1.7.3", "localforage": "^1.7.3",
"uuid": "^3.3.2" "uuid": "^3.3.2"
}, },

View File

@ -1,6 +1,7 @@
import MyWorker from './worker.js'; import MyWorker from './worker.js';
import localforage from 'localforage'; import localforage from 'localforage';
import './third_party/broadcastchannel.js'; //import './third_party/broadcastchannel.js';
import BroadcastChannel from 'broadcast-channel';
import uuid4 from 'uuid/v4'; import uuid4 from 'uuid/v4';
import log from './logger.js'; import log from './logger.js';
@ -22,8 +23,6 @@ const sleep = ms => new Promise(res => setTimeout(res, ms));
* 3. Added the possibility to use blobs as input files via the constructor <code>inputFileBlob data:<JavaScript blob> = InputFile;</code>.<br> * 3. Added the possibility to use blobs as input files via the constructor <code>inputFileBlob data:<JavaScript blob> = InputFile;</code>.<br>
* 4. The class <code>filePart</code> contains data as a JavaScript blob instead of a base64-encoded string.<br> * 4. The class <code>filePart</code> contains data as a JavaScript blob instead of a base64-encoded string.<br>
* 5. The methods <code>getStorageStatistics</code>, <code>getStorageStatisticsFast</code>, <code>optimizeStorage</code>, <code>addProxy</code> are not supported.<br> * 5. The methods <code>getStorageStatistics</code>, <code>getStorageStatisticsFast</code>, <code>optimizeStorage</code>, <code>addProxy</code> are not supported.<br>
* 6. Added the field <code>idb_key</code> to <code>file</code> object, which contains the IndexedDB key in which the file content is stored.<br>
* This field is non-empty only for fully downloaded files. IndexedDB database name is chosen during TdClient creation via options.instanceName parameter.<br>
* <br> * <br>
*/ */
class TdClient { class TdClient {
@ -47,9 +46,8 @@ class TdClient {
constructor(options) { constructor(options) {
log.setVerbosity(options.jsLogVerbosityLevel); log.setVerbosity(options.jsLogVerbosityLevel);
this.worker = new MyWorker(); this.worker = new MyWorker();
var self = this;
this.worker.onmessage = e => { this.worker.onmessage = e => {
self.onResponse(e.data); this.onResponse(e.data);
}; };
this.query_id = 0; this.query_id = 0;
this.query_callbacks = new Map(); this.query_callbacks = new Map();
@ -75,10 +73,19 @@ class TdClient {
* @returns {Promise} Promise object represents the result of the query. * @returns {Promise} Promise object represents the result of the query.
*/ */
send(query) { send(query) {
return this.doSend(query, true);
}
/** @private */
sendInternal(query) {
return this.doSend(query, false);
}
/** @private */
doSend(query, isExternal) {
this.query_id++; this.query_id++;
if (query['@extra']) { if (query['@extra']) {
query['@extra'] = { query['@extra'] = {
'@old_extra': JSON.parse(JSON.stringify(query.extra)), '@old_extra': JSON.parse(JSON.stringify(query['@extra'])),
query_id: this.query_id query_id: this.query_id
}; };
} else { } else {
@ -91,16 +98,20 @@ class TdClient {
} }
log.debug('send to worker: ', query); log.debug('send to worker: ', query);
let res = new Promise((resolve, reject) => { const res = new Promise((resolve, reject) => {
this.query_callbacks.set(this.query_id, [resolve, reject]); this.query_callbacks.set(this.query_id, [resolve, reject]);
}); });
this.externalPostMessage(query); if (isExternal) {
this.externalPostMessage(query);
} else {
this.worker.postMessage(query);
}
return res; return res;
} }
/** @private */ /** @private */
externalPostMessage(query) { externalPostMessage(query) {
let unsupportedMethods = [ const unsupportedMethods = [
'getStorageStatistics', 'getStorageStatistics',
'getStorageStatisticsFast', 'getStorageStatisticsFast',
'optimizeStorage', 'optimizeStorage',
@ -117,16 +128,39 @@ class TdClient {
}); });
return; return;
} }
if (query['@type'] === 'readFile') { if (query['@type'] === 'readFile' || query['@type'] == 'readFilePart') {
this.readFile(query); this.readFile(query);
return; return;
} }
if (query['@type'] === 'deleteFile') {
this.deleteFile(query);
return;
}
this.worker.postMessage(query); this.worker.postMessage(query);
} }
/** @private */ /** @private */
async readFile(query) { async readFile(query) {
let response = await this.fileManager.readFile(query); const response = await this.fileManager.readFile(query);
this.onResponse(response);
}
/** @private */
async deleteFile(query) {
const response = this.fileManager.deleteFile(query);
try {
if (response.idb_key) {
await this.sendInternal({
'@type': 'deleteIdbKey',
idb_key: response.idb_key
});
delete response.idb_key;
}
await this.sendInternal({
'@type': 'deleteFile',
file_id: query.file_id
});
} catch (e) {}
this.onResponse(response); this.onResponse(response);
} }
@ -148,8 +182,8 @@ class TdClient {
response = this.prepareResponse(response); response = this.prepareResponse(response);
if ('@extra' in response) { if ('@extra' in response) {
var query_id = response['@extra'].query_id; const query_id = response['@extra'].query_id;
var [resolve, reject] = this.query_callbacks.get(query_id); const [resolve, reject] = this.query_callbacks.get(query_id);
this.query_callbacks.delete(query_id); this.query_callbacks.delete(query_id);
if ('@old_extra' in response['@extra']) { if ('@old_extra' in response['@extra']) {
response['@extra'] = response['@extra']['@old_extra']; response['@extra'] = response['@extra']['@old_extra'];
@ -188,10 +222,18 @@ class TdClient {
/** @private */ /** @private */
prepareResponse(response) { prepareResponse(response) {
if (response['@type'] === 'file') { if (response['@type'] === 'file') {
if (false && Math.random() < 0.1) {
(async () => {
log.warn('DELETE FILE', response.id);
try {
await this.send({ '@type': 'deleteFile', file_id: response.id });
} catch (e) {}
})();
}
return this.prepareFile(response); return this.prepareFile(response);
} }
for (var key in response) { for (const key in response) {
let field = response[key]; const field = response[key];
if (field && typeof field === 'object') { if (field && typeof field === 'object') {
response[key] = this.prepareResponse(field); response[key] = this.prepareResponse(field);
} }
@ -201,7 +243,8 @@ class TdClient {
/** @private */ /** @private */
onBroadcastMessage(e) { onBroadcastMessage(e) {
var message = e.data; //const message = e.data;
const message = e;
if (message.uid === this.uid) { if (message.uid === this.uid) {
log.info('ignore self broadcast message: ', message); log.info('ignore self broadcast message: ', message);
return; return;
@ -233,8 +276,8 @@ class TdClient {
/** @private */ /** @private */
postState() { postState() {
let state = { const state = {
id: this.uid, uid: this.uid,
state: this.state, state: this.state,
timestamp: this.timestamp, timestamp: this.timestamp,
isBackground: this.isBackground isBackground: this.isBackground
@ -272,7 +315,7 @@ class TdClient {
} }
this.wantSendStart = false; this.wantSendStart = false;
this.state = 'active'; this.state = 'active';
let query = { '@type': 'start' }; const query = { '@type': 'start' };
log.info('send to worker: ', query); log.info('send to worker: ', query);
this.worker.postMessage(query); this.worker.postMessage(query);
} }
@ -306,7 +349,7 @@ class TdClient {
return; return;
} }
let query = { '@type': 'close' }; const query = { '@type': 'close' };
log.info('send to worker: ', query); log.info('send to worker: ', query);
this.worker.postMessage(query); this.worker.postMessage(query);
@ -323,19 +366,20 @@ class TdClient {
this.waitSet = new Set(); this.waitSet = new Set();
log.info('close other clients'); log.info('close other clients');
this.channel = new BroadcastChannel(options.instanceName); this.channel = new BroadcastChannel(options.instanceName, {
webWorkerSupport: false
});
this.postState(); this.postState();
var self = this;
this.channel.onmessage = message => { this.channel.onmessage = message => {
self.onBroadcastMessage(message); this.onBroadcastMessage(message);
}; };
await sleep(300); await sleep(300);
if (this.waitSet.size !== 0) { if (this.waitSet.size !== 0) {
await new Promise(resolve => { await new Promise(resolve => {
self.onWaitSetEmpty = resolve; this.onWaitSetEmpty = resolve;
}); });
} }
this.sendStart(); this.sendStart();
@ -348,6 +392,43 @@ class TdClient {
} }
} }
/** @private */
class ListNode {
constructor(value) {
this.value = value;
this.clear();
}
erase() {
this.prev.connect(this.next);
this.clear();
}
clear() {
this.prev = this;
this.next = this;
}
connect(other) {
this.next = other;
other.prev = this;
}
onUsed(other) {
other.usedAt = Date.now();
other.clear();
other.connect(this.next);
log.debug('LRU: used file_id: ', other.value);
this.connect(other);
}
getLru() {
if (this === this.next) {
throw new Error('popLru from empty list');
}
return this.prev;
}
}
/** @private */ /** @private */
class FileManager { class FileManager {
constructor(instanceName) { constructor(instanceName) {
@ -355,12 +436,13 @@ class FileManager {
this.cache = new Map(); this.cache = new Map();
this.pending = []; this.pending = [];
this.transaction_id = 0; this.transaction_id = 0;
this.totalSize = 0;
this.lru = new ListNode(-1);
} }
init() { init() {
let self = this;
this.idb = new Promise((resolve, reject) => { this.idb = new Promise((resolve, reject) => {
const request = window.indexedDB.open(self.instanceName); const request = window.indexedDB.open(this.instanceName);
request.onsuccess = () => resolve(request.result); request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error); request.onerror = () => reject(request.error);
}); });
@ -370,32 +452,80 @@ class FileManager {
this.isInited = true; this.isInited = true;
} }
unload(info) {
if (info.arr) {
log.debug(
'LRU: delete file_id: ',
info.node.value,
' with arr.length: ',
info.arr.length
);
this.totalSize -= info.arr.length;
delete info.arr;
}
if (info.node) {
info.node.erase();
delete info.node;
}
}
registerFile(file) { registerFile(file) {
const cached_info = this.cache.get(file.id);
if (cached_info && !file.idb_key) {
delete cached_info.idb_key;
}
if (file.idb_key || file.arr) { if (file.idb_key || file.arr) {
file.is_downloading_completed = true; file.is_downloading_completed = true;
var info = {}; let info = {};
let cached_info = this.cache.get(file.id); if (cached_info) {
if (cached_info !== undefined) {
info = cached_info; info = cached_info;
} else { } else {
this.cache.set(file.id, info); this.cache.set(file.id, info);
} }
if (file.idb_key) { if (file.idb_key) {
info.idb_key = file.idb_key; info.idb_key = file.idb_key;
delete file.idb_key;
} }
if (file.arr) { if (file.arr) {
const now = Date.now();
while (this.totalSize > 10000000) {
const node = this.lru.getLru();
// immunity for 5 seconds
if (node.usedAt + 5 * 1000 > now) {
break;
}
const lru_info = this.cache.get(node.value);
this.unload(lru_info);
}
if (info.arr) {
log.warn('Got file.arr at least twice for the same file');
this.totalSize -= info.arr.length;
}
info.arr = file.arr; info.arr = file.arr;
this.totalSize += info.arr.length;
if (!info.node) {
log.debug(
'LRU: create file_id: ',
file.id,
' with arr.length: ',
info.arr.length
);
info.node = new ListNode(file.id);
}
this.lru.onUsed(info.node);
log.info('Total file.arr size: ', this.totalSize);
} }
} }
return file; return file;
} }
async flushLoad() { async flushLoad() {
let pending = this.pending; const pending = this.pending;
this.pending = []; this.pending = [];
let idb = await this.idb; const idb = await this.idb;
let transaction_id = this.transaction_id++; const transaction_id = this.transaction_id++;
let read = idb const read = idb
.transaction(['keyvaluepairs'], 'readonly') .transaction(['keyvaluepairs'], 'readonly')
.objectStore('keyvaluepairs'); .objectStore('keyvaluepairs');
log.debug('Load group of files from idb', pending.length); log.debug('Load group of files from idb', pending.length);
@ -415,9 +545,8 @@ class FileManager {
load(key, resolve, reject) { load(key, resolve, reject) {
if (this.pending.length === 0) { if (this.pending.length === 0) {
let self = this;
setTimeout(() => { setTimeout(() => {
self.flushLoad(); this.flushLoad();
}, 1); }, 1);
} }
this.pending.push({ key: key, resolve: resolve, reject: reject }); this.pending.push({ key: key, resolve: resolve, reject: reject });
@ -427,26 +556,36 @@ class FileManager {
if (info.arr) { if (info.arr) {
return { data: new Blob([info.arr]), transaction_id: -1 }; return { data: new Blob([info.arr]), transaction_id: -1 };
} }
let idb_key = info.idb_key; const idb_key = info.idb_key;
let self = this;
//return this.store.getItem(idb_key); //return this.store.getItem(idb_key);
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
self.load(idb_key, resolve, reject); this.load(idb_key, resolve, reject);
}); });
} }
doDelete(info) {
this.unload(info);
return info.idb_key;
}
async readFile(query) { async readFile(query) {
try { try {
if (query.offset || query.size) {
throw new Error('readFilePart: offset and size are not supported yet');
}
if (!this.isInited) { if (!this.isInited) {
throw new Error('FileManager is not inited'); throw new Error('FileManager is not inited');
} }
let info = this.cache.get(query.file_id); const info = this.cache.get(query.file_id);
if (!info) { if (!info) {
throw new Error('File is not loaded'); throw new Error('File is not loaded');
} }
let response = await this.doLoad(info); if (info.node) {
this.lru.onUsed(info.node);
}
const response = await this.doLoad(info);
return { return {
'@type': 'blob', '@type': 'filePart',
'@extra': query['@extra'], '@extra': query['@extra'],
data: response.data, data: response.data,
transaction_id: response.transaction_id transaction_id: response.transaction_id
@ -460,6 +599,27 @@ class FileManager {
}; };
} }
} }
deleteFile(query) {
const res = {
'@type': 'ok',
'@extra': query['@extra']
};
try {
if (!this.isInited) {
throw new Error('FileManager is not inited');
}
const info = this.cache.get(query.file_id);
if (!info) {
throw new Error('File is not loaded');
}
const idb_key = this.doDelete(info);
if (idb_key) {
res.idb_key = idb_key;
}
} catch (e) {}
return res;
}
} }
export default TdClient; export default TdClient;

View File

@ -14,12 +14,12 @@ const localForageDrivers = [
async function initLocalForage() { async function initLocalForage() {
// Implement the driver here. // Implement the driver here.
var memoryDriver = { const memoryDriver = {
_driver: 'memoryDriver', _driver: 'memoryDriver',
_initStorage: function(options) { _initStorage: function(options) {
var dbInfo = {}; const dbInfo = {};
if (options) { if (options) {
for (var i in options) { for (const i in options) {
dbInfo[i] = options[i]; dbInfo[i] = options[i];
} }
} }
@ -30,7 +30,7 @@ async function initLocalForage() {
this._map.clear(); this._map.clear();
}, },
getItem: async function(key) { getItem: async function(key) {
let value = this._map.get(key); const value = this._map.get(key);
console.log('getItem', this._map, key, value); console.log('getItem', this._map, key, value);
return value; return value;
}, },
@ -50,7 +50,7 @@ async function initLocalForage() {
this._map.delete(key); this._map.delete(key);
}, },
setItem: async function(key, value) { setItem: async function(key, value) {
let originalValue = this._map.get(key); const originalValue = this._map.get(key);
console.log('setItem', this._map, key, value); console.log('setItem', this._map, key, value);
this._map.set(key, value); this._map.set(key, value);
return originalValue; return originalValue;
@ -63,16 +63,16 @@ async function initLocalForage() {
async function loadTdlibWasm(onFS) { async function loadTdlibWasm(onFS) {
console.log('loadTdlibWasm'); console.log('loadTdlibWasm');
let Module = await import('./prebuilt/release/td_wasm.js'); const Module = await import('./prebuilt/release/td_wasm.js');
log.info('got td_wasm.js'); log.info('got td_wasm.js');
let td_wasm = td_wasm_release; const td_wasm = td_wasm_release;
let module = Module.default({ const module = Module.default({
onRuntimeInitialized: () => { onRuntimeInitialized: () => {
log.info('runtime intialized'); log.info('runtime intialized');
}, },
instantiateWasm: (imports, successCallback) => { instantiateWasm: (imports, successCallback) => {
log.info('start instantiateWasm'); log.info('start instantiateWasm');
let next = instance => { const next = instance => {
log.info('finish instantiateWasm'); log.info('finish instantiateWasm');
successCallback(instance); successCallback(instance);
}; };
@ -83,7 +83,7 @@ async function loadTdlibWasm(onFS) {
}); });
log.info('Got module', module); log.info('Got module', module);
onFS(module.FS); onFS(module.FS);
let TdModule = new Promise((resolve, reject) => const TdModule = new Promise((resolve, reject) =>
module.then(m => { module.then(m => {
delete m.then; delete m.then;
resolve(m); resolve(m);
@ -95,11 +95,11 @@ async function loadTdlibWasm(onFS) {
async function loadTdlibAsmjs(onFS) { async function loadTdlibAsmjs(onFS) {
console.log('loadTdlibAsmjs'); console.log('loadTdlibAsmjs');
let Module = await import('./prebuilt/release/td_asmjs.js'); const Module = await import('./prebuilt/release/td_asmjs.js');
console.log('got td_asm.js'); console.log('got td_asm.js');
let fromFile = 'td_asmjs.js.mem'; const fromFile = 'td_asmjs.js.mem';
let toFile = td_asmjs_mem_release; const toFile = td_asmjs_mem_release;
let module = Module({ const module = Module({
onRuntimeInitialized: () => { onRuntimeInitialized: () => {
console.log('runtime intialized'); console.log('runtime intialized');
}, },
@ -112,7 +112,7 @@ async function loadTdlibAsmjs(onFS) {
ENVIROMENT: 'WORKER' ENVIROMENT: 'WORKER'
}); });
onFS(module.FS); onFS(module.FS);
let TdModule = new Promise((resolve, reject) => const TdModule = new Promise((resolve, reject) =>
module.then(m => { module.then(m => {
delete m.then; delete m.then;
resolve(m); resolve(m);
@ -164,7 +164,7 @@ class OutboundFileSystem {
FS.mkdir(root); FS.mkdir(root);
} }
blobToPath(blob, name) { blobToPath(blob, name) {
var dir = this.root + '/' + this.nextFileId; const dir = this.root + '/' + this.nextFileId;
if (!name) { if (!name) {
name = 'blob'; name = 'blob';
} }
@ -177,7 +177,7 @@ class OutboundFileSystem {
}, },
dir dir
); );
let path = dir + '/' + name; const path = dir + '/' + name;
this.files.add(path); this.files.add(path);
return path; return path;
} }
@ -192,9 +192,9 @@ class OutboundFileSystem {
class InboundFileSystem { class InboundFileSystem {
static async create(dbName, root, FS_promise) { static async create(dbName, root, FS_promise) {
let start = performance.now(); const start = performance.now();
try { try {
let ifs = new InboundFileSystem(); const ifs = new InboundFileSystem();
ifs.root = root; ifs.root = root;
ifs.store = localforage.createInstance({ ifs.store = localforage.createInstance({
@ -204,10 +204,10 @@ class InboundFileSystem {
ifs.load_pids(); ifs.load_pids();
let FS = await FS_promise; const FS = await FS_promise;
ifs.FS = FS; ifs.FS = FS;
ifs.FS.mkdir(root); ifs.FS.mkdir(root);
let create_time = (performance.now() - start) / 1000; const create_time = (performance.now() - start) / 1000;
log.debug('InboundFileSystem::create ' + create_time); log.debug('InboundFileSystem::create ' + create_time);
return ifs; return ifs;
} catch (e) { } catch (e) {
@ -216,10 +216,10 @@ class InboundFileSystem {
} }
async load_pids() { async load_pids() {
let keys_start = performance.now(); const keys_start = performance.now();
log.debug('InboundFileSystem::create::keys start'); log.debug('InboundFileSystem::create::keys start');
let keys = await this.store.keys(); const keys = await this.store.keys();
let keys_time = (performance.now() - keys_start) / 1000; const keys_time = (performance.now() - keys_start) / 1000;
log.debug( log.debug(
'InboundFileSystem::create::keys ' + keys_time + ' ' + keys.length 'InboundFileSystem::create::keys ' + keys_time + ' ' + keys.length
); );
@ -251,15 +251,24 @@ class InboundFileSystem {
log.error('Failed persist ' + path + ' ', e); log.error('Failed persist ' + path + ' ', e);
} }
} }
async unlink(pid) {
log.debug('Unlink ' + pid);
try {
this.forget(pid);
await this.store.removeItem(pid);
} catch (e) {
log.error('Failed unlink ' + pid + ' ', e);
}
}
} }
class DbFileSystem { class DbFileSystem {
static async create(root, FS_promise, readOnly = false) { static async create(root, FS_promise, readOnly = false) {
let start = performance.now(); const start = performance.now();
try { try {
let dbfs = new DbFileSystem(); const dbfs = new DbFileSystem();
dbfs.root = root; dbfs.root = root;
let FS = await FS_promise; const FS = await FS_promise;
dbfs.FS = FS; dbfs.FS = FS;
dbfs.syncfs_total_time = 0; dbfs.syncfs_total_time = 0;
dbfs.readOnly = readOnly; dbfs.readOnly = readOnly;
@ -272,9 +281,9 @@ class DbFileSystem {
}); });
}); });
let rmrf = path => { const rmrf = path => {
log.debug('rmrf ', path); log.debug('rmrf ', path);
var info; let info;
try { try {
info = FS.lookupPath(path); info = FS.lookupPath(path);
} catch (e) { } catch (e) {
@ -282,7 +291,7 @@ class DbFileSystem {
} }
log.debug('rmrf ', path, info); log.debug('rmrf ', path, info);
if (info.node.isFolder) { if (info.node.isFolder) {
for (var key in info.node.contents) { for (const key in info.node.contents) {
rmrf(info.path + '/' + info.node.contents[key].name); rmrf(info.path + '/' + info.node.contents[key].name);
} }
log.debug('rmdir ', path); log.debug('rmdir ', path);
@ -292,19 +301,19 @@ class DbFileSystem {
FS.unlink(path); FS.unlink(path);
} }
}; };
//var dirs = ['thumbnails', 'profile_photos', 'secret', 'stickers', 'temp', 'wallpapers', 'secret_thumbnails', 'passport']; //const dirs = ['thumbnails', 'profile_photos', 'secret', 'stickers', 'temp', 'wallpapers', 'secret_thumbnails', 'passport'];
var dirs = []; const dirs = [];
let root_dir = FS.lookupPath(root); const root_dir = FS.lookupPath(root);
for (var key in root_dir.node.contents) { for (const key in root_dir.node.contents) {
let value = root_dir.node.contents[key]; const value = root_dir.node.contents[key];
log.debug('node ', key, value); log.debug('node ', key, value);
if (!value.isFolder) { if (!value.isFolder) {
continue; continue;
} }
dirs.push(root_dir.path + '/' + value.name); dirs.push(root_dir.path + '/' + value.name);
} }
for (let i in dirs) { for (const i in dirs) {
let dir = dirs[i]; const dir = dirs[i];
rmrf(dir); rmrf(dir);
//FS.mkdir(dir); //FS.mkdir(dir);
//FS.mount(FS.filesystems.MEMFS, {}, dir); //FS.mount(FS.filesystems.MEMFS, {}, dir);
@ -312,7 +321,7 @@ class DbFileSystem {
dbfs.syncfsInterval = setInterval(() => { dbfs.syncfsInterval = setInterval(() => {
dbfs.sync(); dbfs.sync();
}, 5000); }, 5000);
let create_time = (performance.now() - start) / 1000; const create_time = (performance.now() - start) / 1000;
log.debug('DbFileSystem::create ' + create_time); log.debug('DbFileSystem::create ' + create_time);
return dbfs; return dbfs;
} catch (e) { } catch (e) {
@ -323,10 +332,10 @@ class DbFileSystem {
if (this.readOnly) { if (this.readOnly) {
return; return;
} }
let start = performance.now(); const start = performance.now();
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
this.FS.syncfs(false, () => { this.FS.syncfs(false, () => {
let syncfs_time = (performance.now() - start) / 1000; const syncfs_time = (performance.now() - start) / 1000;
this.syncfs_total_time += syncfs_time; this.syncfs_total_time += syncfs_time;
log.debug('SYNC: ' + syncfs_time); log.debug('SYNC: ' + syncfs_time);
log.debug('SYNC total: ' + this.syncfs_total_time); log.debug('SYNC total: ' + this.syncfs_total_time);
@ -344,7 +353,7 @@ class DbFileSystem {
return; return;
} }
this.FS.unmount(this.root); this.FS.unmount(this.root);
var req = indexedDB.deleteDatabase(this.root); const req = indexedDB.deleteDatabase(this.root);
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
req.onsuccess = function(e) { req.onsuccess = function(e) {
log.info('SUCCESS'); log.info('SUCCESS');
@ -364,32 +373,32 @@ class DbFileSystem {
class TdFileSystem { class TdFileSystem {
static async init_fs(prefix, FS_promise) { static async init_fs(prefix, FS_promise) {
let FS = await FS_promise; const FS = await FS_promise;
FS.mkdir(prefix); FS.mkdir(prefix);
return FS; return FS;
} }
static async create(instanceName, FS_promise, readOnly = false) { static async create(instanceName, FS_promise, readOnly = false) {
try { try {
let tdfs = new TdFileSystem(); const tdfs = new TdFileSystem();
let prefix = '/' + instanceName; const prefix = '/' + instanceName;
tdfs.prefix = prefix; tdfs.prefix = prefix;
FS_promise = TdFileSystem.init_fs(prefix, FS_promise); FS_promise = TdFileSystem.init_fs(prefix, FS_promise);
//MEMFS. Store to IDB and delete files as soon as possible //MEMFS. Store to IDB and delete files as soon as possible
let inboundFileSystem = InboundFileSystem.create( const inboundFileSystem = InboundFileSystem.create(
instanceName, instanceName,
prefix + '/inboundfs', prefix + '/inboundfs',
FS_promise FS_promise
); );
//IDBFS. MEMFS which is flushed to IDB from time to time //IDBFS. MEMFS which is flushed to IDB from time to time
let dbFileSystem = DbFileSystem.create( const dbFileSystem = DbFileSystem.create(
prefix + '/dbfs', prefix + '/dbfs',
FS_promise, FS_promise,
readOnly readOnly
); );
let FS = await FS_promise; const FS = await FS_promise;
tdfs.FS = FS; tdfs.FS = FS;
//WORKERFS. Temporary stores Blobs for outbound files //WORKERFS. Temporary stores Blobs for outbound files
@ -421,7 +430,7 @@ class TdClient {
async testLocalForage() { async testLocalForage() {
await initLocalForage(); await initLocalForage();
var DRIVERS = [ const DRIVERS = [
localforage.INDEXEDDB, localforage.INDEXEDDB,
'memoryDriver', 'memoryDriver',
localforage.LOCALSTORAGE, localforage.LOCALSTORAGE,
@ -435,7 +444,7 @@ class TdClient {
console.log('A'); console.log('A');
await localforage.setItem('hello', 'world'); await localforage.setItem('hello', 'world');
console.log('B'); console.log('B');
let x = await localforage.getItem('hello'); const x = await localforage.getItem('hello');
console.log('got ', x); console.log('got ', x);
await localforage.clear(); await localforage.clear();
console.log('C'); console.log('C');
@ -454,15 +463,13 @@ class TdClient {
this.wasInit = true; this.wasInit = true;
options = options || {}; options = options || {};
let mode = 'wasm'; const mode = options.mode || 'wasm';
mode = options.mode || mode;
var self = this; const FS_promise = new Promise(resolve => {
let FS_promise = new Promise(resolve => { this.onFS = resolve;
self.onFS = resolve;
}); });
let tdfs_promise = TdFileSystem.create( const tdfs_promise = TdFileSystem.create(
options.instanceName, options.instanceName,
FS_promise, FS_promise,
options.readOnly options.readOnly
@ -474,7 +481,7 @@ class TdClient {
} }
log.info('load TdModule'); log.info('load TdModule');
this.TdModule = await loadTdlib(mode, self.onFS); this.TdModule = await loadTdlib(mode, this.onFS);
log.info('got TdModule'); log.info('got TdModule');
this.td_functions = { this.td_functions = {
td_create: this.TdModule.cwrap('td_create', 'number', []), td_create: this.TdModule.cwrap('td_create', 'number', []),
@ -520,7 +527,7 @@ class TdClient {
// wait till it is allowed to start // wait till it is allowed to start
this.callback({ '@type': 'inited' }); this.callback({ '@type': 'inited' });
await new Promise(resolve => { await new Promise(resolve => {
self.onStart = resolve; this.onStart = resolve;
}); });
this.isStarted = true; this.isStarted = true;
@ -570,8 +577,8 @@ class TdClient {
path: this.tdfs.outboundFileSystem.blobToPath(query.data, query.name) path: this.tdfs.outboundFileSystem.blobToPath(query.data, query.name)
}; };
} }
for (var key in query) { for (const key in query) {
let field = query[key]; const field = query[key];
if (field && typeof field === 'object') { if (field && typeof field === 'object') {
query[key] = this.prepareQueryRecursive(field); query[key] = this.prepareQueryRecursive(field);
} }
@ -584,7 +591,7 @@ class TdClient {
query.parameters.database_directory = this.tdfs.dbFileSystem.root; query.parameters.database_directory = this.tdfs.dbFileSystem.root;
query.parameters.files_directory = this.tdfs.inboundFileSystem.root; query.parameters.files_directory = this.tdfs.inboundFileSystem.root;
let useDb = this.useDatabase; const useDb = this.useDatabase;
query.parameters.use_file_database = useDb; query.parameters.use_file_database = useDb;
query.parameters.use_chat_info_database = useDb; query.parameters.use_chat_info_database = useDb;
query.parameters.use_message_database = useDb; query.parameters.use_message_database = useDb;
@ -602,12 +609,29 @@ class TdClient {
log.info('ignore on_start'); log.info('ignore on_start');
} }
readFilePart(query) { deleteIdbKey(query) {
var res;
try { try {
//let file_size = this.FS.stat(query.path).size; } catch (e) {
var stream = this.FS.open(query.path, 'r'); this.callback({
var buf = new Uint8Array(query.size); '@type': 'error',
'@extra': query['@extra'],
code: 400,
message: e
});
return;
}
this.callback({
'@type': 'ok',
'@extra': query['@extra']
});
}
readFilePart(query) {
let res;
try {
//const file_size = this.FS.stat(query.path).size;
const stream = this.FS.open(query.path, 'r');
const buf = new Uint8Array(query.size);
this.FS.read(stream, buf, 0, query.size, query.offset); this.FS.read(stream, buf, 0, query.size, query.offset);
this.FS.close(stream); this.FS.close(stream);
res = buf; res = buf;
@ -636,7 +660,7 @@ class TdClient {
} }
if (this.wasFatalError) { if (this.wasFatalError) {
if (query['@type'] === 'destroy') { if (query['@type'] === 'destroy') {
this.destroy({ '@type': 'Ok', '@extra': query['@extra'] }); this.destroy({ '@type': 'ok', '@extra': query['@extra'] });
} }
return; return;
} }
@ -671,6 +695,10 @@ class TdClient {
this.readFilePart(query); this.readFilePart(query);
return; return;
} }
if (query['@type'] === 'deleteIdbKey') {
this.deleteIdbKey(query);
return;
}
query = this.prepareQuery(query); query = this.prepareQuery(query);
this.td_functions.td_send(this.client, JSON.stringify(query)); this.td_functions.td_send(this.client, JSON.stringify(query));
this.scheduleReceiveSoon(); this.scheduleReceiveSoon();
@ -678,8 +706,8 @@ class TdClient {
execute(query) { execute(query) {
try { try {
let res = this.td_functions.td_execute(0, JSON.stringify(query)); const res = this.td_functions.td_execute(0, JSON.stringify(query));
let response = JSON.parse(res); const response = JSON.parse(res);
this.callback(response); this.callback(response);
} catch (error) { } catch (error) {
this.onFatalError(error); this.onFatalError(error);
@ -692,11 +720,11 @@ class TdClient {
} }
try { try {
while (true) { while (true) {
let msg = this.td_functions.td_receive(this.client); const msg = this.td_functions.td_receive(this.client);
if (!msg) { if (!msg) {
break; break;
} }
let response = this.prepareResponse(JSON.parse(msg)); const response = this.prepareResponse(JSON.parse(msg));
if ( if (
response['@type'] === 'updateAuthorizationState' && response['@type'] === 'updateAuthorizationState' &&
response.authorization_state['@type'] === 'authorizationStateClosed' response.authorization_state['@type'] === 'authorizationStateClosed'
@ -733,7 +761,7 @@ class TdClient {
return; return;
} }
this.cancelReceive(); this.cancelReceive();
let timeout = this.td_functions.td_get_timeout(); const timeout = this.td_functions.td_get_timeout();
this.scheduleReceiveIn(timeout); this.scheduleReceiveIn(timeout);
} }
scheduleReceiveIn(timeout) { scheduleReceiveIn(timeout) {
@ -782,13 +810,13 @@ class TdClient {
} }
saveFile(pid, file) { saveFile(pid, file) {
let isSaving = this.savingFiles.has(pid); const isSaving = this.savingFiles.has(pid);
this.savingFiles.set(pid, file); this.savingFiles.set(pid, file);
if (isSaving) { if (isSaving) {
return file; return file;
} }
try { try {
var arr = this.FS.readFile(file.local.path); const arr = this.FS.readFile(file.local.path);
if (arr) { if (arr) {
file = Object.assign({}, file); file = Object.assign({}, file);
file.arr = arr; file.arr = arr;
@ -810,7 +838,7 @@ class TdClient {
} }
prepareFile(file) { prepareFile(file) {
let pid = file.remote.id; const pid = file.remote.id;
if (!pid) { if (!pid) {
return file; return file;
} }
@ -832,8 +860,8 @@ class TdClient {
if (response['@type'] === 'file') { if (response['@type'] === 'file') {
return this.prepareFile(response); return this.prepareFile(response);
} }
for (var key in response) { for (const key in response) {
let field = response[key]; const field = response[key];
if (field && typeof field === 'object') { if (field && typeof field === 'object') {
response[key] = this.prepareResponse(field); response[key] = this.prepareResponse(field);
} }
@ -843,13 +871,13 @@ class TdClient {
flushPendingQueries() { flushPendingQueries() {
this.isPending = false; this.isPending = false;
for (let query of this.pendingQueries) { for (const query of this.pendingQueries) {
this.send(query); this.send(query);
} }
} }
} }
var client = new TdClient((e, t = []) => postMessage(e, t)); const client = new TdClient((e, t = []) => postMessage(e, t));
onmessage = function(e) { onmessage = function(e) {
try { try {