Merge pull request #1850 from matiaspando/feature/backupFlag

Added the flag backupNeeded
This commit is contained in:
Matias Alejo Garcia 2014-12-02 11:31:39 -03:00
commit 3829d6692f
10 changed files with 139 additions and 92 deletions

View file

@ -1244,6 +1244,21 @@ label.postfix, span.postfix {
height: 80px; height: 80px;
} }
.need-backup {
background: #C0392A;
-moz-box-shadow: 1px 1px 0px 0px #A02F23;
box-shadow: 1px 1px 0px 0px #A02F23;
position: absolute;
top: 22px;
left: 0px;
width: 14px;
height: 14px;
border-radius: 100%;
font-size: 9px;
padding-top: 2px;
color: #fff;
}
a:hover .photo-container { a:hover .photo-container {
background: #34495E; background: #34495E;
color: #fff; color: #fff;

View file

@ -18,71 +18,72 @@ angular.module('copayApp.controllers').controller('ImportController',
$scope.$digest(); $scope.$digest();
} }
$scope.getFile = function() {
// If we use onloadend, we need to check the readyState. $scope.getFile = function() {
reader.onloadend = function(evt) { // If we use onloadend, we need to check the readyState.
if (evt.target.readyState == FileReader.DONE) { // DONE == 2 reader.onloadend = function(evt) {
var encryptedObj = evt.target.result; if (evt.target.readyState == FileReader.DONE) { // DONE == 2
var encryptedObj = evt.target.result;
updateStatus('Importing wallet - Procesing backup...');
identityService.importWallet(encryptedObj, $scope.password, {}, function(err) {
if (err) {
$scope.loading = false;
$scope.error = 'Could not read wallet. Please check your password';
}
});
}
}
};
$scope.import = function(form) {
$scope.loading = true;
if (form.$invalid) {
$scope.loading = false;
$scope.error = 'There is an error in the form';
return;
}
var backupFile = $scope.file;
var backupText = form.backupText.$modelValue;
var backupOldWallet = form.backupOldWallet.$modelValue;
var password = form.password.$modelValue;
if (backupOldWallet) {
backupText = backupOldWallet.value;
}
if (!backupFile && !backupText) {
$scope.loading = false;
$scope.error = 'Please, select your backup file';
return;
}
$scope.importOpts = {};
var skipFields = [];
if ($scope.skipPublicKeyRing)
skipFields.push('publicKeyRing');
if ($scope.skipTxProposals)
skipFields.push('txProposals');
if (skipFields)
$scope.importOpts.skipFields = skipFields;
if (backupFile) {
reader.readAsBinaryString(backupFile);
} else {
updateStatus('Importing wallet - Procesing backup...'); updateStatus('Importing wallet - Procesing backup...');
identityService.importWallet(encryptedObj, $scope.password, {}, function(err){ identityService.importWallet(encryptedObj, $scope.password, $scope.importOpts, function(err) {
if (err) { if (err) {
$scope.loading = false; $scope.loading = false;
$scope.error = 'Could not read wallet. Please check your password'; $scope.error = 'Could not read wallet. Please check your password';
} }
copay.Compatibility.deleteOldWallet(backupOldWallet);
}); });
} }
}; };
}; });
$scope.import = function(form) {
$scope.loading = true;
if (form.$invalid) {
$scope.loading = false;
$scope.error = 'There is an error in the form';
return;
}
var backupFile = $scope.file;
var backupText = form.backupText.$modelValue;
var backupOldWallet = form.backupOldWallet.$modelValue;
var password = form.password.$modelValue;
if (backupOldWallet) {
backupText = backupOldWallet.value;
}
if (!backupFile && !backupText) {
$scope.loading = false;
$scope.error = 'Please, select your backup file';
return;
}
$scope.importOpts = {};
var skipFields = [];
if ($scope.skipPublicKeyRing)
skipFields.push('publicKeyRing');
if ($scope.skipTxProposals)
skipFields.push('txProposals');
if (skipFields)
$scope.importOpts.skipFields = skipFields;
if (backupFile) {
reader.readAsBinaryString(backupFile);
} else {
updateStatus('Importing wallet - Procesing backup...');
identityService.importWallet(encryptedObj, $scope.password, $scope.importOpts, function(err){
if (err) {
$scope.loading = false;
$scope.error = 'Could not read wallet. Please check your password';
}
copay.Compatibility.deleteOldWallet(backupOldWallet);
});
}
};
});

View file

@ -28,8 +28,8 @@ angular.module('copayApp.controllers').controller('ProfileController', function(
$scope.init = function() { $scope.init = function() {
if ($rootScope.quotaPerItem) { if ($rootScope.quotaPerItem) {
$scope.perItem = $filter('noFractionNumber')($rootScope.quotaPerItem/1000,1); $scope.perItem = $filter('noFractionNumber')($rootScope.quotaPerItem / 1000, 1);
$scope.nrWallets =parseInt($rootScope.quotaItems) - 1; $scope.nrWallets = parseInt($rootScope.quotaItems) - 1;
} }
}; };
@ -37,13 +37,13 @@ angular.module('copayApp.controllers').controller('ProfileController', function(
if (!$rootScope.iden) return; if (!$rootScope.iden) return;
var wallets = $rootScope.iden.listWallets(); var wallets = $rootScope.iden.listWallets();
var max =$rootScope.quotaPerItem; var max = $rootScope.quotaPerItem;
_.each(wallets, function(w) { _.each(wallets, function(w) {
var bits = w.sizes().total; var bits = w.sizes().total;
w.kb = $filter('noFractionNumber')(bits/1000, 1); w.kb = $filter('noFractionNumber')(bits / 1000, 1);
if (max) { if (max) {
w.usage = $filter('noFractionNumber')(bits/max * 100, 0); w.usage = $filter('noFractionNumber')(bits / max * 100, 0);
} }
}); });
@ -73,15 +73,15 @@ angular.module('copayApp.controllers').controller('ProfileController', function(
}); });
}; };
$scope.deleteProfile = function () { $scope.deleteProfile = function() {
identityService.deleteProfile(function (err, res) { identityService.deleteProfile(function(err, res) {
if (err) { if (err) {
log.warn(err); log.warn(err);
notification.error('Error', 'Could not delete profile'); notification.error('Error', 'Could not delete profile');
return; return;
} }
$location.path('/'); $location.path('/');
setTimeout(function () { setTimeout(function() {
notification.error('Success', 'Profile successfully deleted'); notification.error('Success', 'Profile successfully deleted');
}, 1); }, 1);
}); });

View file

@ -62,6 +62,8 @@ function Identity(opts) {
this.walletIds = opts.walletIds || {}; this.walletIds = opts.walletIds || {};
this.wallets = opts.wallets || {}; this.wallets = opts.wallets || {};
this.focusedTimestamps = opts.focusedTimestamps || {}; this.focusedTimestamps = opts.focusedTimestamps || {};
this.backupNeeded = opts.backupNeeded || false;
}; };
@ -91,7 +93,9 @@ Identity.prototype.getName = function() {
* @return {undefined} * @return {undefined}
*/ */
Identity.create = function(opts, cb) { Identity.create = function(opts, cb) {
opts = _.extend({}, opts); opts = _.extend({
backupNeeded: true
}, opts);
var iden = new Identity(opts); var iden = new Identity(opts);
iden.store(_.extend(opts, { iden.store(_.extend(opts, {
@ -265,21 +269,36 @@ Identity.prototype.toObj = function() {
return _.extend({ return _.extend({
walletIds: _.isEmpty(this.wallets) ? this.walletsIds : _.keys(this.wallets), walletIds: _.isEmpty(this.wallets) ? this.walletsIds : _.keys(this.wallets),
}, },
_.pick(this, 'version', 'fullName', 'password', 'email', 'focusedTimestamps')); _.pick(this, 'version', 'fullName', 'password', 'email', 'backupNeeded', 'focusedTimestamps'));
}; };
Identity.prototype.exportEncryptedWithWalletInfo = function(opts) { Identity.prototype.exportEncryptedWithWalletInfo = function(opts) {
var crypto = opts.cryptoUtil || cryptoUtil; var crypto = opts.cryptoUtil || cryptoUtil;
return crypto.encrypt(this.password, this.exportWithWalletInfo(opts)); return crypto.encrypt(this.password, this.exportWithWalletInfo(opts));
}; };
Identity.prototype.setBackupNeeded = function() {
this.backupNeeded = true;
this.store({
noWallets: true
}, function() {});
}
Identity.prototype.setBackupDone = function() {
this.backupNeeded = false;
this.store({
noWallets: true
}, function() {});
}
Identity.prototype.exportWithWalletInfo = function(opts) { Identity.prototype.exportWithWalletInfo = function(opts) {
return _.extend({ return _.extend({
wallets: _.map(this.wallets, function(wallet) { wallets: _.map(this.wallets, function(wallet) {
return wallet.toObj(); return wallet.toObj();
}) })
}, },
_.pick(this, 'version', 'fullName', 'password', 'email') _.pick(this, 'version', 'fullName', 'password', 'email', 'backupNeeded')
); );
}; };
@ -288,15 +307,15 @@ Identity.prototype.exportWithWalletInfo = function(opts) {
* @param {Function} cb * @param {Function} cb
*/ */
Identity.prototype.store = function(opts, cb) { Identity.prototype.store = function(opts, cb) {
log.debug('Storing profile');
var self = this; var self = this;
opts = opts || {}; opts = opts || {};
var storeFunction = opts.failIfExists ? self.storage.createItem : self.storage.setItem; var storeFunction = opts.failIfExists ? self.storage.createItem : self.storage.setItem;
storeFunction.call(self.storage, this.getId(), this.toObj(), function(err) { storeFunction.call(self.storage, this.getId(), this.toObj(), function(err) {
if (err) return cb(err); if (err) {
return cb(err);
}
if (opts.noWallets) if (opts.noWallets)
return cb(); return cb();
@ -323,7 +342,7 @@ Identity.prototype.remove = function(opts, cb) {
if (err) return cb(err); if (err) return cb(err);
cb(); cb();
}); });
}, function (err) { }, function(err) {
if (err) return cb(err); if (err) return cb(err);
self.storage.removeItem(self.getId(), function(err) { self.storage.removeItem(self.getId(), function(err) {
@ -552,13 +571,16 @@ Identity.prototype.createWallet = function(opts, cb) {
var self = this; var self = this;
var w = new walletClass(opts); var w = new walletClass(opts);
self.bindWallet(w); self.bindWallet(w);
self.updateFocusedTimestamp(w.getId()); self.updateFocusedTimestamp(w.getId());
self.storeWallet(w, function(err) { self.storeWallet(w, function(err) {
if (err) return cb(err); if (err) return cb(err);
self.backupNeeded = true;
self.store({ self.store({
noWallets: true noWallets: true,
}, function(err) { }, function(err) {
return cb(err, w); return cb(err, w);
}); });

View file

@ -38,6 +38,7 @@ BackupService.prototype.profileEncrypted = function(iden) {
BackupService.prototype.profileDownload = function(iden) { BackupService.prototype.profileDownload = function(iden) {
var ew = this.profileEncrypted(iden); var ew = this.profileEncrypted(iden);
iden.setBackupDone();
var name = iden.fullName; var name = iden.fullName;
var filename = name + '-profile.json'; var filename = name + '-profile.json';
this._download(ew, name, filename) this._download(ew, name, filename)

View file

@ -48,6 +48,7 @@ angular.module('copayApp.services')
passphraseConfig: config.passphraseConfig, passphraseConfig: config.passphraseConfig,
failIfExists: true, failIfExists: true,
}, function(err, iden) { }, function(err, iden) {
if (err) return cb(err); if (err) return cb(err);
preconditions.checkState(iden); preconditions.checkState(iden);
root.bind(iden); root.bind(iden);
@ -102,7 +103,7 @@ angular.module('copayApp.services')
}); });
}; };
root.deleteProfile = function (cb) { root.deleteProfile = function(cb) {
$rootScope.iden.remove(null, cb); $rootScope.iden.remove(null, cb);
}; };

View file

@ -111,7 +111,14 @@ describe('Identity model', function() {
params: params params: params
}; };
}; };
var orig;
beforeEach(function() {
orig = Identity.prototype.store;
sinon.stub(Identity.prototype, 'store').yields(null);
});
afterEach(function() {
Identity.prototype.store = orig;
});
describe('new Identity()', function() { describe('new Identity()', function() {
it('returns an identity', function() { it('returns an identity', function() {
var iden = new Identity(getDefaultParams()); var iden = new Identity(getDefaultParams());
@ -124,7 +131,6 @@ describe('Identity model', function() {
it('should create and store identity', function() { it('should create and store identity', function() {
var args = createIdentity(); var args = createIdentity();
args.blockchain.on = sinon.stub(); args.blockchain.on = sinon.stub();
sinon.stub(Identity.prototype, 'store').yields(null);
Identity.create(args.params, function(err, iden) { Identity.create(args.params, function(err, iden) {
should.not.exist(err); should.not.exist(err);
should.exist(iden); should.exist(iden);
@ -240,7 +246,6 @@ describe('Identity model', function() {
args = createIdentity(); args = createIdentity();
args.params.noWallets = true; args.params.noWallets = true;
var old = Identity.prototype.createWallet; var old = Identity.prototype.createWallet;
sinon.stub(Identity.prototype, 'store').yields(null);
Identity.create(args.params, function(err, res) { Identity.create(args.params, function(err, res) {
iden = res; iden = res;
}); });
@ -297,7 +302,6 @@ describe('Identity model', function() {
args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}'); args.storage.getItem.onFirstCall().callsArgWith(1, null, '{"wallet": "fakeData"}');
var backup = Wallet.fromUntrustedObj; var backup = Wallet.fromUntrustedObj;
args.params.noWallets = true; args.params.noWallets = true;
sinon.stub(Identity.prototype, 'store').yields(null);
sinon.stub().returns(args.wallet); sinon.stub().returns(args.wallet);
var opts = { var opts = {
@ -390,8 +394,6 @@ describe('Identity model', function() {
beforeEach(function() { beforeEach(function() {
args = createIdentity(); args = createIdentity();
args.params.Async = net = sinon.stub(); args.params.Async = net = sinon.stub();
sinon.stub(Identity.prototype, 'store').yields(null);
net.cleanUp = sinon.spy(); net.cleanUp = sinon.spy();
net.on = sinon.stub(); net.on = sinon.stub();
net.start = sinon.spy(); net.start = sinon.spy();

View file

@ -76,6 +76,7 @@ describe("Angular services", function() {
a[Waddr] = 200; a[Waddr] = 200;
w.getBalance = sinon.stub().yields(null, 100000001, a, 90000002, 5); w.getBalance = sinon.stub().yields(null, 100000001, a, 90000002, 5);
//retuns values in DEFAULT UNIT(bits) //retuns values in DEFAULT UNIT(bits)
balanceService.update(w, function() { balanceService.update(w, function() {
var b = w.balanceInfo; var b = w.balanceInfo;
@ -90,7 +91,7 @@ describe("Angular services", function() {
expect(b.balanceByAddr[Waddr]).to.equal(2); expect(b.balanceByAddr[Waddr]).to.equal(2);
expect(b.safeUnspentCount).to.equal(5); expect(b.safeUnspentCount).to.equal(5);
expect(b.topAmount).to.equal(899800.02); expect(b.topAmount).to.equal(899800.02);
},false); }, false);
})); }));
}); });

View file

@ -16,10 +16,13 @@
<div class="menu" ng-mouseover="hoverIn()" ng-mouseleave="hoverOut()" <div class="menu" ng-mouseover="hoverIn()" ng-mouseleave="hoverOut()"
ng-click="hoverMenu = !hoverMenu"> ng-click="hoverMenu = !hoverMenu">
<a class="dropdown ellipsis text-gray" ng-class="{'hover': hoverMenu}"> <a class="dropdown ellipsis text-gray pr" ng-class="{'hover': hoverMenu}">
<div class="photo-container"> <div class="photo-container">
<img gravatar-src="'{{username}}'" gravatar-size="35"> <img gravatar-src="'{{username}}'" gravatar-size="35">
</div> </div>
<span class="need-backup" ng-if="!$root.needsEmailConfirmation && $root.iden.backupNeeded">
<i class="fi-alert vm"></i>
</span>
<span class="m15t">{{username}} </span> <span class="m15t">{{username}} </span>
<i class="icon-arrow-down2 size-16 vm"></i> <i class="icon-arrow-down2 size-16 vm"></i>
</a> </a>
@ -33,7 +36,8 @@
<i class="icon-download size-18 m10r"></i> {{'Import wallet'|translate}}</a></li> <i class="icon-download size-18 m10r"></i> {{'Import wallet'|translate}}</a></li>
<li class="divider"></li> <li class="divider"></li>
<li><a href="#!/profile" title="Profile"> <li><a href="#!/profile" title="Profile">
<i class="icon-person size-18 m10r"></i> {{'Profile'|translate}}</a></li> <i class="icon-person size-18 m10r"></i> {{'Profile'|translate}}<span class="size-10 text-warning" ng-if="!$root.needsEmailConfirmation && $root.iden.backupNeeded"> [ Needs Backup ]</span></a>
</li>
<li><a href="#!/" title="Close" ng-click="signout()"> <li><a href="#!/" title="Close" ng-click="signout()">
<i class="icon-power size-18 m10r"></i> {{'Close'|translate}}</a></li> <i class="icon-power size-18 m10r"></i> {{'Close'|translate}}</a></li>
</ul> </ul>

View file

@ -15,7 +15,7 @@
</a> </a>
</div> </div>
<div class="large-7 medium-7 columns"> <div class="large-7 medium-7 columns">
<h2>Backup Profile</h2> <h2>Profile <span class="size-12 text-warning" ng-if="$root.iden.backupNeeded"> [ Needs Backup ]</span></h2>
<p translate class="text-gray">It's important to backup your profile so that you can recover it in case of disaster. The backup will include all your profile's wallets</p> <p translate class="text-gray">It's important to backup your profile so that you can recover it in case of disaster. The backup will include all your profile's wallets</p>
</div> </div>
<div class="large-3 medium-3 columns"> <div class="large-3 medium-3 columns">