Browse Source

Improved Safari support

Added no-blob IndexedDB support. Disabled for Safari, because IDB is
very slow here.
Added FileManager.getFileCorrectUrl method for Safari
master
Igor Zhukov 10 years ago
parent
commit
10c6d37656
  1. 12
      app/js/directives.js
  2. 46
      app/js/lib/bin_utils.js
  3. 6
      app/js/lib/mtproto_wrapper.js
  4. 178
      app/js/lib/ng_utils.js
  5. 16
      app/js/services.js

12
app/js/directives.js

@ -1233,17 +1233,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (src.substr(0, 5) == 'data:') { if (src.substr(0, 5) == 'data:') {
remove = true; remove = true;
src = src.substr(5).split(';'); var blob = dataUrlToBlob(src);
var contentType = src[0];
var base64 = atob(src[1].split(',')[1]);
var array = new Uint8Array(base64.length);
for (var i = 0; i < base64.length; i++) {
array[i] = base64.charCodeAt(i);
}
var blob = new Blob([array], {type: contentType});
ErrorService.confirm({type: 'FILE_CLIPBOARD_PASTE'}).then(function () { ErrorService.confirm({type: 'FILE_CLIPBOARD_PASTE'}).then(function () {
$scope.draftMessage.files = [blob]; $scope.draftMessage.files = [blob];
$scope.draftMessage.isMedia = true; $scope.draftMessage.isMedia = true;

46
app/js/lib/bin_utils.js

@ -92,6 +92,52 @@ function uint6ToBase64 (nUint6) {
: 65; : 65;
} }
function base64ToBlob(base64str, mimeType) {
var sliceSize = 1024;
var byteCharacters = atob(base64str);
var bytesLength = byteCharacters.length;
var slicesCount = Math.ceil(bytesLength / sliceSize);
var byteArrays = new Array(slicesCount);
for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
var begin = sliceIndex * sliceSize;
var end = Math.min(begin + sliceSize, bytesLength);
var bytes = new Array(end - begin);
for (var offset = begin, i = 0 ; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return blobConstruct(byteArrays, mimeType);
}
function dataUrlToBlob(url) {
// var name = 'b64blob ' + url.length;
// console.time(name);
var urlParts = url.split(',');
var base64str = urlParts[1];
var mimeType = urlParts[0].split(':')[1].split(';')[0];
var blob = base64ToBlob(base64str, mimeType);
// console.timeEnd(name);
return blob;
}
function blobConstruct (blobParts, mimeType) {
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);
}
return blob;
}
function bytesCmp (bytes1, bytes2) { function bytesCmp (bytes1, bytes2) {
var len = bytes1.length; var len = bytes1.length;
if (len != bytes2.length) { if (len != bytes2.length) {

6
app/js/lib/mtproto_wrapper.js

@ -7,7 +7,7 @@
angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
.factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, MtpSingleInstanceService, ErrorService, $q) { .factory('MtpApiManager', function (Storage, MtpAuthorizer, MtpNetworkerFactory, MtpSingleInstanceService, ErrorService, qSync, $q) {
var cachedNetworkers = {}, var cachedNetworkers = {},
cachedUploadNetworkers = {}, cachedUploadNetworkers = {},
cachedExportPromise = {}, cachedExportPromise = {},
@ -65,9 +65,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
} }
if (cache[dcID] !== undefined) { if (cache[dcID] !== undefined) {
return {then: function (cb) { return qSync.when(cache[dcID]);
cb(cache[dcID]);
}};
} }
var akk = 'dc' + dcID + '_auth_key', var akk = 'dc' + dcID + '_auth_key',

178
app/js/lib/ng_utils.js

@ -33,10 +33,23 @@ angular.module('izhukov.utils', [])
}) })
.service('FileManager', function ($window, $q, $timeout) { .service('qSync', function () {
return {
when: function (result) {
return {then: function (cb) {
return cb(result);
}};
}
}
})
.service('FileManager', function ($window, $q, $timeout, qSync) {
$window.URL = $window.URL || $window.webkitURL; $window.URL = $window.URL || $window.webkitURL;
$window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder; $window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder;
var buggyUnknownBlob = navigator.userAgent.indexOf('Safari') != -1;
var blobSupported = true; var blobSupported = true;
@ -61,20 +74,6 @@ angular.module('izhukov.utils', [])
}); });
} }
function blobConstruct (blobParts, mimeType) {
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);
}
return blob;
}
function fileWriteData(fileWriter, bytes) { function fileWriteData(fileWriter, bytes) {
var deferred = $q.defer(); var deferred = $q.defer();
@ -182,42 +181,68 @@ angular.module('izhukov.utils', [])
return 'data:' + mimeType + ';base64,' + bytesToBase64(fileData); return 'data:' + mimeType + ';base64,' + bytesToBase64(fileData);
} }
function getDataUrl(blob) {
var deferred;
try {
var reader = new FileReader();
reader.onloadend = function() {
deferred.resolve(reader.result);
}
reader.readAsDataURL(blob);
} catch (e) {
return $q.reject(e);
}
deferred = $q.defer();
return deferred.promise;
}
function getFileCorrectUrl(blob, mimeType) {
if (buggyUnknownBlob) {
var mimeType = blob.type || blob.mimeType || mimeType || '';
if (!mimeType.match(/image\/(jpeg|gif|png|bmp)|video\/quicktime/)) {
return getDataUrl(blob);
}
}
return qSync.when(getUrl(blob, mimeType));
}
function downloadFile (blob, mimeType, fileName) { function downloadFile (blob, mimeType, fileName) {
if (window.navigator && navigator.msSaveBlob !== undefined) { if (window.navigator && navigator.msSaveBlob !== undefined) {
window.navigator.msSaveBlob(blob, fileName); window.navigator.msSaveBlob(blob, fileName);
return false; return false;
} }
getFileCorrectUrl(blob, mimeType).then(function (url) {
var anchor = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
anchor.href = url;
anchor.target = '_blank';
anchor.download = fileName;
if (anchor.dataset) {
anchor.dataset.downloadurl = ["video/quicktime", fileName, url].join(':');
}
$(anchor).css({position: 'absolute', top: 1, left: 1}).appendTo('body');
var url = getUrl(blob, mimeType);
var anchor = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
anchor.href = url;
anchor.target = '_blank';
anchor.download = fileName;
if (anchor.dataset) {
anchor.dataset.downloadurl = ["video/quicktime", fileName, url].join(':');
}
$(anchor).css({position: 'absolute', top: 1, left: 1}).appendTo('body');
try {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent(
'click', true, false, window, 0, 0, 0, 0, 0
, false, false, false, false, 0, null
);
anchor.dispatchEvent(clickEvent);
} catch (e) {
console.error('Download click error', e);
try { try {
console.error('Download click error', e); var clickEvent = document.createEvent('MouseEvents');
anchor[0].click(); clickEvent.initMouseEvent(
'click', true, false, window, 0, 0, 0, 0, 0
, false, false, false, false, 0, null
);
anchor.dispatchEvent(clickEvent);
} catch (e) { } catch (e) {
window.open(url, '_blank'); console.error('Download click error', e);
try {
console.error('Download click error', e);
anchor[0].click();
} catch (e) {
window.open(url, '_blank');
}
} }
} $timeout(function () {
$timeout(function () { $(anchor).remove();
$(anchor).remove(); }, 100);
}, 100); });
} }
return { return {
@ -228,6 +253,8 @@ angular.module('izhukov.utils', [])
getFakeFileWriter: getFakeFileWriter, getFakeFileWriter: getFakeFileWriter,
chooseSave: chooseSaveFile, chooseSave: chooseSaveFile,
getUrl: getUrl, getUrl: getUrl,
getDataUrl: getDataUrl,
getFileCorrectUrl: getFileCorrectUrl,
download: downloadFile download: downloadFile
}; };
}) })
@ -237,11 +264,15 @@ angular.module('izhukov.utils', [])
$window.indexedDB = $window.indexedDB || $window.webkitIndexedDB || $window.mozIndexedDB || $window.OIndexedDB || $window.msIndexedDB; $window.indexedDB = $window.indexedDB || $window.webkitIndexedDB || $window.mozIndexedDB || $window.OIndexedDB || $window.msIndexedDB;
$window.IDBTransaction = $window.IDBTransaction || $window.webkitIDBTransaction || $window.OIDBTransaction || $window.msIDBTransaction; $window.IDBTransaction = $window.IDBTransaction || $window.webkitIDBTransaction || $window.OIDBTransaction || $window.msIDBTransaction;
var dbName = 'cachedFiles', var dbName = 'cachedFiles';
dbStoreName = 'files', var dbStoreName = 'files';
dbVersion = 1, var dbVersion = 1;
openDbPromise, var openDbPromise;
storageIsAvailable = $window.indexedDB !== undefined && $window.IDBTransaction !== undefined; var storageIsAvailable = $window.indexedDB !== undefined &&
$window.IDBTransaction !== undefined &&
navigator.userAgent.indexOf('Safari') == -1;
// As of Safari 8.0 IndexedDB is REALLY slow, no point in it
var storeBlobsAvailable = storageIsAvailable || false;
function isAvailable () { function isAvailable () {
return storageIsAvailable; return storageIsAvailable;
@ -307,15 +338,24 @@ angular.module('izhukov.utils', [])
function saveFile (fileName, blob) { function saveFile (fileName, blob) {
return openDatabase().then(function (db) { return openDatabase().then(function (db) {
if (!storeBlobsAvailable) {
return saveFileBase64(db, fileName, blob);
}
try { try {
var deferred = $q.defer(), var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName),
objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName),
request = objectStore.put(blob, fileName); request = objectStore.put(blob, fileName);
} catch (error) { } catch (error) {
if (storeBlobsAvailable) {
storeBlobsAvailable = false;
return saveFileBase64(db, fileName, blob);
}
storageIsAvailable = false; storageIsAvailable = false;
return $q.reject(error); return $q.reject(error);
} }
var deferred = $q.defer();
request.onsuccess = function (event) { request.onsuccess = function (event) {
deferred.resolve(blob); deferred.resolve(blob);
}; };
@ -328,6 +368,38 @@ angular.module('izhukov.utils', [])
}); });
}; };
function saveFileBase64(db, fileName, blob) {
try {
var reader = new FileReader();
reader.readAsDataURL(blob);
} catch (e) {
storageIsAvailable = false;
return $q.reject();
}
var deferred = $q.defer();
reader.onloadend = function() {
try {
var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName),
request = objectStore.put(reader.result, fileName);
} catch (error) {
storageIsAvailable = false;
deferred.reject(error);
return;
};
request.onsuccess = function (event) {
deferred.resolve(blob);
};
request.onerror = function (error) {
deferred.reject(error);
};
}
return deferred.promise;
}
function getFile (fileName) { function getFile (fileName) {
return openDatabase().then(function (db) { return openDatabase().then(function (db) {
var deferred = $q.defer(), var deferred = $q.defer(),
@ -335,10 +407,14 @@ angular.module('izhukov.utils', [])
request = objectStore.get(fileName); request = objectStore.get(fileName);
request.onsuccess = function (event) { request.onsuccess = function (event) {
if (event.target.result === undefined) { var result = event.target.result;
if (result === undefined) {
deferred.reject(); deferred.reject();
} else if (typeof result === 'string' &&
result.substr(0, 5) === 'data:') {
deferred.resolve(dataUrlToBlob(result));
} else { } else {
deferred.resolve(event.target.result); deferred.resolve(result);
} }
}; };

16
app/js/services.js

@ -2500,9 +2500,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
downloadPromise.then(function (blob) { downloadPromise.then(function (blob) {
var url = FileManager.getUrl(blob, mimeType); FileManager.getFileCorrectUrl(blob, mimeType).then(function (url) {
historyVideo.url = $sce.trustAsResourceUrl(url);
});
delete historyVideo.progress; delete historyVideo.progress;
historyVideo.url = $sce.trustAsResourceUrl(url);
historyVideo.downloaded = true; historyVideo.downloaded = true;
console.log('video save done'); console.log('video save done');
}, function (e) { }, function (e) {
@ -2655,9 +2657,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
downloadPromise.then(function (blob) { downloadPromise.then(function (blob) {
var url = FileManager.getUrl(blob, doc.mime_type); FileManager.getFileCorrectUrl(blob, doc.mime_type).then(function (url) {
historyDoc.url = $sce.trustAsResourceUrl(url);
})
delete historyDoc.progress; delete historyDoc.progress;
historyDoc.url = $sce.trustAsResourceUrl(url);
historyDoc.downloaded = true; historyDoc.downloaded = true;
console.log('file save done'); console.log('file save done');
}, function (e) { }, function (e) {
@ -2771,9 +2774,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
}); });
downloadPromise.then(function (blob) { downloadPromise.then(function (blob) {
var url = FileManager.getUrl(blob, mimeType); FileManager.getFileCorrectUrl(blob, mimeType).then(function (url) {
historyAudio.url = $sce.trustAsResourceUrl(url);
});
delete historyAudio.progress; delete historyAudio.progress;
historyAudio.url = $sce.trustAsResourceUrl(url);
historyAudio.downloaded = true; historyAudio.downloaded = true;
console.log('audio save done'); console.log('audio save done');
}, function (e) { }, function (e) {

Loading…
Cancel
Save