Igor Zhukov
11 years ago
12 changed files with 2218 additions and 2069 deletions
@ -0,0 +1,435 @@
@@ -0,0 +1,435 @@
|
||||
/*! |
||||
* Webogram v0.1.6 - messaging web application for MTProto |
||||
* https://github.com/zhukov/webogram
|
||||
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com> |
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
function bigint (num) { |
||||
return new BigInteger(num.toString(16), 16); |
||||
} |
||||
|
||||
function bigStringInt (strNum) { |
||||
return new BigInteger(strNum, 10); |
||||
} |
||||
|
||||
function dHexDump (bytes) { |
||||
var arr = []; |
||||
for (var i = 0; i < bytes.length; i++) { |
||||
if (i && !(i % 2)) { |
||||
if (!(i % 16)) { |
||||
arr.push("\n"); |
||||
} else if (!(i % 4)) { |
||||
arr.push(' '); |
||||
} else { |
||||
arr.push(' '); |
||||
} |
||||
} |
||||
arr.push((bytes[i] < 16 ? '0' : '') + bytes[i].toString(16)); |
||||
} |
||||
|
||||
console.log(arr.join('')); |
||||
} |
||||
|
||||
function bytesToHex (bytes) { |
||||
bytes = bytes || []; |
||||
var arr = []; |
||||
for (var i = 0; i < bytes.length; i++) { |
||||
arr.push((bytes[i] < 16 ? '0' : '') + (bytes[i] || 0).toString(16)); |
||||
} |
||||
return arr.join(''); |
||||
} |
||||
|
||||
function bytesFromHex (hexString) { |
||||
var len = hexString.length, |
||||
i, |
||||
bytes = []; |
||||
|
||||
for (i = 0; i < len; i += 2) { |
||||
bytes.push(parseInt(hexString.substr(i, 2), 16)); |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
function bytesToBase64 (bytes) { |
||||
var mod3, result = ''; |
||||
|
||||
for (var nLen = bytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) { |
||||
mod3 = nIdx % 3; |
||||
nUint24 |= bytes[nIdx] << (16 >>> mod3 & 24); |
||||
if (mod3 === 2 || nLen - nIdx === 1) { |
||||
result += String.fromCharCode( |
||||
uint6ToBase64(nUint24 >>> 18 & 63), |
||||
uint6ToBase64(nUint24 >>> 12 & 63), |
||||
uint6ToBase64(nUint24 >>> 6 & 63), |
||||
uint6ToBase64(nUint24 & 63) |
||||
); |
||||
nUint24 = 0; |
||||
} |
||||
} |
||||
|
||||
return result.replace(/A(?=A$|$)/g, '='); |
||||
} |
||||
|
||||
function uint6ToBase64 (nUint6) { |
||||
return nUint6 < 26 |
||||
? nUint6 + 65 |
||||
: nUint6 < 52 |
||||
? nUint6 + 71 |
||||
: nUint6 < 62 |
||||
? nUint6 - 4 |
||||
: nUint6 === 62 |
||||
? 43 |
||||
: nUint6 === 63 |
||||
? 47 |
||||
: 65; |
||||
} |
||||
|
||||
function bytesCmp (bytes1, bytes2) { |
||||
var len = bytes1.length; |
||||
if (len != bytes2.length) { |
||||
return false; |
||||
} |
||||
|
||||
for (var i = 0; i < len; i++) { |
||||
if (bytes1[i] != bytes2[i]) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
function bytesXor (bytes1, bytes2) { |
||||
var len = bytes1.length, |
||||
bytes = []; |
||||
|
||||
for (var i = 0; i < len; ++i) { |
||||
bytes[i] = bytes1[i] ^ bytes2[i]; |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
function bytesToWords (bytes) { |
||||
var len = bytes.length, |
||||
words = []; |
||||
|
||||
for (var i = 0; i < len; i++) { |
||||
words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8); |
||||
} |
||||
|
||||
return new CryptoJS.lib.WordArray.init(words, len); |
||||
} |
||||
|
||||
function bytesFromWords (wordArray) { |
||||
var words = wordArray.words, |
||||
sigBytes = wordArray.sigBytes, |
||||
bytes = []; |
||||
|
||||
for (var i = 0; i < sigBytes; i++) { |
||||
bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
function bytesFromBigInt (bigInt, len) { |
||||
var bytes = bigInt.toByteArray(); |
||||
|
||||
while (!bytes[0] && (!len || bytes.length > len)) { |
||||
bytes = bytes.slice(1); |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
function bytesToArrayBuffer (b) { |
||||
return (new Uint8Array(b)).buffer; |
||||
} |
||||
|
||||
function bytesFromArrayBuffer (buffer) { |
||||
var len = buffer.byteLength, |
||||
byteView = new Uint8Array(buffer), |
||||
bytes = []; |
||||
|
||||
for (var i = 0; i < len; ++i) { |
||||
bytes[i] = byteView[i]; |
||||
} |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
function longToInts (sLong) { |
||||
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); |
||||
|
||||
return [divRem[0].intValue(), divRem[1].intValue()]; |
||||
} |
||||
|
||||
function longToBytes (sLong) { |
||||
return bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse(); |
||||
} |
||||
|
||||
function longFromInts (high, low) { |
||||
return bigint(high).shiftLeft(32).add(bigint(low)).toString(10); |
||||
} |
||||
|
||||
function intToUint (val) { |
||||
val = parseInt(val); |
||||
if (val < 0) { |
||||
val = val + 4294967296; |
||||
} |
||||
return val; |
||||
} |
||||
|
||||
function uintToInt (val) { |
||||
if (val > 2147483647) { |
||||
val = val - 4294967296; |
||||
} |
||||
return val; |
||||
} |
||||
|
||||
function sha1Hash (bytes) { |
||||
// console.log('SHA-1 hash start');
|
||||
var hashBytes = sha1.hash(bytes, true); |
||||
// console.log('SHA-1 hash finish');
|
||||
|
||||
return hashBytes; |
||||
} |
||||
|
||||
|
||||
|
||||
function rsaEncrypt (publicKey, bytes) { |
||||
var needPadding = 255 - bytes.length; |
||||
if (needPadding > 0) { |
||||
var padding = new Array(needPadding); |
||||
(new SecureRandom()).nextBytes(padding); |
||||
|
||||
bytes = bytes.concat(padding); |
||||
} |
||||
|
||||
// console.log('RSA encrypt start');
|
||||
var N = new BigInteger(publicKey.modulus, 16), |
||||
E = new BigInteger(publicKey.exponent, 16), |
||||
X = new BigInteger(bytes), |
||||
encryptedBigInt = X.modPowInt(E, N), |
||||
encryptedBytes = bytesFromBigInt(encryptedBigInt, 256); |
||||
|
||||
// console.log('RSA encrypt finish');
|
||||
|
||||
return encryptedBytes; |
||||
} |
||||
|
||||
function aesEncrypt (bytes, keyBytes, ivBytes) { |
||||
// console.log('AES encrypt start', bytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
|
||||
|
||||
var needPadding = 16 - (bytes.length % 16); |
||||
if (needPadding > 0 && needPadding < 16) { |
||||
var padding = new Array(needPadding); |
||||
(new SecureRandom()).nextBytes(padding); |
||||
|
||||
bytes = bytes.concat(padding); |
||||
} |
||||
|
||||
var encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), { |
||||
iv: bytesToWords(ivBytes), |
||||
padding: CryptoJS.pad.NoPadding, |
||||
mode: CryptoJS.mode.IGE |
||||
}).ciphertext; |
||||
|
||||
var encryptedBytes = bytesFromWords(encryptedWords); |
||||
|
||||
// console.log('AES encrypt finish');
|
||||
|
||||
return encryptedBytes; |
||||
} |
||||
|
||||
function aesDecrypt (encryptedBytes, keyBytes, ivBytes) { |
||||
// console.log('AES decrypt start', encryptedBytes.length/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/);
|
||||
|
||||
var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), { |
||||
iv: bytesToWords(ivBytes), |
||||
padding: CryptoJS.pad.NoPadding, |
||||
mode: CryptoJS.mode.IGE |
||||
}); |
||||
|
||||
var bytes = bytesFromWords(decryptedWords); |
||||
|
||||
// console.log('AES decrypt finish');
|
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
function gzipUncompress (bytes) { |
||||
// console.log('Gzip uncompress start');
|
||||
var result = (new Zlib.Gunzip(bytes)).decompress(); |
||||
// console.log('Gzip uncompress finish');
|
||||
return result; |
||||
} |
||||
|
||||
function nextRandomInt (maxValue) { |
||||
return Math.floor(Math.random() * maxValue); |
||||
}; |
||||
|
||||
function pqPrimeFactorization (pqBytes) { |
||||
var what = new BigInteger(pqBytes), |
||||
result = false; |
||||
|
||||
console.log('PQ start', pqBytes, what.bitLength()); |
||||
|
||||
if (what.bitLength() <= 64) { |
||||
// console.time('PQ long');
|
||||
try { |
||||
result = pqPrimeLong(goog.math.Long.fromString(what.toString(16), 16)); |
||||
} catch (e) { |
||||
console.error('Pq long Exception', e); |
||||
}; |
||||
// console.timeEnd('PQ long');
|
||||
} |
||||
// console.log(result);
|
||||
|
||||
if (result === false) { |
||||
// console.time('pq BigInt');
|
||||
result = pqPrimeBigInteger(what); |
||||
// console.timeEnd('pq BigInt');
|
||||
} |
||||
|
||||
console.log('PQ finish'); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
function pqPrimeBigInteger (what) { |
||||
var it = 0, |
||||
g; |
||||
for (var i = 0; i < 3; i++) { |
||||
var q = (nextRandomInt(128) & 15) + 17, |
||||
x = bigint(nextRandomInt(1000000000) + 1), |
||||
y = x.clone(), |
||||
lim = 1 << (i + 18); |
||||
|
||||
for (var j = 1; j < lim; j++) { |
||||
++it; |
||||
var a = x.clone(), |
||||
b = x.clone(), |
||||
c = bigint(q); |
||||
|
||||
while (!b.equals(BigInteger.ZERO)) { |
||||
if (!b.and(BigInteger.ONE).equals(BigInteger.ZERO)) { |
||||
c = c.add(a); |
||||
if (c.compareTo(what) > 0) { |
||||
c = c.subtract(what); |
||||
} |
||||
} |
||||
a = a.add(a); |
||||
if (a.compareTo(what) > 0) { |
||||
a = a.subtract(what); |
||||
} |
||||
b = b.shiftRight(1); |
||||
} |
||||
|
||||
x = c.clone(); |
||||
var z = x.compareTo(y) < 0 ? y.subtract(x) : x.subtract(y); |
||||
g = z.gcd(what); |
||||
if (!g.equals(BigInteger.ONE)) { |
||||
break; |
||||
} |
||||
if ((j & (j - 1)) == 0) { |
||||
y = x.clone(); |
||||
} |
||||
} |
||||
if (g.compareTo(BigInteger.ONE) > 0) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
var f = what.divide(g), P, Q; |
||||
|
||||
if (g.compareTo(f) > 0) { |
||||
P = f; |
||||
Q = g; |
||||
} else { |
||||
P = g; |
||||
Q = f; |
||||
} |
||||
|
||||
return [bytesFromBigInt(P), bytesFromBigInt(Q)]; |
||||
} |
||||
|
||||
function gcdLong(a, b) { |
||||
while (a.notEquals(goog.math.Long.ZERO) && b.notEquals(goog.math.Long.ZERO)) { |
||||
while (b.and(goog.math.Long.ONE).equals(goog.math.Long.ZERO)) { |
||||
b = b.shiftRight(1); |
||||
} |
||||
while (a.and(goog.math.Long.ONE).equals(goog.math.Long.ZERO)) { |
||||
a = a.shiftRight(1); |
||||
} |
||||
if (a.compare(b) > 0) { |
||||
a = a.subtract(b); |
||||
} else { |
||||
b = b.subtract(a); |
||||
} |
||||
} |
||||
return b.equals(goog.math.Long.ZERO) ? a : b; |
||||
} |
||||
|
||||
function pqPrimeLong(what) { |
||||
// console.log('start long');
|
||||
var it = 0, |
||||
g; |
||||
for (var i = 0; i < 3; i++) { |
||||
var q = goog.math.Long.fromInt((nextRandomInt(128) & 15) + 17), |
||||
x = goog.math.Long.fromInt(nextRandomInt(1000000000) + 1), |
||||
y = x, |
||||
lim = 1 << (i + 18); |
||||
|
||||
for (var j = 1; j < lim; j++) { |
||||
++it; |
||||
// if (!(it % 100)) {
|
||||
// console.log(dT(), 'it', it, i, j, x.toString());
|
||||
// }
|
||||
var a = x, |
||||
b = x, |
||||
c = q; |
||||
|
||||
while (b.notEquals(goog.math.Long.ZERO)) { |
||||
if (b.and(goog.math.Long.ONE).notEquals(goog.math.Long.ZERO)) { |
||||
c = c.add(a); |
||||
if (c.compare(what) > 0) { |
||||
c = c.subtract(what); |
||||
} |
||||
} |
||||
a = a.add(a); |
||||
if (a.compare(what) > 0) { |
||||
a = a.subtract(what); |
||||
} |
||||
b = b.shiftRight(1); |
||||
} |
||||
|
||||
x = c; |
||||
var z = x.compare(y) < 0 ? y.subtract(x) : x.subtract(y); |
||||
g = gcdLong(z, what); |
||||
if (g.notEquals(goog.math.Long.ONE)) { |
||||
break; |
||||
} |
||||
if ((j & (j - 1)) == 0) { |
||||
y = x; |
||||
} |
||||
} |
||||
if (g.compare(goog.math.Long.ONE) > 0) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
var f = what.div(g), P, Q; |
||||
|
||||
if (g.compare(f) > 0) { |
||||
P = f; |
||||
Q = g; |
||||
} else { |
||||
P = g; |
||||
Q = f; |
||||
} |
||||
|
||||
return [bytesFromHex(P.toString(16)), bytesFromHex(Q.toString(16))]; |
||||
} |
@ -0,0 +1,586 @@
@@ -0,0 +1,586 @@
|
||||
/*! |
||||
* Webogram v0.1.6 - messaging web application for MTProto |
||||
* https://github.com/zhukov/webogram
|
||||
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com> |
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) |
||||
|
||||
.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, ErrorService, $q) { |
||||
var cachedNetworkers = {}, |
||||
cachedUploadNetworkers = {}, |
||||
cachedExportPromise = {}, |
||||
baseDcID = false; |
||||
|
||||
Storage.get('dc').then(function (dcID) { |
||||
if (dcID) { |
||||
baseDcID = dcID; |
||||
} |
||||
}); |
||||
|
||||
function mtpSetUserAuth (dcID, userAuth) { |
||||
Storage.set({ |
||||
dc: dcID, |
||||
user_auth: angular.extend({dcID: dcID}, userAuth) |
||||
}); |
||||
|
||||
baseDcID = dcID; |
||||
} |
||||
|
||||
function mtpLogOut () { |
||||
return mtpInvokeApi('auth.logOut').then(function () { |
||||
Storage.remove('dc', 'user_auth'); |
||||
|
||||
baseDcID = false; |
||||
}, function (error) { |
||||
Storage.remove('dc', 'user_auth'); |
||||
if (error && error.code != 401) { |
||||
Storage.remove('dc' + baseDcID + '_auth_key'); |
||||
} |
||||
baseDcID = false; |
||||
error.handled = true; |
||||
}); |
||||
} |
||||
|
||||
function mtpGetNetworker (dcID, options) { |
||||
options = options || {}; |
||||
|
||||
var cache = (options.fileUpload || options.fileDownload) |
||||
? cachedUploadNetworkers |
||||
: cachedNetworkers; |
||||
if (!dcID) { |
||||
throw new Exception('get Networker without dcID'); |
||||
} |
||||
|
||||
if (cache[dcID] !== undefined) { |
||||
return $q.when(cache[dcID]); |
||||
} |
||||
|
||||
var akk = 'dc' + dcID + '_auth_key', |
||||
ssk = 'dc' + dcID + '_server_salt'; |
||||
|
||||
return Storage.get(akk, ssk).then(function (result) { |
||||
|
||||
if (cache[dcID] !== undefined) { |
||||
return cache[dcID]; |
||||
} |
||||
|
||||
var authKeyHex = result[0], |
||||
serverSaltHex = result[1]; |
||||
// console.log('ass', dcID, authKeyHex, serverSaltHex);
|
||||
if (authKeyHex && authKeyHex.length == 512) { |
||||
var authKey = bytesFromHex(authKeyHex); |
||||
var serverSalt = bytesFromHex(serverSaltHex); |
||||
|
||||
return cache[dcID] = MtpNetworkerFactory.getNetworker(dcID, authKey, serverSalt, options); |
||||
} |
||||
|
||||
if (!options.createNetworker) { |
||||
return $q.reject({type: 'AUTH_KEY_EMPTY', code: 401}); |
||||
} |
||||
|
||||
return MtpAuthorizer.auth(dcID).then(function (auth) { |
||||
var storeObj = {}; |
||||
storeObj[akk] = bytesToHex(auth.authKey); |
||||
storeObj[ssk] = bytesToHex(auth.serverSalt); |
||||
Storage.set(storeObj); |
||||
|
||||
return cache[dcID] = MtpNetworkerFactory.getNetworker(dcID, auth.authKey, auth.serverSalt, options); |
||||
}, function (error) { |
||||
console.log('Get networker error', error, error.stack); |
||||
return $q.reject(error); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
function mtpInvokeApi (method, params, options) { |
||||
options = options || {}; |
||||
|
||||
var deferred = $q.defer(), |
||||
rejectPromise = function (error) { |
||||
if (!error) { |
||||
error = {type: 'ERROR_EMPTY'}; |
||||
} else if (!angular.isObject(error)) { |
||||
error = {message: error}; |
||||
} |
||||
deferred.reject(error); |
||||
|
||||
if (!options.noErrorBox) { |
||||
error.input = method; |
||||
error.stack = error.stack || (new Error()).stack; |
||||
setTimeout(function () { |
||||
if (!error.handled) { |
||||
ErrorService.show({error: error}); |
||||
error.handled = true; |
||||
} |
||||
}, 100); |
||||
} |
||||
}, |
||||
dcID, |
||||
networkerPromise; |
||||
|
||||
if (dcID = options.dcID) { |
||||
networkerPromise = mtpGetNetworker(dcID, options); |
||||
} else { |
||||
networkerPromise = Storage.get('dc').then(function (baseDcID) { |
||||
return mtpGetNetworker(dcID = baseDcID || 1, options); |
||||
}); |
||||
} |
||||
|
||||
var cachedNetworker, |
||||
stack = false; |
||||
|
||||
networkerPromise.then(function (networker) { |
||||
return (cachedNetworker = networker).wrapApiCall(method, params, options).then( |
||||
function (result) { |
||||
deferred.resolve(result); |
||||
// $timeout(function () {
|
||||
// deferred.resolve(result);
|
||||
// }, 1000);
|
||||
}, |
||||
function (error) { |
||||
console.error(dT(), 'Error', error.code, error.type, baseDcID, dcID); |
||||
if (error.code == 401 && baseDcID == dcID) { |
||||
Storage.remove('dc', 'user_auth'); |
||||
} |
||||
else if (error.code == 401 && baseDcID && dcID != baseDcID) { |
||||
if (cachedExportPromise[dcID] === undefined) { |
||||
var exportDeferred = $q.defer(); |
||||
|
||||
mtpInvokeApi('auth.exportAuthorization', {dc_id: dcID}, {noErrorBox: true}).then(function (exportedAuth) { |
||||
mtpInvokeApi('auth.importAuthorization', { |
||||
id: exportedAuth.id, |
||||
bytes: exportedAuth.bytes |
||||
}, {dcID: dcID, noErrorBox: true}).then(function () { |
||||
exportDeferred.resolve(); |
||||
}, function (e) { |
||||
exportDeferred.reject(e); |
||||
}) |
||||
}, function (e) { |
||||
exportDeferred.reject(e) |
||||
}); |
||||
|
||||
cachedExportPromise[dcID] = exportDeferred.promise; |
||||
} |
||||
|
||||
cachedExportPromise[dcID].then(function () { |
||||
(cachedNetworker = networker).wrapApiCall(method, params, options).then(function (result) { |
||||
deferred.resolve(result); |
||||
}, function (error) { |
||||
rejectPromise(error); |
||||
}); |
||||
}, function (error) { |
||||
rejectPromise(error); |
||||
}); |
||||
} |
||||
else if (error.code == 303) { |
||||
var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_)(\d+)/)[2]; |
||||
if (newDcID != dcID) { |
||||
if (options.dcID) { |
||||
options.dcID = newDcID; |
||||
} else { |
||||
Storage.set({dc: baseDcID = newDcID}); |
||||
} |
||||
|
||||
mtpGetNetworker(newDcID, options).then(function (networker) { |
||||
networker.wrapApiCall(method, params, options).then(function (result) { |
||||
deferred.resolve(result); |
||||
}, function (error) { |
||||
rejectPromise(error); |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
else { |
||||
rejectPromise(error); |
||||
} |
||||
}); |
||||
}, function (error) { |
||||
rejectPromise(error); |
||||
}); |
||||
|
||||
if (!(stack = (stack || (new Error()).stack))) { |
||||
try {window.unexistingFunction();} catch (e) { |
||||
stack = e.stack || ''; |
||||
} |
||||
} |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
function mtpGetUserID () { |
||||
return Storage.get('user_auth').then(function (auth) { |
||||
return auth.id || 0; |
||||
}); |
||||
} |
||||
|
||||
function getBaseDcID () { |
||||
return baseDcID || false; |
||||
} |
||||
|
||||
return { |
||||
getBaseDcID: getBaseDcID, |
||||
getUserID: mtpGetUserID, |
||||
invokeApi: mtpInvokeApi, |
||||
setUserAuth: mtpSetUserAuth, |
||||
logOut: mtpLogOut |
||||
} |
||||
}) |
||||
|
||||
.factory('MtpApiFileManager', function (MtpApiManager, $q, FileManager, IdbFileStorage, TmpfsFileStorage, MemoryFileStorage) { |
||||
|
||||
var cachedFs = false; |
||||
var cachedFsPromise = false; |
||||
var apiUploadPromise = $q.when(); |
||||
var cachedSavePromises = {}; |
||||
var cachedDownloadPromises = {}; |
||||
var cachedDownloads = {}; |
||||
|
||||
var downloadPulls = {}; |
||||
var downloadActives = {}; |
||||
var downloadLimit = 5; |
||||
|
||||
function downloadRequest(dcID, cb, activeDelta) { |
||||
if (downloadPulls[dcID] === undefined) { |
||||
downloadPulls[dcID] = []; |
||||
downloadActives[dcID] = 0 |
||||
} |
||||
var downloadPull = downloadPulls[dcID]; |
||||
var deferred = $q.defer(); |
||||
downloadPull.push({cb: cb, deferred: deferred, activeDelta: activeDelta}); |
||||
downloadCheck(dcID); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
var index = 0; |
||||
|
||||
function downloadCheck(dcID) { |
||||
var downloadPull = downloadPulls[dcID]; |
||||
|
||||
if (downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) { |
||||
return false; |
||||
} |
||||
|
||||
var data = downloadPull.shift(), |
||||
activeDelta = data.activeDelta || 1; |
||||
|
||||
downloadActives[dcID] += activeDelta; |
||||
|
||||
var a = index++; |
||||
data.cb() |
||||
.then(function (result) { |
||||
downloadActives[dcID] -= activeDelta; |
||||
downloadCheck(dcID); |
||||
|
||||
data.deferred.resolve(result); |
||||
|
||||
}, function (error) { |
||||
downloadActives[dcID] -= activeDelta; |
||||
downloadCheck(dcID); |
||||
|
||||
data.deferred.reject(error); |
||||
}) |
||||
}; |
||||
|
||||
function getFileName(location) { |
||||
switch (location._) { |
||||
case 'inputVideoFileLocation': |
||||
return 'video' + location.id + '.mp4'; |
||||
|
||||
case 'inputDocumentFileLocation': |
||||
return 'doc' + location.id; |
||||
|
||||
case 'inputAudioFileLocation': |
||||
return 'audio' + location.id; |
||||
} |
||||
|
||||
if (!location.volume_id) { |
||||
console.trace('Empty location', location); |
||||
} |
||||
|
||||
return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg'; |
||||
}; |
||||
|
||||
function getTempFileName(file) { |
||||
var size = file.size || -1; |
||||
var random = nextRandomInt(0xFFFFFFFF); |
||||
return '_temp' + random + '_' + size; |
||||
}; |
||||
|
||||
function getCachedFile (location) { |
||||
if (!location) { |
||||
return false; |
||||
} |
||||
var fileName = getFileName(location); |
||||
|
||||
return cachedDownloads[fileName] || false; |
||||
} |
||||
|
||||
function getFileStorage () { |
||||
if (TmpfsFileStorage.isAvailable()) { |
||||
return TmpfsFileStorage; |
||||
} |
||||
if (IdbFileStorage.isAvailable()) { |
||||
return IdbFileStorage; |
||||
} |
||||
return MemoryFileStorage; |
||||
} |
||||
|
||||
function saveSmallFile (location, bytes) { |
||||
var fileName = getFileName(location), |
||||
mimeType = 'image/jpeg'; |
||||
|
||||
if (!cachedSavePromises[fileName]) { |
||||
cachedSavePromises[fileName] = getFileStorage().saveFile(fileName, bytes).then(function (blob) { |
||||
return cachedDownloads[fileName] = FileManager.getUrl(blob, mimeType); |
||||
}); |
||||
} |
||||
return cachedSavePromises[fileName]; |
||||
} |
||||
|
||||
function downloadSmallFile(location) { |
||||
// console.log('dload small', location);
|
||||
var fileName = getFileName(location), |
||||
mimeType = 'image/jpeg', |
||||
cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName]; |
||||
|
||||
if (cachedPromise) { |
||||
return cachedPromise; |
||||
} |
||||
|
||||
var fileStorage = getFileStorage(); |
||||
|
||||
return cachedDownloadPromises[fileName] = fileStorage.getFile(fileName).then(function (blob) { |
||||
return cachedDownloads[fileName] = FileManager.getUrl(blob, mimeType); |
||||
}, function () { |
||||
var downloadPromise = downloadRequest(location.dc_id, function () { |
||||
// console.log('next small promise');
|
||||
return MtpApiManager.invokeApi('upload.getFile', { |
||||
location: angular.extend({}, location, {_: 'inputFileLocation'}), |
||||
offset: 0, |
||||
limit: 0 |
||||
}, { |
||||
dcID: location.dc_id, |
||||
fileDownload: true, |
||||
createNetworker: true |
||||
}); |
||||
}); |
||||
|
||||
return fileStorage.getFileWriter(fileName, mimeType).then(function (fileWriter) { |
||||
return downloadPromise.then(function (result) { |
||||
return FileManager.write(fileWriter, result.bytes).then(function () { |
||||
return cachedDownloads[fileName] = FileManager.getUrl(fileWriter.finalize(), mimeType); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
function downloadFile (dcID, location, size, options) { |
||||
options = options || {}; |
||||
|
||||
// console.log(dT(), 'Dload file', dcID, location, size);
|
||||
var fileName = getFileName(location), |
||||
toFileEntry = options.toFileEntry || null, |
||||
cachedPromise = cachedSavePromises[fileName] || cachedDownloadPromises[fileName]; |
||||
|
||||
var fileStorage = getFileStorage(); |
||||
|
||||
// console.log(dT(), 'fs', fileStorage, fileName, cachedPromise);
|
||||
|
||||
if (cachedPromise) { |
||||
if (toFileEntry) { |
||||
return cachedPromise.then(function (url) { |
||||
return fileStorage.getFile(fileName).then(function (blob) { |
||||
return FileManager.copy(blob, toFileEntry); |
||||
}); |
||||
}) |
||||
} |
||||
return cachedPromise; |
||||
} |
||||
|
||||
var deferred = $q.defer(), |
||||
canceled = false, |
||||
resolved = false, |
||||
mimeType = options.mime || 'image/jpeg', |
||||
cacheFileWriter, |
||||
errorHandler = function (error) { |
||||
deferred.reject(error); |
||||
errorHandler = angular.noop; |
||||
if (cacheFileWriter) cacheFileWriter.truncate(0); |
||||
}; |
||||
|
||||
|
||||
fileStorage.getFile(fileName).then(function (blob) { |
||||
if (toFileEntry) { |
||||
FileManager.copy(blob, toFileEntry).then(function () { |
||||
deferred.resolve(); |
||||
}, errorHandler); |
||||
} else { |
||||
deferred.resolve(cachedDownloads[fileName] = FileManager.getUrl(blob, mimeType)); |
||||
} |
||||
}, function () { |
||||
var fileWriterPromise = toFileEntry ? $q.when(toFileEntry) : fileStorage.getFileWriter(fileName, mimeType); |
||||
|
||||
fileWriterPromise.then(function (fileWriter) { |
||||
cacheFileWriter = fileWriter; |
||||
var limit = 524288, |
||||
writeFilePromise = $q.when(), |
||||
writeFileDeferred; |
||||
for (var offset = 0; offset < size; offset += limit) { |
||||
writeFileDeferred = $q.defer(); |
||||
(function (isFinal, offset, writeFileDeferred, writeFilePromise) { |
||||
return downloadRequest(dcID, function () { |
||||
if (canceled) { |
||||
return $q.when(); |
||||
} |
||||
return MtpApiManager.invokeApi('upload.getFile', { |
||||
location: location, |
||||
offset: offset, |
||||
limit: limit |
||||
}, { |
||||
dcID: dcID, |
||||
fileDownload: true, |
||||
createNetworker: true |
||||
}); |
||||
}, 6).then(function (result) { |
||||
writeFilePromise.then(function () { |
||||
if (canceled) { |
||||
return $q.when(); |
||||
} |
||||
return FileManager.write(fileWriter, result.bytes).then(function () { |
||||
writeFileDeferred.resolve(); |
||||
}, errorHandler).then(function () { |
||||
if (isFinal) { |
||||
resolved = true; |
||||
if (toFileEntry) { |
||||
deferred.resolve(); |
||||
} else { |
||||
deferred.resolve(cachedDownloads[fileName] = FileManager.getUrl(fileWriter.finalize(), mimeType)); |
||||
} |
||||
} else { |
||||
deferred.notify({done: offset + limit, total: size}); |
||||
}; |
||||
}); |
||||
}); |
||||
}); |
||||
})(offset + limit >= size, offset, writeFileDeferred, writeFilePromise); |
||||
writeFilePromise = writeFileDeferred.promise; |
||||
} |
||||
}); |
||||
}) |
||||
|
||||
deferred.promise.cancel = function () { |
||||
if (!canceled && !resolved) { |
||||
canceled = true; |
||||
delete cachedDownloadPromises[fileName]; |
||||
errorHandler({type: 'DOWNLOAD_CANCELED'}); |
||||
} |
||||
} |
||||
|
||||
if (!toFileEntry) { |
||||
cachedDownloadPromises[fileName] = deferred.promise; |
||||
} |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
function uploadFile (file) { |
||||
var fileSize = file.size, |
||||
// partSize = fileSize > 102400 ? 65536 : 4096,
|
||||
// partSize = fileSize > 102400 ? 524288 : 4096,
|
||||
partSize = fileSize > 102400 ? 524288 : 32768, |
||||
isBigFile = fileSize >= 10485760, |
||||
totalParts = Math.ceil(fileSize / partSize), |
||||
canceled = false, |
||||
resolved = false, |
||||
doneParts = 0; |
||||
|
||||
if (totalParts > 1500) { |
||||
return $q.reject({type: 'FILE_TOO_BIG'}); |
||||
} |
||||
|
||||
var fileID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)], |
||||
deferred = $q.defer(), |
||||
errorHandler = function (error) { |
||||
// console.error('Up Error', error);
|
||||
deferred.reject(error); |
||||
errorHandler = angular.noop; |
||||
}, |
||||
part = 0, |
||||
offset, |
||||
resultInputFile = { |
||||
_: isBigFile ? 'inputFileBig' : 'inputFile', |
||||
id: fileID, |
||||
parts: totalParts, |
||||
name: file.name, |
||||
md5_checksum: '' |
||||
}; |
||||
|
||||
|
||||
var fileReadPromise = $q.when(); |
||||
|
||||
for (offset = 0; offset < fileSize; offset += partSize) { |
||||
(function (offset, part) { |
||||
fileReadPromise = fileReadPromise.then(function () { |
||||
var fileReadDeferred = $q.defer(); |
||||
|
||||
var reader = new FileReader(); |
||||
var blob = file.slice(offset, offset + partSize); |
||||
|
||||
reader.onloadend = function (e) { |
||||
if (canceled || e.target.readyState != FileReader.DONE) { |
||||
return; |
||||
} |
||||
var apiCurPromise = apiUploadPromise = apiUploadPromise.then(function () { |
||||
return MtpApiManager.invokeApi('upload.saveFilePart', { |
||||
file_id: fileID, |
||||
file_part: part, |
||||
bytes: bytesFromArrayBuffer(e.target.result) |
||||
}, { |
||||
startMaxLength: partSize + 256, |
||||
fileUpload: true |
||||
}); |
||||
}, errorHandler); |
||||
|
||||
apiCurPromise.then(function (result) { |
||||
doneParts++; |
||||
fileReadDeferred.resolve(); |
||||
if (doneParts >= totalParts) { |
||||
deferred.resolve(resultInputFile); |
||||
resolved = true; |
||||
} else { |
||||
console.log(dT(), 'Progress', doneParts * partSize / fileSize); |
||||
deferred.notify({done: doneParts * partSize, total: fileSize}); |
||||
} |
||||
}, errorHandler); |
||||
}; |
||||
|
||||
reader.readAsArrayBuffer(blob); |
||||
|
||||
return fileReadDeferred.promise; |
||||
}); |
||||
})(offset, part++); |
||||
} |
||||
|
||||
deferred.promise.cancel = function () { |
||||
console.log('cancel upload', canceled, resolved); |
||||
if (!canceled && !resolved) { |
||||
canceled = true; |
||||
errorHandler({type: 'UPLOAD_CANCELED'}); |
||||
} |
||||
} |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
return { |
||||
getCachedFile: getCachedFile, |
||||
downloadFile: downloadFile, |
||||
downloadSmallFile: downloadSmallFile, |
||||
saveSmallFile: saveSmallFile, |
||||
uploadFile: uploadFile |
||||
}; |
||||
}) |
@ -0,0 +1,518 @@
@@ -0,0 +1,518 @@
|
||||
/*! |
||||
* Webogram v0.1.6 - messaging web application for MTProto |
||||
* https://github.com/zhukov/webogram
|
||||
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com> |
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
angular.module('izhukov.utils', []) |
||||
|
||||
.provider('Storage', function () { |
||||
|
||||
var keyPrefix = ''; |
||||
var cache = {}; |
||||
var useCs = !!(window.chrome && chrome.storage && chrome.storage.local); |
||||
var useLs = !useCs && !!window.localStorage; |
||||
|
||||
this.setPrefix = function (newPrefix) { |
||||
keyPrefix = newPrefix |
||||
}; |
||||
|
||||
this.$get = ['$q', function ($q) { |
||||
function getValue() { |
||||
var keys = Array.prototype.slice.call(arguments), |
||||
result = [], |
||||
single = keys.length == 1, |
||||
allFound = true; |
||||
|
||||
for (var i = 0; i < keys.length; i++) { |
||||
keys[i] = keyPrefix + keys[i]; |
||||
} |
||||
|
||||
angular.forEach(keys, function (key) { |
||||
if (cache[key] !== undefined) { |
||||
result.push(cache[key]); |
||||
} |
||||
else if (useLs) { |
||||
var value = localStorage.getItem(key); |
||||
value = (value === undefined || value === null) ? false : JSON.parse(value); |
||||
result.push(cache[key] = value); |
||||
} |
||||
else if (!useCs) { |
||||
result.push(cache[key] = false); |
||||
} |
||||
else { |
||||
allFound = false; |
||||
} |
||||
}); |
||||
|
||||
if (allFound) { |
||||
return $q.when(single ? result[0] : result); |
||||
} |
||||
|
||||
var deferred = $q.defer(); |
||||
|
||||
chrome.storage.local.get(keys, function (resultObj) { |
||||
result = []; |
||||
angular.forEach(keys, function (key) { |
||||
var value = resultObj[key]; |
||||
value = value === undefined || value === null ? false : JSON.parse(value); |
||||
result.push(cache[key] = value); |
||||
}); |
||||
|
||||
deferred.resolve(single ? result[0] : result); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
function setValue(obj) { |
||||
var keyValues = {}; |
||||
angular.forEach(obj, function (value, key) { |
||||
keyValues[keyPrefix + key] = JSON.stringify(value); |
||||
cache[keyPrefix + key] = value; |
||||
}); |
||||
|
||||
if (useLs) { |
||||
angular.forEach(keyValues, function (value, key) { |
||||
localStorage.setItem(key, value); |
||||
}); |
||||
return $q.when(); |
||||
} |
||||
|
||||
if (!useCs) { |
||||
return $q.when(); |
||||
} |
||||
|
||||
var deferred = $q.defer(); |
||||
|
||||
chrome.storage.local.set(keyValues, function () { |
||||
deferred.resolve(); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
function removeValue () { |
||||
var keys = Array.prototype.slice.call(arguments); |
||||
|
||||
for (var i = 0; i < keys.length; i++) { |
||||
keys[i] = keyPrefix + keys[i]; |
||||
} |
||||
|
||||
angular.forEach(keys, function(key){ |
||||
delete cache[key]; |
||||
}); |
||||
|
||||
if (useLs) { |
||||
angular.forEach(keys, function(key){ |
||||
localStorage.removeItem(key); |
||||
}); |
||||
|
||||
return $q.when(); |
||||
} |
||||
|
||||
if (!useCs) { |
||||
return $q.when(); |
||||
} |
||||
|
||||
var deferred = $q.defer(); |
||||
|
||||
chrome.storage.local.remove(keys, function () { |
||||
deferred.resolve(); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
}; |
||||
|
||||
return { |
||||
get: getValue, |
||||
set: setValue, |
||||
remove: removeValue |
||||
}; |
||||
}]; |
||||
|
||||
}) |
||||
|
||||
.service('FileManager', function ($window, $timeout, $q) { |
||||
|
||||
$window.URL = $window.URL || $window.webkitURL; |
||||
$window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder; |
||||
|
||||
function fileCopyTo (fromFileEntry, toFileEntry) { |
||||
var deferred = $q.defer(); |
||||
|
||||
toFileEntry.createWriter(function (fileWriter) { |
||||
fileWriteData(fileWriter, fromFileEntry).then(function () { |
||||
deferred.resolve(fileWriter); |
||||
}, function (e) { |
||||
deferred.reject(e); |
||||
fileWriter.truncate(0); |
||||
}); |
||||
}, function (e) { |
||||
deferred.reject(e); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
function fileWriteData(fileWriter, bytes) { |
||||
var deferred = $q.defer(); |
||||
|
||||
fileWriter.onwriteend = function(e) { |
||||
deferred.resolve(); |
||||
}; |
||||
fileWriter.onerror = function (e) { |
||||
deferred.reject(); |
||||
}; |
||||
|
||||
if (bytes instanceof Blob) { // is file bytes
|
||||
fileWriter.write(bytes); |
||||
} else { |
||||
fileWriter.write(new Blob([bytesToArrayBuffer(bytes)])); |
||||
} |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
function chooseSaveFile (fileName, ext, mimeType) { |
||||
if (!$window.chrome || !chrome.fileSystem || !chrome.fileSystem.chooseEntry) { |
||||
return $q.reject(); |
||||
}; |
||||
var deferred = $q.defer(); |
||||
|
||||
chrome.fileSystem.chooseEntry({ |
||||
type: 'saveFile', |
||||
suggestedName: fileName, |
||||
accepts: [{ |
||||
mimeTypes: [mimeType], |
||||
extensions: [ext] |
||||
}] |
||||
}, function (writableFileEntry) { |
||||
deferred.resolve(writableFileEntry); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
function getFakeFileWriter (mimeType, saveFileCallback) { |
||||
var blobParts = [], |
||||
fakeFileWriter = { |
||||
write: function (blob) { |
||||
blobParts.push(blob); |
||||
$timeout(function () { |
||||
if (fakeFileWriter.onwriteend) { |
||||
fakeFileWriter.onwriteend(); |
||||
} |
||||
}); |
||||
}, |
||||
truncate: function () { |
||||
blobParts = []; |
||||
}, |
||||
finalize: function () { |
||||
var blob; |
||||
try { |
||||
blob = new Blob(blobParts, {type: mimeType}); |
||||
} catch (e) { |
||||
var bb = new BlobBuilder; |
||||
angular.forEach(blobParts, function(blobPart) { |
||||
bb.append(blobPart); |
||||
}); |
||||
blob = bb.getBlob(mimeType); |
||||
} |
||||
if (saveFileCallback) { |
||||
saveFileCallback(blob); |
||||
} |
||||
return blob; |
||||
} |
||||
}; |
||||
|
||||
return fakeFileWriter; |
||||
}; |
||||
|
||||
function getUrl (fileData, mimeType) { |
||||
// console.log(dT(), 'get url', fileData, mimeType, fileData.toURL !== undefined, fileData instanceof Blob);
|
||||
if (fileData.toURL !== undefined) { |
||||
return fileData.toURL(mimeType); |
||||
} |
||||
if (fileData instanceof Blob) { |
||||
return URL.createObjectURL(fileData); |
||||
} |
||||
return 'data:' + mimeType + ';base64,' + bytesToBase64(fileData); |
||||
} |
||||
|
||||
function downloadFile (url, mimeType, fileName) { |
||||
var anchor = $('<a>Download</a>') |
||||
.css({position: 'absolute', top: 1, left: 1}) |
||||
.attr('href', url) |
||||
.attr('target', '_blank') |
||||
.attr('download', fileName) |
||||
.appendTo('body'); |
||||
|
||||
anchor[0].dataset.downloadurl = [mimeType, fileName, url].join(':'); |
||||
anchor[0].click(); |
||||
$timeout(function () { |
||||
anchor.remove(); |
||||
}, 100); |
||||
} |
||||
|
||||
return { |
||||
copy: fileCopyTo, |
||||
write: fileWriteData, |
||||
getFakeFileWriter: getFakeFileWriter, |
||||
chooseSave: chooseSaveFile, |
||||
getUrl: getUrl, |
||||
download: downloadFile |
||||
}; |
||||
}) |
||||
|
||||
.service('IdbFileStorage', function ($q, $window, FileManager) { |
||||
|
||||
$window.indexedDB = $window.indexedDB || $window.webkitIndexedDB || $window.mozIndexedDB || $window.OIndexedDB || $window.msIndexedDB; |
||||
$window.IDBTransaction = $window.IDBTransaction || $window.webkitIDBTransaction || $window.OIDBTransaction || $window.msIDBTransaction; |
||||
|
||||
var dbName = 'cachedFiles', |
||||
dbStoreName = 'files', |
||||
dbVersion = 1, |
||||
openDbPromise, |
||||
storageIsAvailable = $window.indexedDB !== undefined && $window.IDBTransaction !== undefined; |
||||
|
||||
function isAvailable (argument) { |
||||
return storageIsAvailable; |
||||
} |
||||
|
||||
function openDatabase() { |
||||
if (openDbPromise) { |
||||
return openDbPromise; |
||||
} |
||||
|
||||
var request = indexedDB.open(dbName, dbVersion), |
||||
deferred = $q.defer(), |
||||
createObjectStore = function (db) { |
||||
db.createObjectStore(dbStoreName); |
||||
}; |
||||
|
||||
request.onsuccess = function (event) { |
||||
db = request.result; |
||||
|
||||
db.onerror = function (event) { |
||||
storageIsAvailable = false; |
||||
console.error("Error creating/accessing IndexedDB database", event); |
||||
deferred.reject(event); |
||||
}; |
||||
|
||||
// Interim solution for Google Chrome to create an objectStore. Will be deprecated
|
||||
if (db.setVersion) { |
||||
if (db.version != dbVersion) { |
||||
db.setVersion(dbVersion).onsuccess = function () { |
||||
createObjectStore(db); |
||||
deferred.resolve(db); |
||||
}; |
||||
} |
||||
else { |
||||
deferred.resolve(db); |
||||
} |
||||
} |
||||
else { |
||||
deferred.resolve(db); |
||||
} |
||||
}; |
||||
|
||||
request.onupgradeneeded = function (event) { |
||||
createObjectStore(event.target.result); |
||||
}; |
||||
|
||||
return openDbPromise = deferred.promise; |
||||
}; |
||||
|
||||
function saveFile (fileName, blob) { |
||||
return openDatabase().then(function (db) { |
||||
try { |
||||
var deferred = $q.defer(), |
||||
objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName), |
||||
request = objectStore.put(blob, fileName); |
||||
} catch (error) { |
||||
storageIsAvailable = false; |
||||
return $q.reject(error); |
||||
} |
||||
|
||||
request.onsuccess = function (event) { |
||||
deferred.resolve(blob); |
||||
}; |
||||
|
||||
request.onerror = function (error) { |
||||
deferred.reject(error); |
||||
}; |
||||
|
||||
return deferred.promise; |
||||
}); |
||||
}; |
||||
|
||||
function getFile (fileName) { |
||||
return openDatabase().then(function (db) { |
||||
var deferred = $q.defer(), |
||||
objectStore = db.transaction([dbStoreName], IDBTransaction.READ || 'readonly').objectStore(dbStoreName), |
||||
request = objectStore.get(fileName); |
||||
|
||||
request.onsuccess = function (event) { |
||||
if (event.target.result === undefined) { |
||||
deferred.reject(); |
||||
} else { |
||||
deferred.resolve(event.target.result); |
||||
} |
||||
}; |
||||
|
||||
request.onerror = function (error) { |
||||
deferred.reject(error); |
||||
}; |
||||
|
||||
return deferred.promise; |
||||
}); |
||||
} |
||||
|
||||
function getFileWriter (fileName, mimeType) { |
||||
var fakeWriter = FileManager.getFakeFileWriter(mimeType, function (blob) { |
||||
saveFile(fileName, blob); |
||||
}); |
||||
return $q.when(fakeWriter); |
||||
} |
||||
|
||||
return { |
||||
isAvailable: isAvailable, |
||||
saveFile: saveFile, |
||||
getFile: getFile, |
||||
getFileWriter: getFileWriter |
||||
}; |
||||
}) |
||||
|
||||
|
||||
.service('TmpfsFileStorage', function ($q, $window, FileManager) { |
||||
|
||||
$window.requestFileSystem = $window.requestFileSystem || $window.webkitRequestFileSystem; |
||||
|
||||
var reqFsPromise, |
||||
fileSystem, |
||||
storageIsAvailable = $window.requestFileSystem !== undefined; |
||||
|
||||
function requestFS () { |
||||
if (reqFsPromise) { |
||||
return reqFsPromise; |
||||
} |
||||
|
||||
if (!$window.requestFileSystem) { |
||||
return reqFsPromise = $q.reject({type: 'FS_BROWSER_UNSUPPORTED', description: 'requestFileSystem not present'}); |
||||
} |
||||
|
||||
var deferred = $q.defer(); |
||||
|
||||
$window.requestFileSystem($window.TEMPORARY, 5*1024*1024, function (fs) { |
||||
cachedFs = fs; |
||||
deferred.resolve(); |
||||
}, function (e) { |
||||
storageIsAvailable = false; |
||||
deferred.reject(e); |
||||
}); |
||||
|
||||
return reqFsPromise = deferred.promise; |
||||
}; |
||||
|
||||
function isAvailable () { |
||||
return storageIsAvailable; |
||||
} |
||||
|
||||
function getFile (fileName, size) { |
||||
size = size || 1; |
||||
return requestFS().then(function () { |
||||
// console.log(dT(), 'get file', fileName);
|
||||
var deferred = $q.defer(); |
||||
cachedFs.root.getFile(fileName, {create: false}, function(fileEntry) { |
||||
fileEntry.file(function(file) { |
||||
// console.log(dT(), 'aa', file);
|
||||
if (file.size >= size) { |
||||
deferred.resolve(fileEntry); |
||||
} else { |
||||
deferred.reject(new Error('FILE_NOT_FOUND')); |
||||
} |
||||
}, function (error) { |
||||
console.log(dT(), 'error', error); |
||||
deferred.reject(error); |
||||
}); |
||||
}, function () { |
||||
deferred.reject(new Error('FILE_NOT_FOUND')); |
||||
}); |
||||
return deferred.promise; |
||||
}); |
||||
} |
||||
|
||||
function saveFile (fileName, blob) { |
||||
return getFileWriter(fileName).then(function (fileWriter) { |
||||
return FileManager.write(fileWriter, blob).then(function () { |
||||
return fileWriter.finalize(); |
||||
}) |
||||
}); |
||||
} |
||||
|
||||
function getFileWriter (fileName) { |
||||
// console.log(dT(), 'get file writer', fileName);
|
||||
return requestFS().then(function () { |
||||
var deferred = $q.defer(); |
||||
cachedFs.root.getFile(fileName, {create: true}, function (fileEntry) { |
||||
fileEntry.createWriter(function (fileWriter) { |
||||
fileWriter.finalize = function () { |
||||
return fileEntry; |
||||
} |
||||
// console.log(dT(), 'got writer');
|
||||
deferred.resolve(fileWriter); |
||||
}, function (error) { |
||||
deferred.reject(error); |
||||
}); |
||||
}, function (error) { |
||||
deferred.reject(error); |
||||
}); |
||||
|
||||
return deferred.promise; |
||||
}) |
||||
} |
||||
|
||||
return { |
||||
isAvailable: isAvailable, |
||||
saveFile: saveFile, |
||||
getFile: getFile, |
||||
getFileWriter: getFileWriter |
||||
}; |
||||
}) |
||||
|
||||
.service('MemoryFileStorage', function ($q, FileManager) { |
||||
|
||||
var storage = {}; |
||||
|
||||
function isAvailable () { |
||||
return true; |
||||
} |
||||
|
||||
function getFile (fileName, size) { |
||||
if (storage[fileName]) { |
||||
return $q.when(storage[fileName]); |
||||
} |
||||
return $q.reject(new Error('FILE_NOT_FOUND')); |
||||
} |
||||
|
||||
function saveFile (fileName, blob) { |
||||
return $q.when(storage[fileName] = blob); |
||||
} |
||||
|
||||
function getFileWriter (fileName, mimeType) { |
||||
var fakeWriter = FileManager.getFakeFileWriter(mimeType, function (blob) { |
||||
saveFile(fileName, blob); |
||||
}); |
||||
return $q.when(fakeWriter); |
||||
} |
||||
|
||||
return { |
||||
isAvailable: isAvailable, |
||||
saveFile: saveFile, |
||||
getFile: getFile, |
||||
getFileWriter: getFileWriter |
||||
}; |
||||
}) |
||||
|
@ -0,0 +1,571 @@
@@ -0,0 +1,571 @@
|
||||
/*! |
||||
* Webogram v0.1.6 - messaging web application for MTProto |
||||
* https://github.com/zhukov/webogram
|
||||
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com> |
||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
function TLSerialization (options) { |
||||
options = options || {}; |
||||
this.maxLength = options.startMaxLength || 2048; // 2Kb
|
||||
this.offset = 0; // in bytes
|
||||
|
||||
this.createBuffer(); |
||||
|
||||
// this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug;
|
||||
this.mtproto = options.mtproto || false; |
||||
return this; |
||||
} |
||||
|
||||
TLSerialization.prototype.createBuffer = function () { |
||||
this.buffer = new ArrayBuffer(this.maxLength); |
||||
this.intView = new Int32Array(this.buffer); |
||||
this.byteView = new Uint8Array(this.buffer); |
||||
}; |
||||
|
||||
TLSerialization.prototype.getArray = function () { |
||||
var resultBuffer = new ArrayBuffer(this.offset); |
||||
var resultArray = new Int32Array(resultBuffer); |
||||
|
||||
resultArray.set(this.intView.subarray(0, this.offset / 4)); |
||||
|
||||
return resultArray; |
||||
}; |
||||
|
||||
TLSerialization.prototype.getBuffer = function () { |
||||
return this.getArray().buffer; |
||||
}; |
||||
|
||||
TLSerialization.prototype.getBytes = function () { |
||||
var bytes = []; |
||||
for (var i = 0; i < this.offset; i++) { |
||||
bytes.push(this.byteView[i]); |
||||
} |
||||
return bytes; |
||||
}; |
||||
|
||||
TLSerialization.prototype.checkLength = function (needBytes) { |
||||
if (this.offset + needBytes < this.maxLength) { |
||||
return; |
||||
} |
||||
|
||||
console.trace('Increase buffer', this.offset, needBytes, this.maxLength); |
||||
this.maxLength = Math.ceil(Math.max(this.maxLength * 2, this.offset + needBytes + 16) / 4) * 4; |
||||
var previousBuffer = this.buffer, |
||||
previousArray = new Int32Array(previousBuffer); |
||||
|
||||
this.createBuffer(); |
||||
|
||||
new Int32Array(this.buffer).set(previousArray); |
||||
}; |
||||
|
||||
TLSerialization.prototype.writeInt = function (i, field) { |
||||
this.debug && console.log('>>>', i.toString(16), i, field); |
||||
|
||||
this.checkLength(4); |
||||
this.intView[this.offset / 4] = i; |
||||
this.offset += 4; |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeInt = function (i, field) { |
||||
this.writeInt(i, (field || '') + ':int'); |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeBool = function (i, field) { |
||||
if (i) { |
||||
this.writeInt(0x997275b5, (field || '') + ':bool'); |
||||
} else { |
||||
this.writeInt(0xbc799737, (field || '') + ':bool'); |
||||
} |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeLongP = function (iHigh, iLow, field) { |
||||
this.writeInt(iLow, (field || '') + ':long[low]'); |
||||
this.writeInt(iHigh, (field || '') + ':long[high]'); |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeLong = function (sLong, field) { |
||||
if (angular.isArray(sLong)) { |
||||
if (sLong.length == 2) { |
||||
return this.storeLongP(sLong[0], sLong[1], field); |
||||
} else { |
||||
return this.storeIntBytes(sLong, 64, field); |
||||
} |
||||
} |
||||
|
||||
var divRem = bigStringInt(sLong).divideAndRemainder(bigint(0x100000000)); |
||||
|
||||
this.writeInt(intToUint(divRem[1].intValue()), (field || '') + ':long[low]'); |
||||
this.writeInt(intToUint(divRem[0].intValue()), (field || '') + ':long[high]'); |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeDouble = function (f) { |
||||
var buffer = new ArrayBuffer(8); |
||||
var intView = new Int32Array(buffer); |
||||
var doubleView = new Float64Array(buffer); |
||||
|
||||
doubleView[0] = f; |
||||
|
||||
this.writeInt(intView[0], (field || '') + ':double[low]'); |
||||
this.writeInt(intView[1], (field || '') + ':double[high]'); |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeString = function (s, field) { |
||||
this.debug && console.log('>>>', s, (field || '') + ':string'); |
||||
|
||||
var sUTF8 = unescape(encodeURIComponent(s)); |
||||
|
||||
this.checkLength(sUTF8.length + 8); |
||||
|
||||
|
||||
var len = sUTF8.length; |
||||
if (len <= 253) { |
||||
this.byteView[this.offset++] = len; |
||||
} else { |
||||
this.byteView[this.offset++] = 254; |
||||
this.byteView[this.offset++] = len & 0xFF; |
||||
this.byteView[this.offset++] = (len & 0xFF00) >> 8; |
||||
this.byteView[this.offset++] = (len & 0xFF0000) >> 16; |
||||
} |
||||
for (var i = 0; i < len; i++) { |
||||
this.byteView[this.offset++] = sUTF8.charCodeAt(i); |
||||
} |
||||
|
||||
// Padding
|
||||
while (this.offset % 4) { |
||||
this.byteView[this.offset++] = 0; |
||||
} |
||||
} |
||||
|
||||
|
||||
TLSerialization.prototype.storeBytes = function (bytes, field) { |
||||
this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':bytes'); |
||||
|
||||
this.checkLength(bytes.length + 8); |
||||
|
||||
var len = bytes.length; |
||||
if (len <= 253) { |
||||
this.byteView[this.offset++] = len; |
||||
} else { |
||||
this.byteView[this.offset++] = 254; |
||||
this.byteView[this.offset++] = len & 0xFF; |
||||
this.byteView[this.offset++] = (len & 0xFF00) >> 8; |
||||
this.byteView[this.offset++] = (len & 0xFF0000) >> 16; |
||||
} |
||||
for (var i = 0; i < len; i++) { |
||||
this.byteView[this.offset++] = bytes[i]; |
||||
} |
||||
|
||||
// Padding
|
||||
while (this.offset % 4) { |
||||
this.byteView[this.offset++] = 0; |
||||
} |
||||
} |
||||
|
||||
TLSerialization.prototype.storeIntBytes = function (bytes, bits, field) { |
||||
var len = bytes.length; |
||||
if ((bits % 32) || (len * 8) != bits) { |
||||
throw new Error('Invalid bits: ' + bits + ', ' + bytes.length); |
||||
} |
||||
|
||||
this.debug && console.log('>>>', bytesToHex(bytes), (field || '') + ':int' + bits); |
||||
this.checkLength(len); |
||||
|
||||
for (var i = 0; i < len; i++) { |
||||
this.byteView[this.offset++] = bytes[i]; |
||||
} |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeRawBytes = function (bytes, field) { |
||||
var len = bytes.length; |
||||
|
||||
this.debug && console.log('>>>', bytesToHex(bytes), (field || '')); |
||||
this.checkLength(len); |
||||
|
||||
for (var i = 0; i < len; i++) { |
||||
this.byteView[this.offset++] = bytes[i]; |
||||
} |
||||
}; |
||||
|
||||
|
||||
TLSerialization.prototype.storeMethod = function (methodName, params) { |
||||
var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API, |
||||
methodData = false, |
||||
i; |
||||
|
||||
for (i = 0; i < schema.methods.length; i++) { |
||||
if (schema.methods[i].method == methodName) { |
||||
methodData = schema.methods[i]; |
||||
break |
||||
} |
||||
} |
||||
if (!methodData) { |
||||
throw new Error('No method ' + methodName + ' found'); |
||||
} |
||||
|
||||
this.storeInt(intToUint(methodData.id), methodName + '[id]'); |
||||
|
||||
var self = this; |
||||
angular.forEach(methodData.params, function (param) { |
||||
self.storeObject(params[param.name], param.type, methodName + '[' + param.name + ']'); |
||||
}); |
||||
|
||||
return methodData.type; |
||||
}; |
||||
|
||||
TLSerialization.prototype.storeObject = function (obj, type, field) { |
||||
switch (type) { |
||||
case 'int': return this.storeInt(obj, field); |
||||
case 'long': return this.storeLong(obj, field); |
||||
case 'int128': return this.storeIntBytes(obj, 128, field); |
||||
case 'int256': return this.storeIntBytes(obj, 256, field); |
||||
case 'int512': return this.storeIntBytes(obj, 512, field); |
||||
case 'string': return this.storeString(obj, field); |
||||
case 'bytes': return this.storeBytes(obj, field); |
||||
case 'double': return this.storeDouble(obj, field); |
||||
case 'Bool': return this.storeBool(obj, field); |
||||
} |
||||
|
||||
if (angular.isArray(obj)) { |
||||
if (type.substr(0, 6) == 'Vector') { |
||||
this.writeInt(0x1cb5c415, field + '[id]'); |
||||
} |
||||
else if (type.substr(0, 6) != 'vector') { |
||||
throw new Error('Invalid vector type ' + type); |
||||
} |
||||
var itemType = type.substr(7, type.length - 8); // for "Vector<itemType>"
|
||||
this.writeInt(obj.length, field + '[count]'); |
||||
for (var i = 0; i < obj.length; i++) { |
||||
this.storeObject(obj[i], itemType, field + '[' + i + ']'); |
||||
} |
||||
return true; |
||||
} |
||||
else if (type.substr(0, 6).toLowerCase() == 'vector') { |
||||
throw new Error('Invalid vector object'); |
||||
} |
||||
|
||||
if (!angular.isObject(obj)) { |
||||
throw new Error('Invalid object for type ' + type); |
||||
} |
||||
|
||||
var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API, |
||||
predicate = obj['_'], |
||||
isBare = false, |
||||
constructorData = false, |
||||
i; |
||||
|
||||
if (isBare = (type.charAt(0) == '%')) { |
||||
type = type.substr(1); |
||||
} |
||||
|
||||
for (i = 0; i < schema.constructors.length; i++) { |
||||
if (schema.constructors[i].predicate == predicate) { |
||||
constructorData = schema.constructors[i]; |
||||
break |
||||
} |
||||
} |
||||
if (!constructorData) { |
||||
throw new Error('No predicate ' + predicate + ' found'); |
||||
} |
||||
|
||||
if (predicate == type) { |
||||
isBare = true; |
||||
} |
||||
|
||||
if (!isBare) { |
||||
this.writeInt(intToUint(constructorData.id), field + '[' + predicate + '][id]'); |
||||
} |
||||
|
||||
var self = this; |
||||
angular.forEach(constructorData.params, function (param) { |
||||
self.storeObject(obj[param.name], param.type, field + '[' + predicate + '][' + param.name + ']'); |
||||
}); |
||||
|
||||
return constructorData.type; |
||||
}; |
||||
|
||||
|
||||
|
||||
function TLDeserialization (buffer, options) { |
||||
options = options || {}; |
||||
|
||||
this.offset = 0; // in bytes
|
||||
this.override = options.override || {}; |
||||
|
||||
this.buffer = buffer; |
||||
this.intView = new Uint32Array(this.buffer); |
||||
this.byteView = new Uint8Array(this.buffer); |
||||
|
||||
// this.debug = options.debug !== undefined ? options.debug : Config.Modes.debug;
|
||||
this.mtproto = options.mtproto || false; |
||||
return this; |
||||
} |
||||
|
||||
TLDeserialization.prototype.readInt = function (field) { |
||||
if (this.offset >= this.intView.length * 4) { |
||||
throw new Error('Nothing to fetch: ' + field); |
||||
} |
||||
|
||||
var i = this.intView[this.offset / 4]; |
||||
|
||||
this.debug && console.log('<<<', i.toString(16), i, field); |
||||
|
||||
this.offset += 4; |
||||
|
||||
return i; |
||||
}; |
||||
|
||||
TLDeserialization.prototype.fetchInt = function (field) { |
||||
return this.readInt((field || '') + ':int'); |
||||
} |
||||
|
||||
TLDeserialization.prototype.fetchDouble = function (field) { |
||||
var buffer = new ArrayBuffer(8); |
||||
var intView = new Int32Array(buffer); |
||||
var doubleView = new Float64Array(buffer); |
||||
|
||||
intView[0] = this.readInt((field || '') + ':double[low]'), |
||||
intView[1] = this.readInt((field || '') + ':double[high]'); |
||||
|
||||
return doubleView[0]; |
||||
}; |
||||
|
||||
TLDeserialization.prototype.fetchLong = function (field) { |
||||
var iLow = this.readInt((field || '') + ':long[low]'), |
||||
iHigh = this.readInt((field || '') + ':long[high]'); |
||||
|
||||
var longDec = bigint(iHigh).shiftLeft(32).add(bigint(iLow)).toString(); |
||||
|
||||
return longDec; |
||||
} |
||||
|
||||
TLDeserialization.prototype.fetchBool = function (field) { |
||||
var i = this.readInt((field || '') + ':bool'); |
||||
if (i == 0x997275b5) { |
||||
return true; |
||||
} else if (i == 0xbc799737) { |
||||
return false |
||||
} |
||||
|
||||
this.offset -= 4; |
||||
return this.fetchObject('Object', field); |
||||
} |
||||
|
||||
TLDeserialization.prototype.fetchString = function (field) { |
||||
var len = this.byteView[this.offset++]; |
||||
|
||||
if (len == 254) { |
||||
var len = this.byteView[this.offset++] | |
||||
(this.byteView[this.offset++] << 8) | |
||||
(this.byteView[this.offset++] << 16); |
||||
} |
||||
|
||||
var sUTF8 = ''; |
||||
for (var i = 0; i < len; i++) { |
||||
sUTF8 += String.fromCharCode(this.byteView[this.offset++]); |
||||
} |
||||
|
||||
// Padding
|
||||
while (this.offset % 4) { |
||||
this.offset++; |
||||
} |
||||
|
||||
try { |
||||
var s = decodeURIComponent(escape(sUTF8)); |
||||
} catch (e) { |
||||
var s = sUTF8; |
||||
} |
||||
|
||||
this.debug && console.log('<<<', s, (field || '') + ':string'); |
||||
|
||||
return s; |
||||
} |
||||
|
||||
|
||||
TLDeserialization.prototype.fetchBytes = function (field) { |
||||
var len = this.byteView[this.offset++]; |
||||
|
||||
if (len == 254) { |
||||
var len = this.byteView[this.offset++] | |
||||
(this.byteView[this.offset++] << 8) | |
||||
(this.byteView[this.offset++] << 16); |
||||
} |
||||
|
||||
var bytes = []; |
||||
for (var i = 0; i < len; i++) { |
||||
bytes.push(this.byteView[this.offset++]); |
||||
} |
||||
|
||||
// Padding
|
||||
while (this.offset % 4) { |
||||
this.offset++; |
||||
} |
||||
|
||||
this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':bytes'); |
||||
|
||||
return bytes; |
||||
} |
||||
|
||||
TLDeserialization.prototype.fetchIntBytes = function (bits, field) { |
||||
if (bits % 32) { |
||||
throw new Error('Invalid bits: ' + bits); |
||||
} |
||||
|
||||
var len = bits / 8; |
||||
var bytes = []; |
||||
for (var i = 0; i < len; i++) { |
||||
bytes.push(this.byteView[this.offset++]); |
||||
} |
||||
|
||||
this.debug && console.log('<<<', bytesToHex(bytes), (field || '') + ':int' + bits); |
||||
|
||||
return bytes; |
||||
}; |
||||
|
||||
|
||||
TLDeserialization.prototype.fetchRawBytes = function (len, field) { |
||||
if (len === false) { |
||||
len = this.readInt((field || '') + '_length'); |
||||
} |
||||
|
||||
var bytes = []; |
||||
for (var i = 0; i < len; i++) { |
||||
bytes.push(this.byteView[this.offset++]); |
||||
} |
||||
|
||||
this.debug && console.log('<<<', bytesToHex(bytes), (field || '')); |
||||
|
||||
return bytes; |
||||
}; |
||||
|
||||
TLDeserialization.prototype.fetchObject = function (type, field) { |
||||
switch (type) { |
||||
case 'int': return this.fetchInt(field); |
||||
case 'long': return this.fetchLong(field); |
||||
case 'int128': return this.fetchIntBytes(128, field); |
||||
case 'int256': return this.fetchIntBytes(256, field); |
||||
case 'int512': return this.fetchIntBytes(512, field); |
||||
case 'string': return this.fetchString(field); |
||||
case 'bytes': return this.fetchBytes(field); |
||||
case 'double': return this.fetchDouble(field); |
||||
case 'Bool': return this.fetchBool(field); |
||||
} |
||||
|
||||
field = field || type || 'Object'; |
||||
|
||||
if (type.substr(0, 6) == 'Vector' || type.substr(0, 6) == 'vector') { |
||||
if (type.charAt(0) == 'V') { |
||||
var constructor = this.readInt(field + '[id]'); |
||||
if (constructor != 0x1cb5c415) { |
||||
throw new Error('Invalid vector constructor ' + constructor); |
||||
} |
||||
} |
||||
var len = this.readInt(field + '[count]'); |
||||
var result = []; |
||||
if (len > 0) { |
||||
var itemType = type.substr(7, type.length - 8); // for "Vector<itemType>"
|
||||
for (var i = 0; i < len; i++) { |
||||
result.push(this.fetchObject(itemType, field + '[' + i + ']')) |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
var schema = this.mtproto ? Config.Schema.MTProto : Config.Schema.API, |
||||
predicate = false, |
||||
constructorData = false; |
||||
|
||||
if (type.charAt(0) == '%') { |
||||
var checkType = type.substr(1); |
||||
for (i = 0; i < schema.constructors.length; i++) { |
||||
if (schema.constructors[i].type == checkType) { |
||||
constructorData = schema.constructors[i]; |
||||
break |
||||
} |
||||
} |
||||
if (!constructorData) { |
||||
throw new Error('Constructor not found for type: ' + type); |
||||
} |
||||
} |
||||
else if (type.charAt(0) >= 97 && type.charAt(0) <= 122) { |
||||
for (i = 0; i < schema.constructors.length; i++) { |
||||
if (schema.constructors[i].predicate == type) { |
||||
constructorData = schema.constructors[i]; |
||||
break |
||||
} |
||||
} |
||||
if (!constructorData) { |
||||
throw new Error('Constructor not found for predicate: ' + type); |
||||
} |
||||
} |
||||
else { |
||||
var constructor = this.readInt(field + '[id]'), |
||||
constructorCmp = uintToInt(constructor); |
||||
|
||||
if (constructorCmp == 0x3072cfa1) { // Gzip packed
|
||||
var compressed = this.fetchBytes(field + '[packed_string]'), |
||||
uncompressed = gzipUncompress(compressed), |
||||
buffer = bytesToArrayBuffer(uncompressed), |
||||
newDeserializer = (new TLDeserialization(buffer)); |
||||
|
||||
return newDeserializer.fetchObject(type, field); |
||||
} |
||||
|
||||
for (i = 0; i < schema.constructors.length; i++) { |
||||
if (schema.constructors[i].id == constructorCmp) { |
||||
constructorData = schema.constructors[i]; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
var fallback = false; |
||||
if (!constructorData && this.mtproto) { |
||||
var schemaFallback = Config.Schema.API; |
||||
for (i = 0; i < schemaFallback.constructors.length; i++) { |
||||
if (schemaFallback.constructors[i].id == constructorCmp) { |
||||
constructorData = schemaFallback.constructors[i]; |
||||
|
||||
delete this.mtproto; |
||||
fallback = true; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if (!constructorData) { |
||||
throw new Error('Constructor not found: ' + constructor); |
||||
} |
||||
} |
||||
|
||||
predicate = constructorData.predicate; |
||||
|
||||
var result = {'_': predicate}, |
||||
overrideKey = (this.mtproto ? 'mt_' : '') + predicate, |
||||
self = this; |
||||
|
||||
|
||||
if (this.override[overrideKey]) { |
||||
this.override[overrideKey].apply(this, [result, field + '[' + predicate + ']']); |
||||
} else { |
||||
angular.forEach(constructorData.params, function (param) { |
||||
result[param.name] = self.fetchObject(param.type, field + '[' + predicate + '][' + param.name + ']'); |
||||
}); |
||||
} |
||||
|
||||
if (fallback) { |
||||
this.mtproto = true; |
||||
} |
||||
|
||||
return result; |
||||
}; |
||||
|
||||
TLDeserialization.prototype.getOffset = function () { |
||||
return this.offset; |
||||
}; |
||||
|
||||
TLDeserialization.prototype.fetchEnd = function () { |
||||
if (this.offset != this.byteView.length) { |
||||
throw new Error('Fetch end with non-empty buffer'); |
||||
} |
||||
return true; |
||||
}; |
Loading…
Reference in new issue