blob: 90a8280514b1ba788d70fc9c83f58016f5236e89 [file] [log] [blame]
/*
* Copyright (C) 2015-2019 Savoir-faire Linux Inc.
* Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "AccRingGeneralVC.h"
//cocoa
#import <Quartz/Quartz.h>
#import <AVFoundation/AVFoundation.h>
//Qt
#import <QSize>
#import <QtMacExtras/qmacfunctions.h>
#import <QPixmap>
//LRC
#import <api/lrc.h>
#import <api/newaccountmodel.h>
#import <api/newdevicemodel.h>
#import <interfaces/pixmapmanipulatori.h>
#import "RegisterNameWC.h"
#import "views/NSColor+RingTheme.h"
#import "views/NSImage+Extensions.h"
#import "views/HoverTableRowView.h"
#import "views/RoundedTextField.h"
#import "ExportPasswordWC.h"
#import "utils.h"
#import "Constants.h"
@interface AccRingGeneralVC ()
@property (unsafe_unretained) IBOutlet NSTextField *displayNameField;
@property (unsafe_unretained) IBOutlet NSTextField *ringIDField;
@property (unsafe_unretained) IBOutlet NSTextField *registeredNameField;
@property (unsafe_unretained) IBOutlet NSTextField *passwordField;
@property (unsafe_unretained) IBOutlet RoundedTextField *accountStatus;
@property (unsafe_unretained) IBOutlet NSButton *registerNameButton;
@property (unsafe_unretained) IBOutlet NSButton* photoView;
@property (unsafe_unretained) IBOutlet NSButton* passwordButton;
@property (unsafe_unretained) IBOutlet NSButton* linkDeviceButton;
@property (unsafe_unretained) IBOutlet NSButton* removeAccountButton;
@property (unsafe_unretained) IBOutlet NSButton* exportAccountButton;
@property (unsafe_unretained) IBOutlet NSImageView* addProfilePhotoImage;
@property (unsafe_unretained) IBOutlet NSTableView* devicesTableView;
@property (unsafe_unretained) IBOutlet NSTableView* blockedContactsTableView;
@property (assign) IBOutlet NSLayoutConstraint* buttonRegisterWidthConstraint;
@property (assign) IBOutlet NSLayoutConstraint* bannedContactHeightConstraint;
@property (assign) IBOutlet NSLayoutConstraint* advancedButtonMarginConstraint;
@property AbstractLoadingWC* accountModal;
@property PasswordChangeWC* passwordModal;
@property std::string selectedAccountID;
@end
@implementation AccRingGeneralVC
QMetaObject::Connection deviceAddedSignal;
QMetaObject::Connection deviceRevokedSignal;
QMetaObject::Connection deviceUpdatedSignal;
QMetaObject::Connection contactBlockedSignal;
QMetaObject::Connection bannedContactsChangedSignal;
QMetaObject::Connection accountStateChangedSignal;
@synthesize displayNameField;
@synthesize ringIDField;
@synthesize registeredNameField;
@synthesize photoView;
@synthesize addProfilePhotoImage;
@synthesize accountModel;
@synthesize registerNameButton, passwordButton, removeAccountButton;
@synthesize buttonRegisterWidthConstraint;
@synthesize accountModal;
@synthesize delegate;
@synthesize devicesTableView;
@synthesize blockedContactsTableView;
@synthesize linkDeviceButton;
@synthesize passwordField;
@synthesize exportAccountButton;
typedef NS_ENUM(NSInteger, TagViews) {
DISPLAYNAME = 100,
DEVICE_NAME_TAG = 200,
DEVICE_ID_TAG = 300,
DEVICE_EDIT_TAG = 400,
DEVICE_REVOKE_TAG = 500,
BANNED_CONTACT_NAME_TAG = 600,
BANNED_CONTACT_ID_TAG = 700,
UNBLOCK_CONTACT_TAG = 800
};
-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil accountmodel:(lrc::api::NewAccountModel*) accountModel
{
if (self = [self initWithNibName: nibNameOrNil bundle:nibBundleOrNil])
{
self.accountModel= accountModel;
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[addProfilePhotoImage setWantsLayer: YES];
devicesTableView.delegate = self;
devicesTableView.dataSource = self;
blockedContactsTableView.delegate = self;
blockedContactsTableView.dataSource= self;
[[self view] setAutoresizingMask: NSViewMinXMargin | NSViewMaxXMargin | NSViewWidthSizable];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self updateView];
}
- (void) setSelectedAccount:(std::string) account {
self.selectedAccountID = account;
[self connectSignals];
[self updateView];
[self hideBannedContacts];
}
-(void) updateView {
const auto& account = accountModel->getAccountInfo(self.selectedAccountID);
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(account.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSImage *image = [[NSImage alloc] initWithData:imageData];
if(image) {
[photoView setBordered:NO];
[photoView setImage: [image roundCorners: image.size.height * 0.5]];
[addProfilePhotoImage setHidden:YES];
} else {
[photoView setImage:nil];
[photoView setBordered:YES];
[addProfilePhotoImage setHidden:NO];
}
NSString* displayName = @(account.profileInfo.alias.c_str());
[displayNameField setStringValue:displayName];
[ringIDField setStringValue:@(account.profileInfo.uri.c_str())];
lrc::api::account::ConfProperties_t accountProperties = self.accountModel->getAccountConfig(self.selectedAccountID);
bool hideLocalAccountConfig = !accountProperties.managerUri.empty();
[passwordButton setHidden:hideLocalAccountConfig];
[linkDeviceButton setHidden:hideLocalAccountConfig];
[passwordField setHidden:hideLocalAccountConfig];
[exportAccountButton setHidden: hideLocalAccountConfig];
if(account.registeredName.empty() && !hideLocalAccountConfig) {
[registerNameButton setHidden:NO];
buttonRegisterWidthConstraint.constant = 260.0;
} else {
buttonRegisterWidthConstraint.constant = 0.0;
[registerNameButton setHidden:YES];
}
[registeredNameField setStringValue:@(account.registeredName.c_str())];
[passwordButton setTitle:accountProperties.archiveHasPassword ? @"Change password" : @"Create password"];
self.accountEnabled = account.enabled;
NSMutableAttributedString *colorTitle = [[NSMutableAttributedString alloc] initWithAttributedString:[removeAccountButton attributedTitle]];
NSRange titleRange = NSMakeRange(0, [colorTitle length]);
[colorTitle addAttribute:NSForegroundColorAttributeName value:[NSColor errorColor] range:titleRange];
[removeAccountButton setAttributedTitle:colorTitle];
[devicesTableView reloadData];
[blockedContactsTableView reloadData];
self.accountStatus.bgColor = colorForAccountStatus(accountModel->getAccountInfo(self.selectedAccountID).status);
[self.accountStatus setNeedsDisplay:YES];
}
-(void) connectSignals {
QObject::disconnect(deviceAddedSignal);
QObject::disconnect(deviceRevokedSignal);
QObject::disconnect(deviceUpdatedSignal);
QObject::disconnect(bannedContactsChangedSignal);
QObject::disconnect(accountStateChangedSignal);
deviceAddedSignal = QObject::connect(&*(self.accountModel->getAccountInfo(self.selectedAccountID)).deviceModel,
&lrc::api::NewDeviceModel::deviceAdded,
[self] (const std::string &id) {
[devicesTableView reloadData];
});
deviceRevokedSignal = QObject::connect(&*(self.accountModel->getAccountInfo(self.selectedAccountID)).deviceModel,
&lrc::api::NewDeviceModel::deviceRevoked,
[self] (const std::string &id, const lrc::api::NewDeviceModel::Status status) {
switch (status) {
case lrc::api::NewDeviceModel::Status::SUCCESS:
[devicesTableView reloadData];
break;
case lrc::api::NewDeviceModel::Status::WRONG_PASSWORD:
[self showAlertWithTitle: @"" andText: @"Device revocation failed with error: Wrong password"];
break;
case lrc::api::NewDeviceModel::Status::UNKNOWN_DEVICE:
[self showAlertWithTitle: @"" andText: @"Device revocation failed with error: Unknown device"];
break;
}
});
deviceUpdatedSignal = QObject::connect(&*(self.accountModel->getAccountInfo(self.selectedAccountID)).deviceModel,
&lrc::api::NewDeviceModel::deviceUpdated,
[self] (const std::string &id) {
[devicesTableView reloadData];
});
bannedContactsChangedSignal = QObject::connect(&*(self.accountModel->getAccountInfo(self.selectedAccountID)).contactModel,
&lrc::api::ContactModel::bannedStatusChanged,
[self] (const std::string &contactUri, bool banned) {
[blockedContactsTableView reloadData];
});
accountStateChangedSignal = QObject::connect(self.accountModel,
&lrc::api::NewAccountModel::accountStatusChanged,
[self] (const std::string& accountID) {
if(accountID != self.selectedAccountID) {
return;
}
self.accountStatus.bgColor = colorForAccountStatus(accountModel->getAccountInfo(accountID).status);
[self.accountStatus setNeedsDisplay:YES];
});
}
-(void) showAlertWithTitle: (NSString *) title andText: (NSString *)text {
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"OK"];
[alert setMessageText:title];
[alert setInformativeText:text];
[alert runModal];
}
- (void)pictureTakerDidEnd:(IKPictureTaker *) picker
returnCode:(NSInteger) code
contextInfo:(void*) contextInfo
{
//do nothing when editing canceled
if (code == 0) {
return;
}
if (auto outputImage = [picker outputImage]) {
auto image = [picker inputImage];
CGFloat newSize = MIN(MIN(image.size.height, image.size.width), MAX_IMAGE_SIZE);
outputImage = [outputImage imageResizeInsideMax: newSize];
[photoView setImage: [outputImage roundCorners: outputImage.size.height * 0.5]];
[photoView setBordered:NO];
[addProfilePhotoImage setHidden:YES];
auto imageToBytes = QByteArray::fromNSData([outputImage TIFFRepresentation]).toBase64();
std::string imageToString = std::string(imageToBytes.constData(), imageToBytes.length());
self.accountModel->setAvatar(self.selectedAccountID, imageToString);
} else if(!photoView.image) {
[photoView setBordered:YES];
[addProfilePhotoImage setHidden:NO];
}
}
#pragma mark - RegisterNameDelegate methods
- (void) didRegisterName:(NSString *) name withSuccess:(BOOL) success
{
[self.accountModal close];
if(!success) {
return;
}
if(name.length == 0) {
return;
}
buttonRegisterWidthConstraint.constant = 0.0;
[registerNameButton setHidden:YES];
[registeredNameField setStringValue:name];
lrc::api::account::ConfProperties_t accountProperties = self.accountModel->getAccountConfig(self.selectedAccountID);
self.accountModel->setAccountConfig(self.selectedAccountID, accountProperties);
}
#pragma mark - NSTextFieldDelegate delegate methods
- (void)controlTextDidChange:(NSNotification *)notif
{
NSTextField* textField = [notif object];
if (textField.tag != DISPLAYNAME) {
return;
}
NSString* displayName = textField.stringValue;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
self.accountModel->setAlias(self.selectedAccountID, [displayName UTF8String]);
lrc::api::account::ConfProperties_t accountProperties = self.accountModel->getAccountConfig(self.selectedAccountID);
self.accountModel->setAccountConfig(self.selectedAccountID, accountProperties);
}
#pragma mark - NSTableViewDataSource methods
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
if(tableView == devicesTableView) {
return self.accountModel->getAccountInfo(self.selectedAccountID).deviceModel->getAllDevices().size();
} else if (tableView == blockedContactsTableView){
return self.accountModel->getAccountInfo(self.selectedAccountID).contactModel->getBannedContacts().size();
}
return 0;
}
#pragma mark - NSTableViewDelegate methods
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if(tableView == devicesTableView) {
NSTableCellView* deviceView = [tableView makeViewWithIdentifier:@"TableCellDeviceItem" owner:self];
NSTextField* nameLabel = [deviceView viewWithTag: DEVICE_NAME_TAG];
NSTextField* idLabel = [deviceView viewWithTag: DEVICE_ID_TAG];
NSButton* revokeButton = [deviceView viewWithTag: DEVICE_REVOKE_TAG];
NSButton* editButton = [deviceView viewWithTag: DEVICE_EDIT_TAG];
[editButton setAction:@selector(editDevice:)];
[editButton setTarget:self];
[revokeButton setAction:@selector(startDeviceRevocation:)];
[revokeButton setTarget:self];
auto devices = self.accountModel->getAccountInfo(self.selectedAccountID).deviceModel->getAllDevices();
auto device = devices.begin();
std::advance(device, row);
auto name = device->name;
auto deviceID = device->id;
[nameLabel setStringValue: @(name.c_str())];
[idLabel setStringValue: @(deviceID.c_str())];
[revokeButton setHidden: device->isCurrent];
[editButton setHidden: !device->isCurrent];
return deviceView;
} else if (tableView == blockedContactsTableView) {
NSTableCellView* contactView = [tableView makeViewWithIdentifier:@"TableCellBannedContactItem" owner:self];
NSTextField* nameLabel = [contactView viewWithTag: BANNED_CONTACT_NAME_TAG];
NSTextField* idLabel = [contactView viewWithTag: BANNED_CONTACT_ID_TAG];
NSButton* revokeButton = [contactView viewWithTag: UNBLOCK_CONTACT_TAG];
auto contacts = self.accountModel->getAccountInfo(self.selectedAccountID).contactModel->getBannedContacts();
auto contactID = contacts.begin();
std::advance(contactID, row);
[idLabel setStringValue: @(contactID->c_str())];
auto contact = self.accountModel->getAccountInfo(self.selectedAccountID).contactModel->getContact([@(contactID->c_str()) UTF8String]);
[nameLabel setStringValue: bestNameForContact(contact)];
[revokeButton setAction:@selector(unblockContact:)];
[revokeButton setTarget:self];
return contactView;
}
}
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
if(tableView == devicesTableView) {
return tableView.rowHeight;
} else if (tableView == blockedContactsTableView) {
CGFloat height = self.bannedContactHeightConstraint.constant;
if(height == 150) {
return 52;
} else {
return 1;
}
}
}
- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
{
return [tableView makeViewWithIdentifier:@"HoverRowView" owner:nil];
}
#pragma mark - Actions
- (IBAction)editPhoto:(id)sender
{
auto pictureTaker = [IKPictureTaker pictureTaker];
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
if (@available(macOS 10.14, *)) {
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
{
[pictureTaker setValue:0 forKey:IKPictureTakerAllowsVideoCaptureKey];
}
if(authStatus == AVAuthorizationStatusNotDetermined)
{
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if(!granted){
[pictureTaker setValue:0 forKey:IKPictureTakerAllowsVideoCaptureKey];
}
}];
}
}
#endif
[pictureTaker beginPictureTakerSheetForWindow:[self.view window]
withDelegate:self
didEndSelector:@selector(pictureTakerDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
- (IBAction)startExportOnRing:(id)sender
{
ExportPasswordWC *passwordWC = [[ExportPasswordWC alloc] initWithNibName:@"ExportPasswordWindow" bundle: nil accountmodel: self.accountModel];
passwordWC.selectedAccountID = self.selectedAccountID;
accountModal = passwordWC;
[self.view.window beginSheet: passwordWC.window completionHandler:nil];
}
- (IBAction)triggerAdwancedSettings: (NSButton *)sender {
[self.delegate triggerAdvancedOptions];
}
- (IBAction)enableAccount: (NSButton *)sender {
const auto& account = accountModel->getAccountInfo(self.selectedAccountID);
self.accountModel->setAccountEnabled(self.selectedAccountID, !account.enabled);
self.accountEnabled = account.enabled;
}
- (IBAction)removeAccount:(id)sender
{
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText: NSLocalizedString(@"Remove account",
@"Remove account alert title")];
[alert setInformativeText:NSLocalizedString(@"By clicking \"OK\" you will remove this account on this device! This action can not be undone. Also, your registered name can be lost.",
@"Remove account alert message")];
if ([alert runModal] == NSAlertFirstButtonReturn) {
self.accountModel->removeAccount(self.selectedAccountID);
}
}
- (IBAction)exportAccount:(id)sender
{
NSSavePanel* filePicker = [NSSavePanel savePanel];
NSString* name = [@(self.selectedAccountID.c_str()) stringByAppendingString: @".gz"];
[filePicker setNameFieldStringValue: name];
if ([filePicker runModal] != NSFileHandlingPanelOKButton) {
return;
}
NSString *password = @"";
const char* fullPath = [[filePicker URL] fileSystemRepresentation];
lrc::api::account::ConfProperties_t accountProperties = self.accountModel->getAccountConfig(self.selectedAccountID);
if(accountProperties.archiveHasPassword) {
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText: NSLocalizedString(@"Enter account password",
@"Backup enter password")];
NSTextField *input = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 20)];
[alert setAccessoryView:input];
if ([alert runModal] != NSAlertFirstButtonReturn) {
return;
}
password = [input stringValue];
}
if (self.accountModel->exportToFile(self.selectedAccountID, fullPath, [password UTF8String])) {
[self didCompleteExportWithPath:[filePicker URL]];
} else {
[self showAlertWithTitle: @"" andText: NSLocalizedString(@"An error occured during the backup", @"Backup error")];
}
}
- (IBAction)startNameRegistration:(id)sender
{
RegisterNameWC* registerWC = [[RegisterNameWC alloc] initWithNibName:@"RegisterNameWindow" bundle: nil accountmodel: self.accountModel];
registerWC.delegate = self;
registerWC.selectedAccountID = self.selectedAccountID;
self.accountModal = registerWC;
[self.view.window beginSheet:registerWC.window completionHandler:nil];
}
- (IBAction)changePassword:(id)sender
{
PasswordChangeWC* passwordWC = [[PasswordChangeWC alloc] initWithNibName:@"PasswordChange" bundle: nil accountmodel: self.accountModel];
passwordWC.selectedAccountID = self.selectedAccountID;
passwordWC.delegate = self;
[self.view.window beginSheet:passwordWC.window completionHandler:nil];
self.passwordModal = passwordWC;
}
- (IBAction)showBanned: (NSButton *)sender {
CGFloat height = self.bannedContactHeightConstraint.constant;
NSRect frame = self.view.frame;
if(height == 150) {
frame.size.height = frame.size.height - 150 - 10;
} else {
frame.size.height = frame.size.height + 150 + 10;
}
self.view.frame = frame;
[self.delegate updateFrame];
CGFloat advancedHeight = self.advancedButtonMarginConstraint.constant;
self.advancedButtonMarginConstraint.constant = (height== 2) ? 40 : 30;
self.bannedContactHeightConstraint.constant = (height== 2) ? 150 : 2;
[[[[self.blockedContactsTableView superview] superview] superview] setHidden:![[[[self.blockedContactsTableView superview] superview] superview] isHidden]];
[blockedContactsTableView reloadData];
}
-(void) hideBannedContacts {
CGFloat height = self.bannedContactHeightConstraint.constant;
NSRect frame = self.view.frame;
if(height == 150) {
[self showBanned:nil];
}
}
- (IBAction)startDeviceRevocation:(NSView*)sender
{
NSInteger row = [devicesTableView rowForView:sender];
if(row < 0) {
return;
}
auto devices = self.accountModel->getAccountInfo(self.selectedAccountID).deviceModel->getAllDevices();
auto device = devices.begin();
std::advance(device, row);
if(device == devices.end()) {
return;
}
[self proceedDeviceRevokationAlert:device->id];
}
- (IBAction)unblockContact:(NSView*)sender
{
NSInteger row = [blockedContactsTableView rowForView:sender];
if(row < 0) {
return;
}
auto contacts = self.accountModel->getAccountInfo(self.selectedAccountID).contactModel->getBannedContacts();
auto contactID = contacts.begin();
std::advance(contactID, row);
if(contactID == contacts.end()) {
return;
}
auto contact = self.accountModel->getAccountInfo(self.selectedAccountID).contactModel->getContact([@(contactID->c_str()) UTF8String]);
if(!contact.isBanned) {
return;
}
self.accountModel->getAccountInfo(self.selectedAccountID).contactModel->addContact(contact);
}
- (IBAction)editDevice:(NSView*)sender
{
NSInteger row = [devicesTableView rowForView:sender];
if(row < 0) {
return;
}
NSTableCellView* deviceView = [devicesTableView viewAtColumn:0 row:row makeIfNecessary:NO];
if(!deviceView || ![deviceView isKindOfClass:[NSTableCellView class]]) {
return;
}
NSTextField* nameLabel = [deviceView viewWithTag: DEVICE_NAME_TAG];
NSButton* editButton = [deviceView viewWithTag: DEVICE_EDIT_TAG];
if ([nameLabel isEditable]) {
self.accountModel->getAccountInfo(self.selectedAccountID).deviceModel->setCurrentDeviceName([nameLabel.stringValue UTF8String]);
[nameLabel setEditable:NO];
[self.view.window makeFirstResponder:nil];
editButton.image = [NSImage imageNamed:NSImageNameTouchBarComposeTemplate];
return;
}
[nameLabel setEditable:YES];
[nameLabel becomeFirstResponder];
editButton.image = [NSImage imageNamed:NSImageNameTouchBarDownloadTemplate];
}
-(void) revokeDeviceWithID: (std::string) deviceID password:(NSString *) password {
self.accountModel->getAccountInfo(self.selectedAccountID).deviceModel->revokeDevice(deviceID, [password UTF8String]);
}
-(void) proceedDeviceRevokationAlert: (std::string) deviceID {
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Revoke Device"];
[alert setInformativeText:@"Attention! This action could not be undone!"];
lrc::api::account::ConfProperties_t accountProperties = self.accountModel->getAccountConfig(self.selectedAccountID);
if(accountProperties.archiveHasPassword) {
NSSecureTextField *passwordText = [[NSSecureTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
[passwordText setPlaceholderString:@"Enter password"];
[alert setAccessoryView:passwordText];
if ([alert runModal] == NSAlertFirstButtonReturn) {
[self revokeDeviceWithID:deviceID password:[passwordText stringValue]];
}
} else {
if ([alert runModal] == NSAlertFirstButtonReturn) {
[self revokeDeviceWithID:deviceID password:@""];
}
}
}
#pragma mark - BackupAccountDelegate methods
-(void) didCompleteExportWithPath:(NSURL*) fileUrl
{
[[NSWorkspace sharedWorkspace] selectFile:fileUrl.path inFileViewerRootedAtPath:@""];
}
#pragma mark - PasswordChangeDelegate
-(void) paswordCreatedWithSuccess:(BOOL) success
{
[passwordButton setTitle: success ? @"Change password" : @"Create password"];
}
@end