Supported IndexDB
Sources refactoring Supported big files upload Closes #332
This commit is contained in:
parent
5661a177da
commit
251d0a2eff
@ -47,8 +47,12 @@
|
|||||||
<script type="text/javascript" src="vendor/closure/long.js"></script>
|
<script type="text/javascript" src="vendor/closure/long.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="js/lib/utils.js"></script>
|
||||||
|
<script type="text/javascript" src="js/lib/bin_utils.js"></script>
|
||||||
|
<script type="text/javascript" src="js/lib/tl_utils.js"></script>
|
||||||
|
<script type="text/javascript" src="js/lib/ng_utils.js"></script>
|
||||||
<script type="text/javascript" src="js/lib/mtproto.js"></script>
|
<script type="text/javascript" src="js/lib/mtproto.js"></script>
|
||||||
<script type="text/javascript" src="js/util.js"></script>
|
<script type="text/javascript" src="js/lib/mtproto_wrapper.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/app.js"></script>
|
<script type="text/javascript" src="js/app.js"></script>
|
||||||
<script type="text/javascript" src="js/services.js"></script>
|
<script type="text/javascript" src="js/services.js"></script>
|
||||||
|
@ -24,7 +24,9 @@ angular.module('myApp', [
|
|||||||
'ngSanitize',
|
'ngSanitize',
|
||||||
'ui.bootstrap',
|
'ui.bootstrap',
|
||||||
'pasvaz.bindonce',
|
'pasvaz.bindonce',
|
||||||
'mtproto.services',
|
'izhukov.utils',
|
||||||
|
'izhukov.mtproto',
|
||||||
|
'izhukov.mtproto.wrapper',
|
||||||
'myApp.filters',
|
'myApp.filters',
|
||||||
'myApp.services',
|
'myApp.services',
|
||||||
/*PRODUCTION_ONLY_BEGIN
|
/*PRODUCTION_ONLY_BEGIN
|
||||||
@ -33,7 +35,7 @@ angular.module('myApp', [
|
|||||||
'myApp.directives',
|
'myApp.directives',
|
||||||
'myApp.controllers'
|
'myApp.controllers'
|
||||||
]).
|
]).
|
||||||
config(['$locationProvider', '$routeProvider', '$compileProvider', function($locationProvider, $routeProvider, $compileProvider) {
|
config(['$locationProvider', '$routeProvider', '$compileProvider', 'StorageProvider', function($locationProvider, $routeProvider, $compileProvider, StorageProvider) {
|
||||||
|
|
||||||
var icons = {}, reverseIcons = {}, i, j, hex, name, dataItem, row, column, totalColumns;
|
var icons = {}, reverseIcons = {}, i, j, hex, name, dataItem, row, column, totalColumns;
|
||||||
|
|
||||||
@ -49,6 +51,10 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Config.Modes.test) {
|
||||||
|
StorageProvider.setPrefix('t_');
|
||||||
|
}
|
||||||
|
|
||||||
$.emojiarea.spritesheetPath = 'img/emojisprite_!.png';
|
$.emojiarea.spritesheetPath = 'img/emojisprite_!.png';
|
||||||
$.emojiarea.spritesheetDimens = Config.EmojiCategorySpritesheetDimens;
|
$.emojiarea.spritesheetDimens = Config.EmojiCategorySpritesheetDimens;
|
||||||
$.emojiarea.iconSize = 20;
|
$.emojiarea.iconSize = 20;
|
||||||
@ -59,7 +65,6 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc
|
|||||||
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|mailto|blob|filesystem|chrome-extension|app):|data:image\//);
|
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|mailto|blob|filesystem|chrome-extension|app):|data:image\//);
|
||||||
|
|
||||||
|
|
||||||
// $locationProvider.html5Mode(true);
|
|
||||||
$routeProvider.when('/', {templateUrl: 'partials/welcome.html', controller: 'AppWelcomeController'});
|
$routeProvider.when('/', {templateUrl: 'partials/welcome.html', controller: 'AppWelcomeController'});
|
||||||
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'AppLoginController'});
|
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'AppLoginController'});
|
||||||
$routeProvider.when('/im', {templateUrl: 'partials/im.html', controller: 'AppIMController', reloadOnSearch: false});
|
$routeProvider.when('/im', {templateUrl: 'partials/im.html', controller: 'AppIMController', reloadOnSearch: false});
|
||||||
|
@ -1125,7 +1125,7 @@ angular.module('myApp.controllers', [])
|
|||||||
$scope.$on('user_update', angular.noop);
|
$scope.$on('user_update', angular.noop);
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller('AppImSendController', function ($scope, $timeout, MtpApiManager, AppConfigManager, AppPeersManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
|
.controller('AppImSendController', function ($scope, $timeout, MtpApiManager, Storage, AppPeersManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) {
|
||||||
|
|
||||||
$scope.$watch('curDialog.peer', resetDraft);
|
$scope.$watch('curDialog.peer', resetDraft);
|
||||||
$scope.$on('user_update', angular.noop);
|
$scope.$on('user_update', angular.noop);
|
||||||
@ -1180,7 +1180,7 @@ angular.module('myApp.controllers', [])
|
|||||||
|
|
||||||
function resetDraft (newPeer) {
|
function resetDraft (newPeer) {
|
||||||
if (newPeer) {
|
if (newPeer) {
|
||||||
AppConfigManager.get('draft' + $scope.curDialog.peerID).then(function (draftText) {
|
Storage.get('draft' + $scope.curDialog.peerID).then(function (draftText) {
|
||||||
// console.log('Restore draft', 'draft' + $scope.curDialog.peerID, draftText);
|
// console.log('Restore draft', 'draft' + $scope.curDialog.peerID, draftText);
|
||||||
$scope.draftMessage.text = draftText || '';
|
$scope.draftMessage.text = draftText || '';
|
||||||
// console.log('send broadcast', $scope.draftMessage);
|
// console.log('send broadcast', $scope.draftMessage);
|
||||||
@ -1204,10 +1204,10 @@ angular.module('myApp.controllers', [])
|
|||||||
|
|
||||||
var backupDraftObj = {};
|
var backupDraftObj = {};
|
||||||
backupDraftObj['draft' + $scope.curDialog.peerID] = newVal;
|
backupDraftObj['draft' + $scope.curDialog.peerID] = newVal;
|
||||||
AppConfigManager.set(backupDraftObj);
|
Storage.set(backupDraftObj);
|
||||||
// console.log('draft save', backupDraftObj);
|
// console.log('draft save', backupDraftObj);
|
||||||
} else {
|
} else {
|
||||||
AppConfigManager.remove('draft' + $scope.curDialog.peerID);
|
Storage.remove('draft' + $scope.curDialog.peerID);
|
||||||
// console.log('draft delete', 'draft' + $scope.curDialog.peerID);
|
// console.log('draft delete', 'draft' + $scope.curDialog.peerID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1780,7 +1780,7 @@ angular.module('myApp.controllers', [])
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, AppConfigManager, NotificationsManager, MtpApiFileManager, ApiUpdatesManager, ChangelogNotifyService, ErrorService) {
|
.controller('SettingsModalController', function ($rootScope, $scope, $timeout, $modal, AppUsersManager, AppChatsManager, AppPhotosManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, ApiUpdatesManager, ChangelogNotifyService, ErrorService) {
|
||||||
|
|
||||||
$scope.profile = {};
|
$scope.profile = {};
|
||||||
$scope.photo = {};
|
$scope.photo = {};
|
||||||
@ -1876,7 +1876,7 @@ angular.module('myApp.controllers', [])
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
AppConfigManager.get('notify_nodesktop', 'notify_nosound', 'send_ctrlenter', 'notify_volume').then(function (settings) {
|
Storage.get('notify_nodesktop', 'notify_nosound', 'send_ctrlenter', 'notify_volume').then(function (settings) {
|
||||||
$scope.notify.desktop = !settings[0];
|
$scope.notify.desktop = !settings[0];
|
||||||
$scope.send.enter = settings[2] ? '' : '1';
|
$scope.send.enter = settings[2] ? '' : '1';
|
||||||
|
|
||||||
@ -1904,8 +1904,8 @@ angular.module('myApp.controllers', [])
|
|||||||
$scope.$watch('notify.volume', function (newValue, oldValue) {
|
$scope.$watch('notify.volume', function (newValue, oldValue) {
|
||||||
if (newValue !== oldValue) {
|
if (newValue !== oldValue) {
|
||||||
var storeVolume = newValue / 10;
|
var storeVolume = newValue / 10;
|
||||||
AppConfigManager.set({notify_volume: storeVolume});
|
Storage.set({notify_volume: storeVolume});
|
||||||
AppConfigManager.remove('notify_nosound');
|
Storage.remove('notify_nosound');
|
||||||
NotificationsManager.clear();
|
NotificationsManager.clear();
|
||||||
|
|
||||||
if (testSoundPromise) {
|
if (testSoundPromise) {
|
||||||
@ -1921,9 +1921,9 @@ angular.module('myApp.controllers', [])
|
|||||||
$scope.notify.desktop = !$scope.notify.desktop;
|
$scope.notify.desktop = !$scope.notify.desktop;
|
||||||
|
|
||||||
if ($scope.notify.desktop) {
|
if ($scope.notify.desktop) {
|
||||||
AppConfigManager.remove('notify_nodesktop');
|
Storage.remove('notify_nodesktop');
|
||||||
} else {
|
} else {
|
||||||
AppConfigManager.set({notify_nodesktop: true});
|
Storage.set({notify_nodesktop: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1931,9 +1931,9 @@ angular.module('myApp.controllers', [])
|
|||||||
$scope.send.enter = newValue;
|
$scope.send.enter = newValue;
|
||||||
|
|
||||||
if ($scope.send.enter) {
|
if ($scope.send.enter) {
|
||||||
AppConfigManager.remove('send_ctrlenter');
|
Storage.remove('send_ctrlenter');
|
||||||
} else {
|
} else {
|
||||||
AppConfigManager.set({send_ctrlenter: true});
|
Storage.set({send_ctrlenter: true});
|
||||||
}
|
}
|
||||||
$rootScope.$broadcast('settings_changed');
|
$rootScope.$broadcast('settings_changed');
|
||||||
}
|
}
|
||||||
@ -1944,7 +1944,7 @@ angular.module('myApp.controllers', [])
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller('ProfileEditModalController', function ($rootScope, $scope, $timeout, $modal, $modalInstance, AppUsersManager, AppChatsManager, MtpApiManager, AppConfigManager, NotificationsManager, MtpApiFileManager, ApiUpdatesManager) {
|
.controller('ProfileEditModalController', function ($rootScope, $scope, $timeout, $modal, $modalInstance, AppUsersManager, AppChatsManager, MtpApiManager, Storage, NotificationsManager, MtpApiFileManager, ApiUpdatesManager) {
|
||||||
|
|
||||||
$scope.profile = {};
|
$scope.profile = {};
|
||||||
$scope.error = {};
|
$scope.error = {};
|
||||||
|
@ -609,7 +609,7 @@ angular.module('myApp.directives', ['myApp.filters'])
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.directive('mySendForm', function ($timeout, $modalStack, AppConfigManager, ErrorService) {
|
.directive('mySendForm', function ($timeout, $modalStack, Storage, ErrorService) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link,
|
link: link,
|
||||||
@ -665,7 +665,7 @@ angular.module('myApp.directives', ['myApp.filters'])
|
|||||||
|
|
||||||
var sendOnEnter = true,
|
var sendOnEnter = true,
|
||||||
updateSendSettings = function () {
|
updateSendSettings = function () {
|
||||||
AppConfigManager.get('send_ctrlenter').then(function (sendOnCtrl) {
|
Storage.get('send_ctrlenter').then(function (sendOnCtrl) {
|
||||||
sendOnEnter = !sendOnCtrl;
|
sendOnEnter = !sendOnCtrl;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
435
app/js/lib/bin_utils.js
Normal file
435
app/js/lib/bin_utils.js
Normal file
@ -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))];
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
586
app/js/lib/mtproto_wrapper.js
Normal file
586
app/js/lib/mtproto_wrapper.js
Normal file
@ -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
|
||||||
|
};
|
||||||
|
})
|
518
app/js/lib/ng_utils.js
Normal file
518
app/js/lib/ng_utils.js
Normal file
@ -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
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
571
app/js/lib/tl_utils.js
Normal file
571
app/js/lib/tl_utils.js
Normal file
@ -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;
|
||||||
|
};
|
@ -11,125 +11,6 @@
|
|||||||
|
|
||||||
angular.module('myApp.services', [])
|
angular.module('myApp.services', [])
|
||||||
|
|
||||||
.service('AppConfigManager', function ($q) {
|
|
||||||
var testPrefix = Config.Modes.test ? 't_' : '';
|
|
||||||
var cache = {};
|
|
||||||
var useCs = !!(window.chrome && chrome.storage && chrome.storage.local);
|
|
||||||
var useLs = !useCs && !!window.localStorage;
|
|
||||||
|
|
||||||
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] = testPrefix + 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[testPrefix + key] = JSON.stringify(value);
|
|
||||||
cache[testPrefix + 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] = testPrefix + 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('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService) {
|
.service('AppUsersManager', function ($rootScope, $modal, $modalStack, $filter, $q, MtpApiFileManager, MtpApiManager, RichTextProcessor, SearchIndexManager, ErrorService) {
|
||||||
var users = {},
|
var users = {},
|
||||||
cachedPhotoLocations = {},
|
cachedPhotoLocations = {},
|
||||||
@ -2176,7 +2057,7 @@ angular.module('myApp.services', [])
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.service('AppPhotosManager', function ($modal, $window, $timeout, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager) {
|
.service('AppPhotosManager', function ($modal, $window, $timeout, $rootScope, MtpApiManager, MtpApiFileManager, AppUsersManager, FileManager) {
|
||||||
var photos = {};
|
var photos = {};
|
||||||
|
|
||||||
function savePhoto (apiPhoto) {
|
function savePhoto (apiPhoto) {
|
||||||
@ -2373,48 +2254,25 @@ angular.module('myApp.services', [])
|
|||||||
secret: fullPhotoSize.location.secret
|
secret: fullPhotoSize.location.secret
|
||||||
};
|
};
|
||||||
|
|
||||||
if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) {
|
FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) {
|
||||||
|
MtpApiFileManager.downloadFile(
|
||||||
chrome.fileSystem.chooseEntry({
|
fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {
|
||||||
type: 'saveFile',
|
mime: mimeType,
|
||||||
suggestedName: fileName,
|
toFileEntry: writableFileEntry
|
||||||
accepts: [{
|
}).then(function (url) {
|
||||||
mimeTypes: [mimeType],
|
console.log('file save done');
|
||||||
extensions: [ext]
|
|
||||||
}]
|
|
||||||
}, function (writableFileEntry) {
|
|
||||||
var downloadPromise = MtpApiFileManager.downloadFile(fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {
|
|
||||||
mime: mimeType,
|
|
||||||
toFileEntry: writableFileEntry
|
|
||||||
});
|
|
||||||
downloadPromise.then(function (url) {
|
|
||||||
console.log('file save done');
|
|
||||||
}, function (e) {
|
|
||||||
console.log('photo download failed', e);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var downloadPromise = MtpApiFileManager.downloadFile(fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType});
|
|
||||||
|
|
||||||
downloadPromise.then(function (url) {
|
|
||||||
|
|
||||||
var a = $('<a>Download</a>')
|
|
||||||
.css({position: 'absolute', top: 1, left: 1})
|
|
||||||
.attr('href', url)
|
|
||||||
.attr('target', '_blank')
|
|
||||||
.attr('download', fileName)
|
|
||||||
.appendTo('body');
|
|
||||||
|
|
||||||
a[0].dataset.downloadurl = [mimeType, fileName, url].join(':');
|
|
||||||
a[0].click();
|
|
||||||
$timeout(function () {
|
|
||||||
a.remove();
|
|
||||||
}, 100);
|
|
||||||
}, function (e) {
|
}, function (e) {
|
||||||
console.log('photo download failed', e);
|
console.log('photo download failed', e);
|
||||||
});
|
});
|
||||||
}
|
}, function () {
|
||||||
|
MtpApiFileManager.downloadFile(
|
||||||
|
fullPhotoSize.location.dc_id, inputFileLocation, fullPhotoSize.size, {mime: mimeType}
|
||||||
|
).then(function (url) {
|
||||||
|
FileManager.download(url, mimeType, fileName);
|
||||||
|
}, function (e) {
|
||||||
|
console.log('photo download failed', e);
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.openPhoto = openPhoto;
|
$rootScope.openPhoto = openPhoto;
|
||||||
@ -2433,7 +2291,7 @@ angular.module('myApp.services', [])
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
.service('AppVideoManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager) {
|
.service('AppVideoManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager, FileManager) {
|
||||||
var videos = {};
|
var videos = {};
|
||||||
var videosForHistory = {};
|
var videosForHistory = {};
|
||||||
|
|
||||||
@ -2551,31 +2409,21 @@ angular.module('myApp.services', [])
|
|||||||
mimeType = 'video/mpeg4',
|
mimeType = 'video/mpeg4',
|
||||||
fileName = 'video' + videoID + '.' + ext;
|
fileName = 'video' + videoID + '.' + ext;
|
||||||
|
|
||||||
if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) {
|
FileManager.chooseSave(fileName, ext, mimeType).then(function (writableFileEntry) {
|
||||||
|
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {
|
||||||
chrome.fileSystem.chooseEntry({
|
mime: mimeType,
|
||||||
type: 'saveFile',
|
toFileEntry: writableFileEntry
|
||||||
suggestedName: fileName,
|
|
||||||
accepts: [{
|
|
||||||
mimeTypes: [mimeType],
|
|
||||||
extensions: [ext]
|
|
||||||
}]
|
|
||||||
}, function (writableFileEntry) {
|
|
||||||
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {
|
|
||||||
mime: mimeType,
|
|
||||||
toFileEntry: writableFileEntry
|
|
||||||
});
|
|
||||||
downloadPromise.then(function (url) {
|
|
||||||
delete historyVideo.progress;
|
|
||||||
console.log('file save done');
|
|
||||||
}, function (e) {
|
|
||||||
console.log('video download failed', e);
|
|
||||||
historyVideo.progress.enabled = false;
|
|
||||||
}, updateDownloadProgress);
|
|
||||||
|
|
||||||
historyVideo.progress.cancel = downloadPromise.cancel;
|
|
||||||
});
|
});
|
||||||
} else {
|
downloadPromise.then(function (url) {
|
||||||
|
delete historyVideo.progress;
|
||||||
|
console.log('file save done');
|
||||||
|
}, function (e) {
|
||||||
|
console.log('video download failed', e);
|
||||||
|
historyVideo.progress.enabled = false;
|
||||||
|
}, updateDownloadProgress);
|
||||||
|
|
||||||
|
historyVideo.progress.cancel = downloadPromise.cancel;
|
||||||
|
}, function () {
|
||||||
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {mime: mimeType});
|
var downloadPromise = MtpApiFileManager.downloadFile(video.dc_id, inputFileLocation, video.size, {mime: mimeType});
|
||||||
|
|
||||||
downloadPromise.then(function (url) {
|
downloadPromise.then(function (url) {
|
||||||
@ -2586,25 +2434,14 @@ angular.module('myApp.services', [])
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var a = $('<a>Download</a>')
|
FileManager.download(url, mimeType, fileName);
|
||||||
.css({position: 'absolute', top: 1, left: 1})
|
|
||||||
.attr('href', url)
|
|
||||||
.attr('target', '_blank')
|
|
||||||
.attr('download', fileName)
|
|
||||||
.appendTo('body');
|
|
||||||
|
|
||||||
a[0].dataset.downloadurl = [mimeType, fileName, url].join(':');
|
|
||||||
a[0].click();
|
|
||||||
$timeout(function () {
|
|
||||||
a.remove();
|
|
||||||
}, 100);
|
|
||||||
}, function (e) {
|
}, function (e) {
|
||||||
console.log('video download failed', e);
|
console.log('video download failed', e);
|
||||||
historyVideo.progress.enabled = false;
|
historyVideo.progress.enabled = false;
|
||||||
}, updateDownloadProgress);
|
}, updateDownloadProgress);
|
||||||
|
|
||||||
historyVideo.progress.cancel = downloadPromise.cancel;
|
historyVideo.progress.cancel = downloadPromise.cancel;
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.openVideo = openVideo;
|
$rootScope.openVideo = openVideo;
|
||||||
@ -2618,7 +2455,7 @@ angular.module('myApp.services', [])
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager) {
|
.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, FileManager) {
|
||||||
var docs = {};
|
var docs = {};
|
||||||
var docsForHistory = {};
|
var docsForHistory = {};
|
||||||
|
|
||||||
@ -2693,34 +2530,23 @@ angular.module('myApp.services', [])
|
|||||||
$rootScope.$broadcast('history_update');
|
$rootScope.$broadcast('history_update');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ext = (doc.file_name.split('.', 2) || [])[1] || '';
|
||||||
if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) {
|
FileManager.chooseSave(doc.file_name, ext, doc.mime_type).then(function (writableFileEntry) {
|
||||||
var ext = (doc.file_name.split('.', 2) || [])[1] || '';
|
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
|
||||||
|
mime: doc.mime_type,
|
||||||
chrome.fileSystem.chooseEntry({
|
toFileEntry: writableFileEntry
|
||||||
type: 'saveFile',
|
|
||||||
suggestedName: doc.file_name,
|
|
||||||
accepts: [{
|
|
||||||
mimeTypes: [doc.mime_type],
|
|
||||||
extensions: [ext]
|
|
||||||
}]
|
|
||||||
}, function (writableFileEntry) {
|
|
||||||
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {
|
|
||||||
mime: doc.mime_type,
|
|
||||||
toFileEntry: writableFileEntry
|
|
||||||
});
|
|
||||||
|
|
||||||
downloadPromise.then(function (url) {
|
|
||||||
delete historyDoc.progress;
|
|
||||||
console.log('file save done');
|
|
||||||
}, function (e) {
|
|
||||||
console.log('document download failed', e);
|
|
||||||
historyDoc.progress.enabled = false;
|
|
||||||
}, updateDownloadProgress);
|
|
||||||
|
|
||||||
historyDoc.progress.cancel = downloadPromise.cancel;
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
|
downloadPromise.then(function (url) {
|
||||||
|
delete historyDoc.progress;
|
||||||
|
console.log('file save done');
|
||||||
|
}, function (e) {
|
||||||
|
console.log('document download failed', e);
|
||||||
|
historyDoc.progress.enabled = false;
|
||||||
|
}, updateDownloadProgress);
|
||||||
|
|
||||||
|
historyDoc.progress.cancel = downloadPromise.cancel;
|
||||||
|
}, function () {
|
||||||
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {mime: doc.mime_type});
|
var downloadPromise = MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, {mime: doc.mime_type});
|
||||||
|
|
||||||
downloadPromise.then(function (url) {
|
downloadPromise.then(function (url) {
|
||||||
@ -2734,18 +2560,7 @@ angular.module('myApp.services', [])
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var a = $('<a>Download</a>')
|
FileManager.download(url, doc.mime_type, doc.file_name);
|
||||||
.css({position: 'absolute', top: 1, left: 1})
|
|
||||||
.attr('href', url)
|
|
||||||
.attr('target', '_blank')
|
|
||||||
.attr('download', doc.file_name)
|
|
||||||
.appendTo('body');
|
|
||||||
|
|
||||||
a[0].dataset.downloadurl = [doc.mime_type, doc.file_name, url].join(':');
|
|
||||||
a[0].click();
|
|
||||||
$timeout(function () {
|
|
||||||
a.remove();
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
}, function (e) {
|
}, function (e) {
|
||||||
console.log('document download failed', e);
|
console.log('document download failed', e);
|
||||||
@ -2753,7 +2568,7 @@ angular.module('myApp.services', [])
|
|||||||
}, updateDownloadProgress);
|
}, updateDownloadProgress);
|
||||||
|
|
||||||
historyDoc.progress.cancel = downloadPromise.cancel;
|
historyDoc.progress.cancel = downloadPromise.cancel;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$rootScope.downloadDoc = downloadDoc;
|
$rootScope.downloadDoc = downloadDoc;
|
||||||
@ -3354,7 +3169,7 @@ angular.module('myApp.services', [])
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.service('NotificationsManager', function ($rootScope, $window, $timeout, $interval, $q, MtpApiManager, AppPeersManager, IdleManager, AppConfigManager) {
|
.service('NotificationsManager', function ($rootScope, $window, $timeout, $interval, $q, MtpApiManager, AppPeersManager, IdleManager, Storage) {
|
||||||
|
|
||||||
var notificationsUiSupport = 'Notification' in window;
|
var notificationsUiSupport = 'Notification' in window;
|
||||||
var notificationsShown = {};
|
var notificationsShown = {};
|
||||||
@ -3493,13 +3308,13 @@ angular.module('myApp.services', [])
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfigManager.get('notify_nosound', 'notify_volume').then(function (settings) {
|
Storage.get('notify_nosound', 'notify_volume').then(function (settings) {
|
||||||
if (!settings[0] && settings[1] === false || settings[1] > 0) {
|
if (!settings[0] && settings[1] === false || settings[1] > 0) {
|
||||||
playSound(settings[1] || 0.5);
|
playSound(settings[1] || 0.5);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
AppConfigManager.get('notify_nodesktop').then(function (noShow) {
|
Storage.get('notify_nodesktop').then(function (noShow) {
|
||||||
if (noShow) {
|
if (noShow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3688,7 +3503,7 @@ angular.module('myApp.services', [])
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
.service('ChangelogNotifyService', function (AppConfigManager, $rootScope, $http, $modal) {
|
.service('ChangelogNotifyService', function (Storage, $rootScope, $http, $modal) {
|
||||||
|
|
||||||
function versionCompare (ver1, ver2) {
|
function versionCompare (ver1, ver2) {
|
||||||
if (typeof ver1 !== 'string') {
|
if (typeof ver1 !== 'string') {
|
||||||
@ -3718,12 +3533,12 @@ angular.module('myApp.services', [])
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkUpdate () {
|
function checkUpdate () {
|
||||||
AppConfigManager.get('last_version').then(function (lastVersion) {
|
Storage.get('last_version').then(function (lastVersion) {
|
||||||
if (lastVersion != Config.App.version) {
|
if (lastVersion != Config.App.version) {
|
||||||
if (lastVersion) {
|
if (lastVersion) {
|
||||||
showChangelog(lastVersion);
|
showChangelog(lastVersion);
|
||||||
}
|
}
|
||||||
AppConfigManager.set({last_version: Config.App.version});
|
Storage.set({last_version: Config.App.version});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
CACHE MANIFEST
|
CACHE MANIFEST
|
||||||
|
|
||||||
# 12
|
# 15
|
||||||
|
|
||||||
NETWORK:
|
NETWORK:
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user