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']) @@ -1233,17 +1233,7 @@ angular.module('myApp.directives', ['myApp.filters'])
if (src.substr(0, 5) == 'data:') {
remove = true;
src = src.substr(5).split(';');
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});
var blob = dataUrlToBlob(src);
ErrorService.confirm({type: 'FILE_CLIPBOARD_PASTE'}).then(function () {
$scope.draftMessage.files = [blob];
$scope.draftMessage.isMedia = true;

46
app/js/lib/bin_utils.js

@ -92,6 +92,52 @@ function uint6ToBase64 (nUint6) { @@ -92,6 +92,52 @@ function uint6ToBase64 (nUint6) {
: 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) {
var len = bytes1.length;
if (len != bytes2.length) {

6
app/js/lib/mtproto_wrapper.js

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
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 = {},
cachedUploadNetworkers = {},
cachedExportPromise = {},
@ -65,9 +65,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) @@ -65,9 +65,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto'])
}
if (cache[dcID] !== undefined) {
return {then: function (cb) {
cb(cache[dcID]);
}};
return qSync.when(cache[dcID]);
}
var akk = 'dc' + dcID + '_auth_key',

178
app/js/lib/ng_utils.js

@ -33,10 +33,23 @@ angular.module('izhukov.utils', []) @@ -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.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder;
var buggyUnknownBlob = navigator.userAgent.indexOf('Safari') != -1;
var blobSupported = true;
@ -61,20 +74,6 @@ angular.module('izhukov.utils', []) @@ -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) {
var deferred = $q.defer();
@ -182,42 +181,68 @@ angular.module('izhukov.utils', []) @@ -182,42 +181,68 @@ angular.module('izhukov.utils', [])
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) {
if (window.navigator && navigator.msSaveBlob !== undefined) {
window.navigator.msSaveBlob(blob, fileName);
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 {
console.error('Download click error', e);
anchor[0].click();
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) {
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 () {
$(anchor).remove();
}, 100);
$timeout(function () {
$(anchor).remove();
}, 100);
});
}
return {
@ -228,6 +253,8 @@ angular.module('izhukov.utils', []) @@ -228,6 +253,8 @@ angular.module('izhukov.utils', [])
getFakeFileWriter: getFakeFileWriter,
chooseSave: chooseSaveFile,
getUrl: getUrl,
getDataUrl: getDataUrl,
getFileCorrectUrl: getFileCorrectUrl,
download: downloadFile
};
})
@ -237,11 +264,15 @@ angular.module('izhukov.utils', []) @@ -237,11 +264,15 @@ angular.module('izhukov.utils', [])
$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;
var dbName = 'cachedFiles';
var dbStoreName = 'files';
var dbVersion = 1;
var openDbPromise;
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 () {
return storageIsAvailable;
@ -307,15 +338,24 @@ angular.module('izhukov.utils', []) @@ -307,15 +338,24 @@ angular.module('izhukov.utils', [])
function saveFile (fileName, blob) {
return openDatabase().then(function (db) {
if (!storeBlobsAvailable) {
return saveFileBase64(db, fileName, blob);
}
try {
var deferred = $q.defer(),
objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName),
var objectStore = db.transaction([dbStoreName], IDBTransaction.READ_WRITE || 'readwrite').objectStore(dbStoreName),
request = objectStore.put(blob, fileName);
} catch (error) {
if (storeBlobsAvailable) {
storeBlobsAvailable = false;
return saveFileBase64(db, fileName, blob);
}
storageIsAvailable = false;
return $q.reject(error);
}
var deferred = $q.defer();
request.onsuccess = function (event) {
deferred.resolve(blob);
};
@ -328,6 +368,38 @@ angular.module('izhukov.utils', []) @@ -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) {
return openDatabase().then(function (db) {
var deferred = $q.defer(),
@ -335,10 +407,14 @@ angular.module('izhukov.utils', []) @@ -335,10 +407,14 @@ angular.module('izhukov.utils', [])
request = objectStore.get(fileName);
request.onsuccess = function (event) {
if (event.target.result === undefined) {
var result = event.target.result;
if (result === undefined) {
deferred.reject();
} else if (typeof result === 'string' &&
result.substr(0, 5) === 'data:') {
deferred.resolve(dataUrlToBlob(result));
} 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']) @@ -2500,9 +2500,11 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
});
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;
historyVideo.url = $sce.trustAsResourceUrl(url);
historyVideo.downloaded = true;
console.log('video save done');
}, function (e) {
@ -2655,9 +2657,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2655,9 +2657,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
});
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;
historyDoc.url = $sce.trustAsResourceUrl(url);
historyDoc.downloaded = true;
console.log('file save done');
}, function (e) {
@ -2771,9 +2774,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils']) @@ -2771,9 +2774,10 @@ angular.module('myApp.services', ['myApp.i18n', 'izhukov.utils'])
});
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;
historyAudio.url = $sce.trustAsResourceUrl(url);
historyAudio.downloaded = true;
console.log('audio save done');
}, function (e) {

Loading…
Cancel
Save