diff --git a/src/js/app.js b/src/js/app.js index 745ceef50..503da9f52 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -19,7 +19,8 @@ var modules = [ 'copayApp.controllers', 'copayApp.directives', 'copayApp.addons', - 'bitcoincom.directives' + 'bitcoincom.directives', + 'bitcoincom.services' ]; var copayApp = window.copayApp = angular.module('copayApp', modules); @@ -30,3 +31,4 @@ angular.module('copayApp.controllers', []); angular.module('copayApp.directives', []); angular.module('copayApp.addons', []); angular.module('bitcoincom.directives', []); +angular.module('bitcoincom.services', []); diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js index 171c71847..6df56c943 100644 --- a/src/js/controllers/walletDetails.js +++ b/src/js/controllers/walletDetails.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService) { +angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicHistory, profileService, lodash, configService, platformInfo, walletService, txpModalService, externalLinkService, popupService, addressbookService, sendFlowService, storageService, $ionicScrollDelegate, $window, bwcError, gettextCatalog, timeService, feeService, appConfigService, rateService, walletHistoryService) { var HISTORY_SHOW_LIMIT = 10; var currentTxHistoryPage = 0; @@ -8,8 +8,11 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun // For gradual migration for doing it properly $scope.vm = { - gettingInitialHistory: false, - updatingTxHistory: false + gettingCachedHistory: true, + gettingInitialHistory: true, + updatingTxHistory: false, + //updateTxHistoryError: false + updateTxHistoryFailed: false }; $scope.amountIsCollapsible = false; @@ -30,7 +33,6 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.txps = []; $scope.updatingStatus = false; $scope.updateStatusError = null; - $scope.updateTxHistoryError = null; $scope.updatingTxHistoryProgress = 0; $scope.wallet = null; $scope.walletId = ''; @@ -192,7 +194,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var updateTxHistory = function(cb) { if (!cb) cb = function() {}; - $scope.updateTxHistoryError = false; + $scope.vm.updateTxHistoryFailed = false; $scope.updatingTxHistoryProgress = 0; feeService.getFeeLevels($scope.wallet.coin, function(err, levels) { @@ -202,10 +204,10 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.vm.gettingInitialHistory = false; if (err) { $scope.txHistory = null; - $scope.updateTxHistoryError = true; + $scope.vm.updateTxHistoryFailed = true; return; } - + applyCurrencyAliases(txHistory); var config = configService.getSync(); @@ -214,7 +216,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin); t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode; }); - + console.log('pagination Got tx history old way'); $scope.completeTxHistory = txHistory; $scope.showHistory(); @@ -230,12 +232,77 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var defaults = configService.getDefaults(); var configCache = configService.getSync(); - lodash.each(txHistory, function(t) { - t.amountUnitStr = $scope.wallet.coin == 'btc' + lodash.each(txHistory, function onTx(tx) { + tx.amountUnitStr = $scope.wallet.coin == 'btc' ? (configCache.bitcoinAlias || defaults.bitcoinAlias) : (configCache.bitcoinCashAlias || defaults.bitcoinCashAlias); - t.amountUnitStr = t.amountUnitStr.toUpperCase(); + tx.amountUnitStr = tx.amountUnitStr.toUpperCase(); + }); + } + + function formatTxHistoryForDisplay(txHistory) { + applyCurrencyAliases(txHistory); + + var config = configService.getSync(); + var fiatCode = config.wallet.settings.alternativeIsoCode; + lodash.each(txHistory, function(t) { + var r = rateService.toFiat(t.amount, fiatCode, $scope.wallet.coin); + t.alternativeAmountStr = r.toFixed(2) + ' ' + fiatCode; + }); + } + + function updateTxHistoryUsingCachedData() { + walletHistoryService.getCachedTxHistory($scope.wallet.id, function onGetCachedTxHistory(err, txHistory){ + $scope.vm.gettingCachedHistory = false; + if (err) { + // Don't display an error because we are also requesting the histroy. + $log.error('Error getting cached tx history.', err); + return; + } + + if (!txHistory) { + $log.debug('No cached tx history.'); + return; + } + + console.log('pagination Got cached txs, count: ', txHistory.length); + + /* + var shortHistory = []; + if (txHistory.length > 0) { + shortHistory = [ + txHistory[0] + ]; + } + txHistory = shortHistory; + */ + + formatTxHistoryForDisplay(txHistory); + + $scope.completeTxHistory = txHistory; + $scope.showHistory(); + $scope.$apply(); + console.log('pagination displayed cached history.'); + }); + } + + function updateTxHistoryFromSmallCache(getLatest) { + walletHistoryService.updateTxHistoryByPage($scope.wallet, getLatest, true, function onUpdateTxHistoryByPage(err, txHistory) { + console.log('pagination returned'); + $scope.vm.gettingInitialHistory = false; + if (err) { + console.error('pagination Failed to get history.', err); + $scope.txHistory = null; + $scope.vm.updateTxHistoryFailed = true; + return; + } + console.log('pagination txs returned in history: ' + txHistory.length); + formatTxHistoryForDisplay(txHistory); + + $scope.completeTxHistory = txHistory; + $scope.showHistory(); + $scope.$apply(); }); } @@ -308,7 +375,8 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun $scope.updateAll = function(force, cb)  { updateStatus(force); - updateTxHistory(cb); + //updateTxHistory(cb); + updateTxHistoryFromSmallCache(); }; $scope.hideToggle = function() { @@ -450,6 +518,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun var refreshInterval; $scope.$on("$ionicView.afterEnter", function(event, data) { + updateTxHistoryUsingCachedData(); $scope.updateAll(); refreshAmountSection(); refreshInterval = $interval($scope.onRefresh, 10 * 1000); diff --git a/src/js/services/wallet-history.service.js b/src/js/services/wallet-history.service.js new file mode 100644 index 000000000..3679dd088 --- /dev/null +++ b/src/js/services/wallet-history.service.js @@ -0,0 +1,129 @@ +'use strict'; + +(function(){ + + angular + .module('bitcoincom.services') + .factory('walletHistoryService', walletHistoryService); + + function walletHistoryService(configService, storageService, lodash, $log, txFormatService) { + // 8 Transactions fit on an iPhone Plus screen when in Wallet Details. + // Make it a little bit bigger so it doesn't have to load more immediately + var PAGE_SIZE = 50 / 1.5; + // How much to overlap on each end of the page, for mitigating inconsistent sort order. + var PAGE_OVERLAP_FRACTION = 0.5; + + var SAFE_CONFIRMATIONS = 6; + + var service = { + getCachedTxHistory: getCachedTxHistory, + updateTxHistoryByPage: updateTxHistoryByPage + }; + return service; + + /** + * @param {string} walletId + * @param {function(error, txs)} cb + */ + function getCachedTxHistory(walletId, cb) { + storageService.getTxHistory(walletId, function onGetTxHistory(err, txHistoryString){ + if (err) { + return cb(err); + } + + if (!txHistoryString) { + return cb(null, txHistoryString); + } + + try { + var txHistory = JSON.parse(txHistoryString); + return cb(null, txHistory); + } catch (e) { + $log.error('Failed to parse tx history.', e); + return cb(e); + } + }); + } + + function processNewTxs(wallet, txs) { + var now = Math.floor(Date.now() / 1000); + var txHistoryUnique = {}; + var processedTxs = []; + wallet.hasUnsafeConfirmed = false; + + lodash.each(txs, function(tx) { + tx = txFormatService.processTx(wallet.coin, tx); + + // no future transactions... + if (tx.time > now) + tx.time = now; + + if (tx.confirmations >= SAFE_CONFIRMATIONS) { + tx.safeConfirmed = SAFE_CONFIRMATIONS + '+'; + } else { + tx.safeConfirmed = false; + wallet.hasUnsafeConfirmed = true; + } + + if (tx.note) { + delete tx.note.encryptedEditedByName; + delete tx.note.encryptedBody; + } + + if (!txHistoryUnique[tx.txid]) { + processedTxs.push(tx); + txHistoryUnique[tx.txid] = true; + } else { + $log.debug('Ignoring duplicate TX in history: ' + tx.txid) + } + }); + + // Update notes? + + return processedTxs; + }; + + /** + * A small cache of up to the 50 most recent transactions + */ + function saveTxHistory(processedTxs, walletId) { + storageService.setTxHistory(processedTxs, walletId, function onSetTxHistory(error){ + // Looks like callback only gets called on error + $log.error('pagination Failed to save tx history.', error); + }); + + } + + // Only clear the cache once we have received new transactions from the server. + function updateTxHistoryByPage(wallet, getLatest, flushCacheOnNew, cb) { + var skip = 0; + var limit = Math.floor(PAGE_SIZE * (1 + PAGE_OVERLAP_FRACTION)); + + var opts = { + skip: skip, + limit: limit + }; + wallet.getTxHistory(opts, function onTxHistory(err, txsFromServer) { + if (err) { + return cb(err); + } + + if (!txsFromServer.length) { + return cb(null, []); + } + + var processedTxs = processNewTxs(wallet, txsFromServer); + + if (getLatest) { + console.log('pagination Saving retrieved txs.'); + saveTxHistory(wallet, processedTxs); + } + + return cb(null, processedTxs); + }); + } + + } + + +})(); \ No newline at end of file diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 4d293a01f..6f0c93697 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -398,6 +398,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var skipped = 0; + function fixTxsUnit(txs) { + if (!txs || !txs[0] || !txs[0].amountStr) return; + + var cacheCoin = txs[0].amountStr.split(' ')[1]; + + if (cacheCoin == 'bits') { + + $log.debug('Fixing Tx Cache Unit to: ' + wallet.coin) + lodash.each(txs, function(tx) { + tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount); + tx.feeStr = txFormatService.formatAmountStr(wallet.coin, tx.fees); + }); + } + }; + var updateLocalTxHistory = function(wallet, opts, cb) { var FIRST_LIMIT = 5; var LIMIT = 50; @@ -408,25 +423,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var progressFn = opts.progressFn || function() {}; var foundLimitTx = false; - if (opts.feeLevels) { opts.lowAmount = root.getLowAmount(wallet, opts.feeLevels); } - var fixTxsUnit = function(txs) { - if (!txs || !txs[0] || !txs[0].amountStr) return; - - var cacheCoin = txs[0].amountStr.split(' ')[1]; - - if (cacheCoin == 'bits') { - - $log.debug('Fixing Tx Cache Unit to: ' + wallet.coin) - lodash.each(txs, function(tx) { - tx.amountStr = txFormatService.formatAmountStr(wallet.coin, tx.amount); - tx.feeStr = txFormatService.formatAmountStr(wallet.coin, tx.fees); - }); - } - }; getSavedTxs(walletId, function(err, txsFromLocal) { if (err) return cb(err); @@ -437,13 +437,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim var endingTxid = confirmedTxs[0] ? confirmedTxs[0].txid : null; var endingTs = confirmedTxs[0] ? confirmedTxs[0].time : null; - $log.debug('Confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length); + console.log('pagination Hard confirmed TXs. Got:' + confirmedTxs.length + '/' + txsFromLocal.length); // First update progressFn(txsFromLocal, 0); wallet.completeHistory = txsFromLocal; function getNewTxs(newTxs, skip, next) { + console.log('pagination getNewTxs skip: ' + skip); getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res) { if (err) { $log.warn(bwcError.msg(err, 'Server Error')); //TODO @@ -456,6 +457,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return next(err); } + console.log('pagination Result count: ' + res.length); // Check if new txs are founds, if yes, lets investigate in the 50 next // To be sure we are not missing txs by sorting (maybe a new tx is after the "endingTxid" var newDiscoveredTxs = res.filter(function (x) { @@ -464,7 +466,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }).length == 0; }); - $log.debug('Discovering TXs. Got:' + newDiscoveredTxs.length); + console.log('pagination Discovering new TXs. Got:' + newDiscoveredTxs.length); var shouldContinue = newDiscoveredTxs.length > 0; @@ -477,7 +479,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim skip = skip + requestLimit; - $log.debug('Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue); + console.log('pagination Syncing TXs. Got:' + newTxs.length + ' Skip:' + skip, ' EndingTxid:', endingTxid, ' Continue:', shouldContinue); // TODO Dirty // do not sync all history, just looking for a single TX. @@ -499,7 +501,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim skipped = skip; if (!shouldContinue) { - $log.debug('Finished Sync: New / soft confirmed Txs: ' + newTxs.length); + console.log('pagination Finished Sync: New / soft confirmed Txs: ' + newTxs.length); return next(null, newTxs); } @@ -545,6 +547,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim getNewTxs([], skipped, function(err, txs) { if (err) return cb(err); + console.log(); createReceivedEvents(txs); var newHistory = lodash.uniq(lodash.compact(txs.concat(confirmedTxs)), function(x) { diff --git a/www/views/includes/walletInfo.html b/www/views/includes/walletInfo.html index aff10fc09..f97ebf9ed 100644 --- a/www/views/includes/walletInfo.html +++ b/www/views/includes/walletInfo.html @@ -5,7 +5,7 @@ + !walletNotRegistered && !updateStatusError && !vm.updateTxHistoryFailed">
Auditable diff --git a/www/views/walletDetails.html b/www/views/walletDetails.html index 6021467ee..2548aa4b9 100644 --- a/www/views/walletDetails.html +++ b/www/views/walletDetails.html @@ -284,13 +284,13 @@
+ ng-show="!txHistory[0] && !vm.gettingInitialHistory && !vm.updateTxHistoryFailed && !updateStatusError" translate> No transactions yet
+ ng-show="!txHistory[0] && !vm.gettingInitialHistory && vm.updateTxHistoryFailed" translate> Could not update transaction history
@@ -300,7 +300,7 @@ {{updatingTxHistoryProgress}} transactions downloaded
-
+