diff --git a/.gitignore b/.gitignore index bce552c6b1..1c1eae8758 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ Monal/Monal.xcodeproj/xcuserdata/anurodhp.xcuserdatad/xcschemes/jrtplib-static.x Monal/Monal.xcodeproj/xcuserdata/anurodhp.xcuserdatad/xcschemes/xcschememanagement.plist contents.xcworkspacedata ._* + +# Pods +Monal/Pods diff --git a/MLCrypto/MLCrypto.xcodeproj/project.pbxproj b/MLCrypto/MLCrypto.xcodeproj/project.pbxproj index 3790b7ec42..a5ef4bd42a 100644 --- a/MLCrypto/MLCrypto.xcodeproj/project.pbxproj +++ b/MLCrypto/MLCrypto.xcodeproj/project.pbxproj @@ -149,7 +149,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1130; - LastUpgradeCheck = 1160; + LastUpgradeCheck = 1220; ORGANIZATIONNAME = "Anurodh Pokharel"; TargetAttributes = { 26A711BC23C4D67300FC6A86 = { @@ -252,6 +252,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -315,6 +316,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -349,6 +351,7 @@ 26A711D223C4D67300FC6A86 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; @@ -384,6 +387,7 @@ 26A711D323C4D67300FC6A86 /* AppStore */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; @@ -479,6 +483,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -513,6 +518,7 @@ 26A7120A23C590AD00FC6A86 /* Adhoc */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; diff --git a/MLCrypto/MLCrypto.xcodeproj/xcshareddata/xcschemes/MLCrypto.xcscheme b/MLCrypto/MLCrypto.xcodeproj/xcshareddata/xcschemes/MLCrypto.xcscheme index a7fa038632..8b7c4d4bd7 100644 --- a/MLCrypto/MLCrypto.xcodeproj/xcshareddata/xcschemes/MLCrypto.xcscheme +++ b/MLCrypto/MLCrypto.xcodeproj/xcshareddata/xcschemes/MLCrypto.xcscheme @@ -1,6 +1,6 @@ #if !TARGET_OS_MACCATALYST #include #include +#define AES_BLOCK_SIZE 16 +#define AUTH_TAG_LENGTH 16 #endif @implementation AESGcm @@ -32,53 +35,90 @@ +(MLEncryptedPayload*) encrypt:(NSData*) body withKey:(NSData*) gcmKey { MLCrypto* crypto = [[MLCrypto alloc] init]; EncryptedPayload* payload = [crypto encryptGCMWithKey:gcmKey decryptedContent:body]; + if(payload == nil) + { + return nil; + } NSMutableData* combinedKey = [NSMutableData dataWithData:gcmKey]; [combinedKey appendData:payload.tag]; + if(combinedKey == nil) + { + return nil; + } return [[MLEncryptedPayload alloc] initWithBody:payload.body key:combinedKey iv:payload.iv authTag:payload.tag]; } else { #if !TARGET_OS_MACCATALYST - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - int outlen; - unsigned char outbuf[body.length]; - unsigned char tag[16]; + EVP_CIPHER_CTX* ctx; + int outlen, tmplen; + unsigned char* outbuf = malloc(body.length + AES_BLOCK_SIZE); + unsigned char tag[AUTH_TAG_LENGTH]; + NSMutableData* combinedKey; + NSData* encryptedMessage; NSData* gcmiv = [self genIV]; - - NSMutableData *encryptedMessage; + if(gcmiv == nil) + goto end1; ctx = EVP_CIPHER_CTX_new(); + if(ctx == NULL) + goto end1; + /* Set cipher type and mode */ - if([gcmKey length]==16) { + if([gcmKey length] == 16) { EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL); + OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16); } - - if([gcmKey length]==32) { + else if([gcmKey length] == 32) + { EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); + OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 32); } + else + goto end2; + /* Set IV length if default 96 bits is not approp riate */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int) gcmiv.length, NULL); + if(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)gcmiv.length, NULL) != 1) + goto end2; + OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == (int)gcmiv.length); + /* Initialise key and IV */ - EVP_EncryptInit_ex(ctx, NULL, NULL, gcmKey.bytes, gcmiv.bytes); - EVP_CIPHER_CTX_set_padding(ctx,1); - /* Encrypt plaintext */ - EVP_EncryptUpdate(ctx, outbuf, &outlen,body.bytes,(int)body.length); + if(EVP_EncryptInit_ex(ctx, NULL, NULL, gcmKey.bytes, gcmiv.bytes) != 1) + goto end2; + + // enable padding, always returns 1 + assert(EVP_CIPHER_CTX_set_padding(ctx, 1) == 1); - encryptedMessage = [NSMutableData dataWithBytes:outbuf length:outlen]; + /* Encrypt plaintext */ + if(EVP_EncryptUpdate(ctx, outbuf, &outlen, body.bytes, (int)body.length) == 0) + goto end2; + tmplen = outlen; /* Finalise: note get no output for GCM */ - EVP_EncryptFinal_ex(ctx, outbuf, &outlen); + if(EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen) == 0) + goto end2; + outlen += tmplen; + encryptedMessage = [NSData dataWithBytesNoCopy:outbuf length:outlen]; /* Get tag */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag); - //[encryptedMessage appendBytes:tag length:16]; + if(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTH_TAG_LENGTH, tag) != 1) + { + EVP_CIPHER_CTX_free(ctx); + return nil; + } - NSMutableData *combinedKey = [NSMutableData dataWithData:gcmKey]; - [combinedKey appendBytes:tag length:16]; + combinedKey = [NSMutableData dataWithData:gcmKey]; + [combinedKey appendBytes:tag length:AUTH_TAG_LENGTH]; EVP_CIPHER_CTX_free(ctx); - return [[MLEncryptedPayload alloc] initWithBody:encryptedMessage key:combinedKey iv:gcmiv authTag:[NSData dataWithBytes:tag length:16]]; + return [[MLEncryptedPayload alloc] initWithBody:encryptedMessage key:combinedKey iv:gcmiv authTag:[NSData dataWithBytes:tag length:AUTH_TAG_LENGTH]]; + + end2: + EVP_CIPHER_CTX_free(ctx); + end1: + free(outbuf); + return nil; #else assert(false); return nil; @@ -95,7 +135,10 @@ +(NSData*) genIV #if !TARGET_OS_MACCATALYST //generate iv unsigned char iv[12]; - RAND_bytes(iv, sizeof(iv)); + if(RAND_bytes(iv, sizeof(iv)) == 0) + { + return nil; + } NSData* gcmiv = [[NSData alloc] initWithBytes:iv length:12]; return gcmiv; #else @@ -113,64 +156,86 @@ +(NSData*) genKey:(int) keySize return [[NSData alloc] initWithBytes:randomBytes length:keySize]; } -+ (NSData *) decrypt:(NSData *)body withKey:(NSData *) key andIv:(NSData *)iv withAuth:( NSData * _Nullable ) auth { ++(NSData*) decrypt:(NSData*) body withKey:(NSData*) key andIv:(NSData*) iv withAuth:(NSData* _Nullable) auth +{ if (@available(iOS 13.0, *)) { + MLCrypto* crypto = [[MLCrypto alloc] init]; - MLCrypto *crypto = [[MLCrypto alloc] init]; - - NSMutableData *combined = [[NSMutableData alloc] init]; + NSMutableData* combined = [[NSMutableData alloc] init]; [combined appendData:iv]; [combined appendData:body]; [combined appendData:auth]; //if auth is nil assume it already was apended to body - NSData *toReturn =[crypto decryptGCMWithKey:key encryptedContent:combined]; + NSData* toReturn = [crypto decryptGCMWithKey:key encryptedContent:combined]; return toReturn; - } else + } + else { #if !TARGET_OS_MACCATALYST - int outlen, rv; - unsigned char outbuf[key.length]; - EVP_CIPHER_CTX *ctx =EVP_CIPHER_CTX_new(); + assert(iv.length == 12); + + NSData* realBody = body; + if(auth == nil) + { + realBody = [NSData dataWithBytesNoCopy:(void* _Nonnull)body.bytes length:body.length - AUTH_TAG_LENGTH freeWhenDone:NO]; + auth = [NSData dataWithBytesNoCopy:(void* _Nonnull)body.bytes + (body.length - AUTH_TAG_LENGTH) length:AUTH_TAG_LENGTH freeWhenDone:NO]; + } + + int outlen, tmplen, retval; + unsigned char* outbuf = malloc(realBody.length + AES_BLOCK_SIZE); + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); /* Select cipher */ - if(key.length==16) { + if(key.length == 16) { EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL); + OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16); } - - if(key.length==32) { + else if(key.length == 32) + { EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); + OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 32); + } + else + { + free(outbuf); + EVP_CIPHER_CTX_free(ctx); + return nil; } /* Set IV length, omit for 96 bits */ EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)iv.length, NULL); + OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == (int)iv.length); + /* Specify key and IV */ EVP_DecryptInit_ex(ctx, NULL, NULL, key.bytes, iv.bytes); - EVP_CIPHER_CTX_set_padding(ctx,1); - /* Decrypt plaintext */ - NSMutableData *decdata = [[NSMutableData alloc] initWithCapacity:body.length]; - int byteCounter=0; - while(byteCounterbody.length) byteRange=NSMakeRange(byteCounter, body.length-byteCounter); - unsigned char bytes[byteRange.length]; - [body getBytes:bytes range:byteRange]; - EVP_DecryptUpdate(ctx, outbuf, &outlen, bytes, (int)byteRange.length); - /* Output decrypted block */ - /* Finalise: note get no output for GCM */ - rv = EVP_DecryptFinal_ex(ctx, outbuf, &outlen); - [decdata appendBytes:outbuf length:byteRange.length]; - byteCounter+=byteRange.length; + DDLogError(@"EVP_DecryptUpdate() --> %ld", (long)retval); + free(outbuf); + EVP_CIPHER_CTX_free(ctx); + return nil; } + outlen = tmplen; - if(auth) { - /* Set expected tag value. */ - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)auth.length, auth.bytes); + /* Finalise: note get no output for GCM */ + if((retval = EVP_DecryptFinal_ex(ctx, outbuf + tmplen, &tmplen)) <= 0) + { + DDLogError(@"EVP_DecryptFinal_ex() --> %ld", (long)retval); + free(outbuf); + EVP_CIPHER_CTX_free(ctx); + return nil; } - EVP_CIPHER_CTX_free(ctx); - return decdata; + + return [NSData dataWithBytesNoCopy:outbuf length:outlen]; #else assert(false); return nil; @@ -178,6 +243,4 @@ + (NSData *) decrypt:(NSData *)body withKey:(NSData *) key andIv:(NSData *)iv wi } } - - @end diff --git a/Monal/Classes/AccountsViewController.h b/Monal/Classes/AccountsViewController.h index bf5233213b..7ac9968975 100644 --- a/Monal/Classes/AccountsViewController.h +++ b/Monal/Classes/AccountsViewController.h @@ -7,9 +7,9 @@ // #import +#import - -@interface AccountsViewController : UITableViewController +@interface AccountsViewController : UITableViewController @property (nonatomic, strong) UITableView* accountsTable; diff --git a/Monal/Classes/AccountsViewController.m b/Monal/Classes/AccountsViewController.m index 391fcbe44b..5a0c6a558e 100644 --- a/Monal/Classes/AccountsViewController.m +++ b/Monal/Classes/AccountsViewController.m @@ -13,6 +13,8 @@ #import "xmpp.h" #import "HelperTools.h" +@class MLQRCodeScanner; + @interface AccountsViewController () @property (nonatomic , strong) MBProgressHUD *hud; @@ -22,6 +24,11 @@ @interface AccountsViewController () @property (nonatomic, strong) NSArray* accountList; +// QR-Code login scan properties +@property (nonatomic, strong) NSString* qrCodeScanLoginJid; +@property (nonatomic, strong) NSString* qrCodeScanLoginPassword; + + @end @implementation AccountsViewController @@ -111,8 +118,16 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [tableView deselectRowAtIndexPath:indexPath animated:YES]; self.selected = indexPath; - [self performSegueWithIdentifier:@"editXMPP" sender:self]; - + if(indexPath.section == 1 && indexPath.row == 1) + { + // open QR-Code scanner + [self performSegueWithIdentifier:@"scanQRCode" sender:self]; + } + else + { + // normal account creation & editing of a existing acount + [self performSegueWithIdentifier:@"editXMPP" sender:self]; + } } @@ -123,16 +138,42 @@ -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender XMPPEdit* editor = (XMPPEdit *)segue.destinationViewController; editor.originIndex=self.selected; - if(self.selected.section == 0) + if(self.selected && self.selected.section == 0) { //existing editor.accountno = [NSString stringWithFormat:@"%@",[[_accountList objectAtIndex:self.selected.row] objectForKey:@"account_id"]]; } - else if(self.selected.section == 1) + else if(self.selected && self.selected.section == 1) + { + // Adding new account + editor.accountno = @"-1"; + } + else if(!self.selected && self.qrCodeScanLoginJid && self.qrCodeScanLoginPassword) { + // QR-Code scanned login details editor.accountno = @"-1"; + editor.jid = self.qrCodeScanLoginJid; + editor.password = self.qrCodeScanLoginPassword; } } + else if([segue.identifier isEqualToString:@"scanQRCode"]) + { + MLQRCodeScanner* qrCodeScanner = (MLQRCodeScanner*)segue.destinationViewController; + qrCodeScanner.loginDelegate = self; + } +} + +-(void) MLQRCodeAccountLoginScannedWithJid:(NSString*) jid password:(NSString*) password +{ + // We do not want to edit a existing account or to create a empty view + self.selected = nil; + // Save the new login credentials till we segue + self.qrCodeScanLoginJid = jid; + self.qrCodeScanLoginPassword = password; + + [self.navigationController popViewControllerAnimated:YES]; + // open account settings with the read credentials + [self performSegueWithIdentifier:@"editXMPP" sender:self]; } @@ -187,7 +228,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } case 1: { - return 1; + return 2; break; } @@ -201,73 +242,69 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - switch (indexPath.section) { - case 0: + if(indexPath.section == 0) + { + UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; + if(cell == nil) { - UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; - if(cell == nil) - { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"AccountCell"]; - } - else - { - cell.accessoryView = nil; - } - if([(NSString*)[[_accountList objectAtIndex:indexPath.row] objectForKey:@"domain"] length] > 0) { - cell.textLabel.text = [NSString stringWithFormat:@"%@@%@", [[_accountList objectAtIndex:indexPath.row] objectForKey:@"username"], - [[_accountList objectAtIndex:indexPath.row] objectForKey:@"domain"]]; - } - else { - cell.textLabel.text = [[_accountList objectAtIndex:indexPath.row] objectForKey:@"username"]; - } - - - UIImageView* accessory = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; - cell.detailTextLabel.text=nil; - - if([[[_accountList objectAtIndex:indexPath.row] objectForKey:@"enabled"] boolValue] == YES) { - cell.imageView.image = [UIImage imageNamed:@"888-checkmark"]; - if([[MLXMPPManager sharedInstance] isAccountForIdConnected: [NSString stringWithFormat:@"%@", [[_accountList objectAtIndex:indexPath.row] objectForKey:@"account_id"]]]) { - accessory.image = [UIImage imageNamed:@"Connected"]; - cell.accessoryView = accessory; - - NSDate* connectedTime = [[MLXMPPManager sharedInstance] connectedTimeFor: [NSString stringWithFormat:@"%@", [[_accountList objectAtIndex:indexPath.row] objectForKey:@"account_id"]]]; - if(connectedTime) { - cell.detailTextLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Connected since: %@", @""), [self.uptimeFormatter stringFromDate:connectedTime]]; - } - } - else { - accessory.image = [UIImage imageNamed:NSLocalizedString(@"Disconnected", @"")]; - cell.accessoryView = accessory; + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"AccountCell"]; + } + else + { + cell.accessoryView = nil; + } + if([(NSString*)[[_accountList objectAtIndex:indexPath.row] objectForKey:@"domain"] length] > 0) { + cell.textLabel.text = [NSString stringWithFormat:@"%@@%@", [[_accountList objectAtIndex:indexPath.row] objectForKey:@"username"], + [[_accountList objectAtIndex:indexPath.row] objectForKey:@"domain"]]; + } + else { + cell.textLabel.text = [[_accountList objectAtIndex:indexPath.row] objectForKey:@"username"]; + } + + + UIImageView* accessory = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; + cell.detailTextLabel.text=nil; + + if([[[_accountList objectAtIndex:indexPath.row] objectForKey:@"enabled"] boolValue] == YES) { + cell.imageView.image = [UIImage imageNamed:@"888-checkmark"]; + if([[MLXMPPManager sharedInstance] isAccountForIdConnected: [NSString stringWithFormat:@"%@", [[_accountList objectAtIndex:indexPath.row] objectForKey:@"account_id"]]]) { + accessory.image = [UIImage imageNamed:@"Connected"]; + cell.accessoryView = accessory; + + NSDate* connectedTime = [[MLXMPPManager sharedInstance] connectedTimeFor: [NSString stringWithFormat:@"%@", [[_accountList objectAtIndex:indexPath.row] objectForKey:@"account_id"]]]; + if(connectedTime) { + cell.detailTextLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Connected since: %@", @""), [self.uptimeFormatter stringFromDate:connectedTime]]; } } else { - cell.imageView.image = [UIImage imageNamed:@"disabled"]; + accessory.image = [UIImage imageNamed:NSLocalizedString(@"Disconnected", @"")]; + cell.accessoryView = accessory; } - return cell; - break; } - case 1: + else + cell.imageView.image = [UIImage imageNamed:@"disabled"]; + return cell; + } + else + { + UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"ProtocolCell"]; + if(cell == nil) + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ProtocolCell"]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.imageView.image = [UIImage imageNamed:@"XMPP"]; + + if(indexPath.row == 0) { - UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"ProtocolCell"]; - if(cell == nil) - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ProtocolCell"]; cell.textLabel.text = NSLocalizedString(@"XMPP", @""); - cell.imageView.image = [UIImage imageNamed:@"XMPP"]; cell.detailTextLabel.text = NSLocalizedString(@"Jabber, Prosody, ejabberd etc. ", @""); - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - - return cell; - break; } - default: + else { - return 0; + cell.textLabel.text = NSLocalizedString(@"XMPP: QR-Code", @""); + cell.detailTextLabel.text = NSLocalizedString(@"Login with a QR-Code", @""); } - break; + return cell; } - - return nil; } @end diff --git a/Monal/Classes/ActiveChatsViewController.m b/Monal/Classes/ActiveChatsViewController.m index fe919c1266..af936114d4 100755 --- a/Monal/Classes/ActiveChatsViewController.m +++ b/Monal/Classes/ActiveChatsViewController.m @@ -8,6 +8,7 @@ #import "ActiveChatsViewController.h" #import "DataLayer.h" +#import "xmpp.h" #import "MLContactCell.h" #import "chatViewController.h" #import "MonalAppDelegate.h" @@ -69,8 +70,10 @@ - (void)viewDidLoad self.view = self.chatListTable; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshDisplay) name:kMonalRefresh object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshDisplay) name:kMonalMessageFiletransferUpdateNotice object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshContact:) name:kMonalContactRefresh object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNewMessage:) name:kMonalNewMessageNotice object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNewMessage:) name:kMonalDeletedMessageNotice object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(messageSent:) name:kMLMessageSentToContact object:nil]; [_chatListTable registerNib:[UINib nibWithNibName:@"MLContactCell" @@ -78,16 +81,21 @@ - (void)viewDidLoad forCellReuseIdentifier:@"ContactCell"]; self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible; -#if !TARGET_OS_MACCATALYST if(@available(iOS 13.0, *)) + { +#if !TARGET_OS_MACCATALYST self.splitViewController.primaryBackgroundStyle = UISplitViewControllerBackgroundStyleSidebar; +#endif + self.settingsButton.image = [UIImage systemImageNamed:@"gearshape.fill"]; + self.addButton.image = [UIImage systemImageNamed:@"plus"]; + self.composeButton.image = [UIImage systemImageNamed:@"person.2.fill"]; + } else { self.settingsButton.image = [UIImage imageNamed:@"973-user"]; self.addButton.image = [UIImage imageNamed:@"907-plus-rounded-square"]; self.composeButton.image = [UIImage imageNamed:@"704-compose"]; } -#endif self.chatListTable.emptyDataSetSource = self; self.chatListTable.emptyDataSetDelegate = self; @@ -241,32 +249,26 @@ -(void) insertOrMoveContact:(MLContact *) contact completion:(void (^ _Nullable) -(void) viewWillAppear:(BOOL) animated { [super viewWillAppear:animated]; + // load contacts self.lastSelectedUser = nil; + if(self.unpinnedContacts.count == 0) { + [self refreshDisplay]; + } } -(void) viewDidAppear:(BOOL) animated { [super viewDidAppear:animated]; - if(self.unpinnedContacts.count == 0) { - [self refreshDisplay]; - } - if(![[HelperTools defaultsDB] boolForKey:@"HasSeenIntro"]) { [self performSegueWithIdentifier:@"showIntro" sender:self]; + return; } - else { - //for 3->4 release remove later - if(![[HelperTools defaultsDB] boolForKey:@"HasSeeniOS13Message"]) { - - UIAlertController *messageAlert =[UIAlertController alertControllerWithTitle:NSLocalizedString(@"Notification Changes",@ "") message:[NSString stringWithFormat:NSLocalizedString(@"Notifications have changed in iOS 13 because of some iOS changes. For now you will just see something saying there is a new message and not the text or who sent it. I have decided to do this so you have reliable messaging while I work to update Monal to get the old expereince back.",@ "")] preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *acceptAction =[UIAlertAction actionWithTitle:NSLocalizedString(@"Got it!",@ "") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [self dismissViewControllerAnimated:YES completion:nil]; - - }]; - [messageAlert addAction:acceptAction]; - [self.tabBarController presentViewController:messageAlert animated:YES completion:nil]; - [[HelperTools defaultsDB] setBool:YES forKey:@"HasSeeniOS13Message"]; - } + if(![[HelperTools defaultsDB] boolForKey:@"HasSeenLogin"]) { + [self performSegueWithIdentifier:@"showLogin" sender:self]; + } + if(![[HelperTools defaultsDB] boolForKey:@"HasSeenPrivacySettings"]) { + [self performSegueWithIdentifier:@"showPrivacySettings" sender:self]; + return; } } @@ -301,17 +303,22 @@ -(BOOL) showAccountNumberWarningIfNeeded -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + DDLogInfo(@"Got segue identifier '%@'", segue.identifier); if([segue.identifier isEqualToString:@"showIntro"]) { - MLWelcomeViewController* welcome = (MLWelcomeViewController *) segue.destinationViewController; - welcome.completion = ^(){ - if([[MLXMPPManager sharedInstance].connectedXMPP count] == 0) - { - if(![[HelperTools defaultsDB] boolForKey:@"HasSeenLogin"]) { - [self performSegueWithIdentifier:@"showLogin" sender:self]; + // needed for >= ios13 + if(@available(iOS 13.0, *)) + { + MLWelcomeViewController* welcome = (MLWelcomeViewController *) segue.destinationViewController; + welcome.completion = ^(){ + if([[MLXMPPManager sharedInstance].connectedXMPP count] == 0) + { + if(![[HelperTools defaultsDB] boolForKey:@"HasSeenLogin"]) { + [self performSegueWithIdentifier:@"showLogin" sender:self]; + } } - } - }; + }; + } } else if([segue.identifier isEqualToString:@"showConversation"]) { @@ -421,22 +428,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } [cell showDisplayName:row.contactDisplayName]; - NSString* state = [row.state stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - - if(([state isEqualToString:@"away"]) || - ([state isEqualToString:@"dnd"])|| - ([state isEqualToString:@"xa"]) - ) - { - cell.status = kStatusAway; - } - else if([state isEqualToString:@"offline"]) { - cell.status = kStatusOffline; - } - else if([state isEqualToString:@"(null)"] || [state isEqualToString:@""]) { - cell.status = kStatusOnline; - } - cell.accountNo = row.accountId.integerValue; cell.username = row.contactJid; cell.count = 0; @@ -448,24 +439,28 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } }); - NSMutableArray* messages = [[DataLayer sharedInstance] lastMessageForContact:cell.username forAccount:row.accountId]; + MLMessage* messageRow = [[DataLayer sharedInstance] lastMessageForContact:cell.username forAccount:row.accountId]; dispatch_async(dispatch_get_main_queue(), ^{ - if(messages.count > 0) + if(messageRow) { - MLMessage *messageRow = messages[0]; if([messageRow.messageType isEqualToString:kMessageTypeUrl]) - { [cell showStatusText:NSLocalizedString(@"πŸ”— A Link", @"")]; - } else if([messageRow.messageType isEqualToString:kMessageTypeImage]) + else if([messageRow.messageType isEqualToString:kMessageTypeFiletransfer]) + { + if([messageRow.filetransferMimeType hasPrefix:@"image/"]) + [cell showStatusText:NSLocalizedString(@"πŸ“· An Image", @"")]; + else //TODO JIM: add support for more mime types + [cell showStatusText:NSLocalizedString(@"πŸ“ A File", @"")]; + } + else if ([messageRow.messageType isEqualToString:kMessageTypeMessageDraft]) { - [cell showStatusText:NSLocalizedString(@"πŸ“· An Image", @"")]; - } else if ([messageRow.messageType isEqualToString:kMessageTypeMessageDraft]) { NSString* draftPreview = [NSString stringWithFormat:NSLocalizedString(@"Draft: %@", @""), messageRow.messageText]; [cell showStatusTextItalic:draftPreview withItalicRange:NSMakeRange(0, 6)]; - } else if([messageRow.messageType isEqualToString:kMessageTypeGeo]) - { + } + else if([messageRow.messageType isEqualToString:kMessageTypeGeo]) [cell showStatusText:NSLocalizedString(@"πŸ“ A Location", @"")]; - } else { + else + { //XEP-0245: The slash me Command NSString* displayName; NSDictionary* accountDict = [[DataLayer sharedInstance] detailsForAccount:row.accountId]; @@ -491,21 +486,27 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [cell showStatusText:messageRow.messageText]; } } - if(messageRow.timestamp) { + if(messageRow.timestamp) + { cell.time.text = [self formattedDateWithSource:messageRow.timestamp]; cell.time.hidden = NO; - } else { - cell.time.hidden = YES; } - } else { + else + cell.time.hidden = YES; + } + else + { [cell showStatusText:nil]; - DDLogWarn(NSLocalizedString(@"Active chat but no messages found in history for %@.", @""), row.contactJid); + DDLogWarn(@"Active chat but no messages found in history for %@.", row.contactJid); } }); [[MLImageManager sharedInstance] getIconForContact:row.contactJid andAccount:row.accountId withCompletion:^(UIImage *image) { - cell.userImage.image = image; + cell.userImage.image = image; }]; - [cell setOrb]; + BOOL muted = [[DataLayer sharedInstance] isMutedJid:row.contactJid]; + dispatch_async(dispatch_get_main_queue(), ^{ + cell.muteBadge.hidden = !muted; + }); return cell; } @@ -547,6 +548,7 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd [self.chatListTable deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; // removeActiveBuddy in db [[DataLayer sharedInstance] removeActiveBuddy:contact.contactJid forAccount:contact.accountId]; + [self refreshDisplay]; } } diff --git a/Monal/Classes/CallViewController.m b/Monal/Classes/CallViewController.m index 42b73bb3bd..7129a9ff21 100644 --- a/Monal/Classes/CallViewController.m +++ b/Monal/Classes/CallViewController.m @@ -8,6 +8,7 @@ #import #import "CallViewController.h" +#import "MLConstants.h" #import "MLImageManager.h" #import "MLXMPPManager.h" @@ -105,17 +106,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } -- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation -{ - if(interfaceOrientation==UIInterfaceOrientationPortrait) - return YES; - else - - return NO; -} - - - -(IBAction)cancelCall:(id)sender { [UIDevice currentDevice].proximityMonitoringEnabled=NO; diff --git a/Monal/Classes/ContactDetails.m b/Monal/Classes/ContactDetails.m index f1b7b2cc3e..b7a2164a63 100644 --- a/Monal/Classes/ContactDetails.m +++ b/Monal/Classes/ContactDetails.m @@ -49,6 +49,7 @@ @implementation ContactDetails -(void) viewDidLoad { [super viewDidLoad]; + [self.tableView registerNib:[UINib nibWithNibName:@"MLTextInputCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"TextCell"]; @@ -59,6 +60,7 @@ -(void) viewDidLoad -(void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + if(!self.contact) return; self.tableView.rowHeight = UITableViewAutomaticDimension; @@ -114,7 +116,7 @@ -(void) viewWillAppear:(BOOL)animated -(void)viewWillDisappear:(BOOL)animated { - [super viewWillAppear:animated]; + [super viewWillDisappear:animated]; } -(IBAction) callContact:(id)sender @@ -172,132 +174,133 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { UITableViewCell* thecell; - switch(indexPath.section) { - case 0: { - MLContactDetailHeader* detailCell = (MLContactDetailHeader *)[tableView dequeueReusableCellWithIdentifier:@"headerCell"]; + if(indexPath.section == 0) + { + MLContactDetailHeader* detailCell = (MLContactDetailHeader *)[tableView dequeueReusableCellWithIdentifier:@"headerCell"]; - // Set jid field - if(self.contact.isGroup) { - detailCell.jid.text=[NSString stringWithFormat:@"%@ (%lu)", self.contact.contactJid, self.groupMemberCount]; - //hide things that aren't relevant - detailCell.phoneButton.hidden = YES; - detailCell.isContact.hidden = YES; - } else { - detailCell.jid.text = self.contact.contactJid; - detailCell.isContact.hidden = self.isSubscribed; - detailCell.isContact.text = self.subMessage; - } - - // Set human readable lastInteraction field - NSDate* lastInteractionDate = [[DataLayer sharedInstance] lastInteractionOfJid:self.contact.contactJid forAccountNo:self.contact.accountId]; - NSString* lastInteractionStr; - if(lastInteractionDate.timeIntervalSince1970 > 0) { - lastInteractionStr = [NSDateFormatter localizedStringFromDate:lastInteractionDate dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle]; - } else { - lastInteractionStr = NSLocalizedString(@"now", @""); - } - detailCell.lastInteraction.text = [NSString stringWithFormat:NSLocalizedString(@"Last seen: %@", @""), lastInteractionStr]; + // Set jid field + if(self.contact.isGroup) + { + detailCell.jid.text=[NSString stringWithFormat:@"%@ (%lu)", self.contact.contactJid, self.groupMemberCount]; + //hide things that aren't relevant + detailCell.phoneButton.hidden = YES; + detailCell.isContact.hidden = YES; + } + else + { + detailCell.jid.text = self.contact.contactJid; + detailCell.isContact.hidden = self.isSubscribed; + detailCell.isContact.text = self.subMessage; + } + + // Set human readable lastInteraction field + NSDate* lastInteractionDate = [[DataLayer sharedInstance] lastInteractionOfJid:self.contact.contactJid forAccountNo:self.contact.accountId]; + NSString* lastInteractionStr; + if(lastInteractionDate.timeIntervalSince1970 > 0) + lastInteractionStr = [NSDateFormatter localizedStringFromDate:lastInteractionDate dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterShortStyle]; + else + lastInteractionStr = NSLocalizedString(@"now", @""); + detailCell.lastInteraction.text = [NSString stringWithFormat:NSLocalizedString(@"Last seen: %@", @""), lastInteractionStr]; + + if(self.contact.isGroup || !self.isSubscribed) { + detailCell.lockButton.hidden = YES; + } - if(self.contact.isGroup || !self.isSubscribed) { - detailCell.lockButton.hidden = YES; + [[MLImageManager sharedInstance] getIconForContact:self.contact.contactJid andAccount:self.contact.accountId withCompletion:^(UIImage *image) { + detailCell.buddyIconView.image = image; + // detailCell.background.image=image; + }]; + + detailCell.background.image = [UIImage imageNamed:@"Tie_My_Boat_by_Ray_Garcia"]; + + if(self.isMuted) + [detailCell.muteButton setImage:[UIImage imageNamed:@"847-moon-selected"] forState:UIControlStateNormal]; + else + [detailCell.muteButton setImage:[UIImage imageNamed:@"847-moon"] forState:UIControlStateNormal]; + + if(self.isEncrypted) + [detailCell.lockButton setImage:[UIImage imageNamed:@"744-locked-selected"] forState:UIControlStateNormal]; + else + [detailCell.lockButton setImage:[UIImage imageNamed:@"745-unlocked"] forState:UIControlStateNormal]; + + return detailCell; + } + else if(indexPath.section == 1) + { + if(indexPath.row == 0) + { + MLTextInputCell *cell = (MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; + if(self.contact.isGroup) + { + cell.textInput.enabled=NO; + cell.textInput.text=self.contact.accountNickInGroup; } - - [[MLImageManager sharedInstance] getIconForContact:self.contact.contactJid andAccount:self.contact.accountId withCompletion:^(UIImage *image) { - detailCell.buddyIconView.image = image; - // detailCell.background.image=image; - }]; - - detailCell.background.image = [UIImage imageNamed:@"Tie_My_Boat_by_Ray_Garcia"]; - - if(self.isMuted) { - [detailCell.muteButton setImage:[UIImage imageNamed:@"847-moon-selected"] forState:UIControlStateNormal]; - } else { - [detailCell.muteButton setImage:[UIImage imageNamed:@"847-moon"] forState:UIControlStateNormal]; + else + { + cell.textInput.text=[self.contact contactDisplayName]; + cell.textInput.placeholder = NSLocalizedString(@"Set a nickname for this contact", @""); + cell.textInput.delegate = self; } - - if(self.isEncrypted) { - [detailCell.lockButton setImage:[UIImage imageNamed:@"744-locked-selected"] forState:UIControlStateNormal]; + return cell; + } + else if(indexPath.row == 1) + { + MLDetailsTableViewCell* cell = (MLDetailsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"MessageCell"]; + if(self.contact.isGroup) { + cell.cellDetails.text = self.contact.groupSubject; } else { - [detailCell.lockButton setImage:[UIImage imageNamed:@"745-unlocked"] forState:UIControlStateNormal]; + cell.cellDetails.text = self.contact.statusMessage; + if([cell.cellDetails.text isEqualToString:@"(null)"]) + cell.cellDetails.text = @""; } - - thecell = detailCell; - break; + return cell; } - case 1: { - if(indexPath.row == 0) - { - MLTextInputCell *cell= (MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; - if(self.contact.isGroup) { - cell.textInput.enabled=NO; - cell.textInput.text=self.contact.accountNickInGroup; - } else { - cell.textInput.text=[self.contact contactDisplayName]; - cell.textInput.placeholder = NSLocalizedString(@"Set a nickname for this contact", @""); - cell.textInput.delegate = self; - } - thecell = cell; - } - else if(indexPath.row == 1) { - MLDetailsTableViewCell* cell = (MLDetailsTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"MessageCell"]; - if(self.contact.isGroup) { - cell.cellDetails.text = self.contact.groupSubject; - } else { - cell.cellDetails.text = self.contact.statusMessage; - if([cell.cellDetails.text isEqualToString:@"(null)"]) - cell.cellDetails.text = @""; - } - thecell = cell; - } - else { - UITableViewCell* cell= (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"TableCell"]; - cell.textLabel.text = NSLocalizedString(@"View Images Received",@""); - thecell = cell; - } - break; + else + { + UITableViewCell* cell= (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"TableCell"]; + cell.textLabel.text = NSLocalizedString(@"View Images Received",@""); + return cell; } - case 2: { - thecell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Sub"]; - if(indexPath.row == 0) { - thecell.textLabel.text = NSLocalizedString(@"Encryption Keys", @""); - } else - if(indexPath.row == 1) { - if(self.contact.isGroup) { - thecell.textLabel.text = NSLocalizedString(@"Participants", @""); - } else { - thecell.textLabel.text = NSLocalizedString(@"Resources", @""); - } - } - else if(indexPath.row == 2) { - if(self.contact.isGroup) { - thecell.textLabel.text = NSLocalizedString(@"Leave Conversation", @""); - } else { - if(self.isSubscribed) { - thecell.textLabel.text = NSLocalizedString(@"Remove Contact", @""); - } else { - thecell.textLabel.text = NSLocalizedString(@"Add Contact", @""); - } - } - } - else if(indexPath.row == 3) { - thecell.textLabel.text = NSLocalizedString(@"Block Sender", @""); - } - else if(indexPath.row == 4) { - thecell.textLabel.text = NSLocalizedString(@"Unblock Sender", @""); + } + else + { + thecell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Sub"]; + if(indexPath.row == 0) + thecell.textLabel.text = NSLocalizedString(@"Encryption Keys", @""); + else if(indexPath.row == 1) + { + if(self.contact.isGroup) { + thecell.textLabel.text = NSLocalizedString(@"Participants", @""); + } else { + thecell.textLabel.text = NSLocalizedString(@"Resources", @""); } - else if(indexPath.row == 5) { - if(self.isPinned) { - thecell.textLabel.text = NSLocalizedString(@"Unpin Chat", @""); - } else { - thecell.textLabel.text = NSLocalizedString(@"Pin Chat", @""); - } + } + else if(indexPath.row == 2) + { + if(self.contact.isGroup) + thecell.textLabel.text = NSLocalizedString(@"Leave Conversation", @""); + else + { + if(self.isSubscribed) + thecell.textLabel.text = NSLocalizedString(@"Remove Contact", @""); + else + thecell.textLabel.text = NSLocalizedString(@"Add Contact", @""); } - thecell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; - break; } + else if(indexPath.row == 3) + thecell.textLabel.text = NSLocalizedString(@"Block Sender", @""); + else if(indexPath.row == 4) + thecell.textLabel.text = NSLocalizedString(@"Unblock Sender", @""); + else if(indexPath.row == 5) + { + if(self.isPinned) + thecell.textLabel.text = NSLocalizedString(@"Unpin Chat", @""); + else + thecell.textLabel.text = NSLocalizedString(@"Pin Chat", @""); + } + thecell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; + return thecell; } - return thecell; - } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; @@ -481,19 +484,18 @@ -(void) unBlockContact { } -(void) showChatImges{ - NSMutableArray *images = [[DataLayer sharedInstance] allAttachmentsFromContact:self.contact.contactJid forAccount:self.accountNo]; + NSMutableArray* images = [[DataLayer sharedInstance] allAttachmentsFromContact:self.contact.contactJid forAccount:self.accountNo]; if(!self.photos) - { self.photos =[[NSMutableArray alloc] init]; - for (NSDictionary *imagePath in images) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *readPath = [documentsDirectory stringByAppendingPathComponent:@"imagecache"]; - readPath = [readPath stringByAppendingPathComponent:[imagePath objectForKey:@"path"]]; - UIImage *image=[UIImage imageWithContentsOfFile:readPath]; - IDMPhoto* photo=[IDMPhoto photoWithImage:image]; - [self.photos addObject:photo]; - } + { + self.photos = [[NSMutableArray alloc] init]; + for(NSDictionary* imageInfo in images) + if(![imageInfo[@"needsDownloading"] boolValue] && [imageInfo[@"mimeType"] hasPrefix:@"image/"]) + { + UIImage* image = [UIImage imageWithContentsOfFile:imageInfo[@"cacheFile"]]; + IDMPhoto* photo = [IDMPhoto photoWithImage:image]; + [self.photos addObject:photo]; + } } dispatch_async(dispatch_get_main_queue(), ^{ diff --git a/Monal/Classes/ContactsViewController.m b/Monal/Classes/ContactsViewController.m index e9f5820072..b411b4e5f9 100644 --- a/Monal/Classes/ContactsViewController.m +++ b/Monal/Classes/ContactsViewController.m @@ -17,27 +17,23 @@ #import "MonalAppDelegate.h" #import "UIColor+Theme.h" #import "MLGroupChatTableViewController.h" +#import "xmpp.h" +@interface ContactsViewController () -#define konlineSection 1 -#define kofflineSection 2 - -@interface ContactsViewController () @property (nonatomic, strong) NSArray* searchResults ; -@property (nonatomic, strong) UISearchController *searchController; +@property (nonatomic, strong) UISearchController* searchController; @property (nonatomic ,strong) NSMutableArray* contacts; -@property (nonatomic ,strong) NSMutableArray* offlineContacts; @property (nonatomic ,strong) MLContact* lastSelectedContact; @end @implementation ContactsViewController - - #pragma mark view life cycle -- (void)viewDidLoad + +-(void) viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. @@ -49,7 +45,6 @@ - (void)viewDidLoad self.contacts=[[NSMutableArray alloc] init] ; - self.offlineContacts=[[NSMutableArray alloc] init] ; [self.contactsTable reloadData]; @@ -60,7 +55,7 @@ - (void)viewDidLoad self.splitViewController.preferredDisplayMode=UISplitViewControllerDisplayModeAllVisible; - self.searchController =[[UISearchController alloc] initWithSearchResultsController:nil]; + self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil]; self.searchController.searchResultsUpdater = self; self.searchController.delegate = self; @@ -71,8 +66,11 @@ - (void)viewDidLoad self.tableView.emptyDataSetSource = self; self.tableView.emptyDataSetDelegate = self; - + if(@available(iOS 13.0, *)) + self.navigationItem.rightBarButtonItem.image = [UIImage systemImageNamed:@"person.3.fill"]; + else + self.navigationItem.rightBarButtonItem.image = [UIImage imageNamed:@"974-users"]; } -(void) dealloc @@ -83,24 +81,22 @@ -(void) dealloc -(void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + + self.lastSelectedContact = nil; + [self refreshDisplay]; + + if(self.contacts.count == 0) + [self reloadTable]; } --(void) viewDidAppear:(BOOL)animated +-(void) viewDidAppear:(BOOL) animated { [super viewDidAppear:animated]; - - self.lastSelectedContact=nil; - [self refreshDisplay]; - - if(self.contacts.count+self.offlineContacts.count == 0) - { - [self reloadTable]; - } } --(void) viewWillDisappear:(BOOL)animated +-(void) viewWillDisappear:(BOOL) animated { [super viewWillDisappear:animated]; } @@ -113,7 +109,7 @@ - (void)didReceiveMemoryWarning #pragma mark - jingle --(void) showCallRequest:(NSNotification *) notification +-(void) showCallRequest:(NSNotification*) notification { NSDictionary* dic = notification.object; @@ -149,29 +145,11 @@ -(void) reloadTable -(void) refreshDisplay { - if([[HelperTools defaultsDB] boolForKey:@"SortContacts"]) //sort by status - { - NSMutableArray* results = [[DataLayer sharedInstance] onlineContactsSortedBy:@"Status"]; - dispatch_async(dispatch_get_main_queue(), ^{ - self.contacts= results; - [self reloadTable]; - }); - } - else { - NSMutableArray* results = [[DataLayer sharedInstance] onlineContactsSortedBy:@"Name"]; - dispatch_async(dispatch_get_main_queue(), ^{ - self.contacts= results; - [self reloadTable]; - }); - } - if([[HelperTools defaultsDB] boolForKey:@"OfflineContact"]) - { - NSMutableArray* results = [[DataLayer sharedInstance] offlineContacts]; - dispatch_async(dispatch_get_main_queue(), ^{ - self.offlineContacts= results; - [self reloadTable]; - }); - } + NSMutableArray* results = [[DataLayer sharedInstance] contactList]; + dispatch_async(dispatch_get_main_queue(), ^{ + self.contacts = results; + [self reloadTable]; + }); if(self.searchResults.count == 0) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -182,73 +160,61 @@ -(void) refreshDisplay #pragma mark - chat presentation --(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender +-(void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id) sender { - if([segue.identifier isEqualToString:@"showDetails"]) + if([segue.identifier isEqualToString:@"showDetails"]) { UINavigationController* nav = segue.destinationViewController; ContactDetails* details = (ContactDetails *)nav.topViewController; details.contact = sender; } else if([segue.identifier isEqualToString:@"showGroups"]) - { - MLGroupChatTableViewController* groups = (MLGroupChatTableViewController *)segue.destinationViewController; - groups.selectGroup = ^(MLContact *selectedContact) { - if(self.selectContact) self.selectContact(selectedContact); - [self close:nil]; - }; - } + { + MLGroupChatTableViewController* groups = (MLGroupChatTableViewController *)segue.destinationViewController; + groups.selectGroup = ^(MLContact *selectedContact) { + if(self.selectContact) self.selectContact(selectedContact); + [self close:nil]; + }; + } } #pragma mark - Search Controller -- (void)didDismissSearchController:(UISearchController *)searchController; +-(void) didDismissSearchController:(UISearchController*) searchController; { self.searchResults = nil; [self reloadTable]; } - -- (void)updateSearchResultsForSearchController:(UISearchController *)searchController; +-(void) updateSearchResultsForSearchController:(UISearchController*) searchController; { - if(searchController.searchBar.text.length > 0) { + if(searchController.searchBar.text.length > 0) + { NSString* term = [searchController.searchBar.text copy]; self.searchResults = [[DataLayer sharedInstance] searchContactsWithString:term]; - } else { - self.searchResults = nil; } + else + self.searchResults = nil; [self reloadTable]; } - #pragma mark - tableview datasource + -(NSString*) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - NSString* toReturn = nil; - switch (section) { - case konlineSection: - toReturn = NSLocalizedString(@"Recently Seen", @""); - break; - case kofflineSection: - toReturn = NSLocalizedString(@"Away", @""); - break; - default: - break; - } - - return toReturn; + return nil; } --(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath +-(UISwipeActionsConfiguration*)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAt:(NSIndexPath *)indexPath { - UITableViewRowAction *delete = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:NSLocalizedString(@"Delete", @"") handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) { + UIContextualAction* delete = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:NSLocalizedString(@"Delete", @"") handler:^(UIContextualAction* action, __kindof UIView* sourceView, void (^ _Nonnull completionHandler)(BOOL)) { [self deleteRowAtIndexPath:indexPath]; }]; - UITableViewRowAction* mute; + UIContextualAction* mute; MLContactCell* cell = (MLContactCell *)[tableView cellForRowAtIndexPath:indexPath]; if(cell.muteBadge.hidden) { - mute = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:NSLocalizedString(@"Mute", @"") handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) { + mute = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:NSLocalizedString(@"Mute", @"") handler:^(UIContextualAction* action, __kindof UIView* sourceView, void (^ _Nonnull completionHandler)(BOOL)) { [self muteContactAtIndexPath:indexPath]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; @@ -257,7 +223,7 @@ -(NSString*) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteg [mute setBackgroundColor:[UIColor monalGreen]]; } else { - mute = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:NSLocalizedString(@"Unmute",@"") handler:^(UITableViewRowAction * _Nonnull action, NSIndexPath * _Nonnull indexPath) { + mute = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:NSLocalizedString(@"Unmute", @"") handler:^(UIContextualAction* action, __kindof UIView* sourceView, void (^ _Nonnull completionHandler)(BOOL)) { [self unMuteContactAtIndexPath:indexPath]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; @@ -272,114 +238,61 @@ -(NSString*) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteg // [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; // }]; // [block setBackgroundColor:[UIColor darkGrayColor]]; - - return @[delete, mute]; - + return [UISwipeActionsConfiguration configurationWithActions:@[delete, mute]]; } --(MLContact *)contactAtIndexPath:(NSIndexPath *) indexPath +-(MLContact*) contactAtIndexPath:(NSIndexPath*) indexPath { - MLContact* contact; - if ((indexPath.section==1) && (indexPath.row<=[self.contacts count]) ) { - contact=[self.contacts objectAtIndex:indexPath.row]; - } - else if((indexPath.section==2) && (indexPath.row<=[self.offlineContacts count]) ) { - contact=[self.offlineContacts objectAtIndex:indexPath.row]; - } - return contact; + return [self.contacts objectAtIndex:indexPath.row]; } --(void) muteContactAtIndexPath:(NSIndexPath *) indexPath +-(void) muteContactAtIndexPath:(NSIndexPath*) indexPath { - MLContact *contact = [self contactAtIndexPath:indexPath]; - if(contact){ + MLContact* contact = [self contactAtIndexPath:indexPath]; + if(contact) [[DataLayer sharedInstance] muteJid:contact.contactJid]; - } } --(void) unMuteContactAtIndexPath:(NSIndexPath *) indexPath +-(void) unMuteContactAtIndexPath:(NSIndexPath*) indexPath { - MLContact *contact = [self contactAtIndexPath:indexPath]; - if(contact){ + MLContact* contact = [self contactAtIndexPath:indexPath]; + if(contact) [[DataLayer sharedInstance] unMuteJid:contact.contactJid]; - } } -(void) blockContactAtIndexPath:(NSIndexPath *) indexPath { - MLContact *contact = [self contactAtIndexPath:indexPath]; - if(contact){ + MLContact* contact = [self contactAtIndexPath:indexPath]; + if(contact) [[DataLayer sharedInstance] blockJid:contact.contactJid]; - } } -(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { - NSInteger toreturn=0; - if(self.searchResults.count>0) { - toreturn =1; - } - else{ - if([[HelperTools defaultsDB] boolForKey:@"OfflineContact"]) - toreturn =3; - else - toreturn =2; - } - return toreturn; + return 1; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +- (NSInteger)tableView:(UITableView*) tableView numberOfRowsInSection:(NSInteger) section { - NSInteger toReturn=0; - if(self.searchResults.count>0) { - toReturn=[self.searchResults count]; - } - //if(tableView ==self.view) - else { - switch (section) { - case konlineSection: - toReturn= [self.contacts count]; - break; - case kofflineSection: - toReturn=[self.offlineContacts count]; - break; - default: - break; - } - } - - - return toReturn; + if(self.searchResults.count > 0) + return [self.searchResults count]; + else + return [self.contacts count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MLContact* row = nil; - if(self.searchResults.count > 0) { + if(self.searchResults.count > 0) row = [self.searchResults objectAtIndex:indexPath.row]; - } else - { - if(indexPath.section == konlineSection) - { - row = [self.contacts objectAtIndex:indexPath.row]; - } - else if(indexPath.section == kofflineSection) - { - row = [self.offlineContacts objectAtIndex:indexPath.row]; - } - else { - DDLogError(@"Could not identify cell section"); - } - } + row = [self.contacts objectAtIndex:indexPath.row]; MLContactCell* cell = [tableView dequeueReusableCellWithIdentifier:@"ContactCell"]; if(!cell) - { cell = [[MLContactCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ContactCell"]; - } cell.count = 0; cell.userImage.image = nil; @@ -387,46 +300,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [cell showDisplayName:row.contactDisplayName]; - if(![row.statusMessage isEqualToString:@"(null)"] && ![row.statusMessage isEqualToString:@""]) { + if(![row.statusMessage isEqualToString:@"(null)"] && ![row.statusMessage isEqualToString:@""]) [cell showStatusText:row.statusMessage]; - } - else { + else [cell showStatusText:nil]; - } - if(row.isGroup && row.groupSubject) { + if(row.isGroup && row.groupSubject) [cell showStatusText:row.groupSubject]; - } - - if(tableView == self.view) { - if(indexPath.section == konlineSection) - { - NSString* stateString = [row.state stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ; - - if(([stateString isEqualToString:@"away"]) || - ([stateString isEqualToString:@"dnd"])|| - ([stateString isEqualToString:@"xa"]) - ) - { - cell.status = kStatusAway; - } - else if([row.state isEqualToString:@"(null)"] || - [row.state isEqualToString:@""]) - cell.status=kStatusOnline; - } - else if(indexPath.section == kofflineSection) { - cell.status=kStatusOffline; - }} - else { - if(row.isOnline == YES) - { - cell.status = kStatusOnline; - } - else - { - cell.status = kStatusOffline; - } - } cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton; @@ -441,7 +321,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [[MLImageManager sharedInstance] getIconForContact:row.contactJid andAccount:row.accountId withCompletion:^(UIImage *image) { cell.userImage.image=image; }]; - [cell setOrb]; BOOL muted = [[DataLayer sharedInstance] isMutedJid:row.contactJid]; dispatch_async(dispatch_get_main_queue(), ^{ @@ -457,54 +336,37 @@ -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPat return 60.0f; } --(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath { +-(NSString*) tableView:(UITableView*) tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath*) indexPath +{ return NSLocalizedString(@"Remove Contact",@""); } -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +-(BOOL) tableView:(UITableView*) tableView canEditRowAtIndexPath:(NSIndexPath*) indexPath { - if(tableView ==self.view) { + if(tableView == self.view) return YES; - } else - { return NO; - } } -- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath +-(BOOL) tableView:(UITableView*) tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath*) indexPath { - if(tableView ==self.view) { + if(tableView == self.view) return YES; - } else - { return NO; - } } -(void) deleteRowAtIndexPath:(NSIndexPath *) indexPath { - MLContact* contact; - if ((indexPath.section == 1) && (indexPath.row<=[self.contacts count]) ) { - contact=[self.contacts objectAtIndex:indexPath.row]; - } - else if((indexPath.section == 2) && (indexPath.row<=[self.offlineContacts count]) ) { - contact=[self.offlineContacts objectAtIndex:indexPath.row]; - } - else { - //we cannot delete here - return; - } - + MLContact* contact = [self.contacts objectAtIndex:indexPath.row]; NSString* messageString = [NSString stringWithFormat:NSLocalizedString(@"Remove %@ from contacts?", @""), contact.contactJid]; NSString* detailString = NSLocalizedString(@"They will no longer see when you are online. They may not be able to access your encryption keys.", @""); - BOOL isMUC=contact.isGroup; - if(isMUC) + if(contact.isGroup) { - messageString =NSLocalizedString(@"Leave this converstion?", @""); - detailString=nil; + messageString = NSLocalizedString(@"Leave this converstion?", @""); + detailString = nil; } UIAlertController* alert = [UIAlertController alertControllerWithTitle:messageString @@ -514,26 +376,14 @@ -(void) deleteRowAtIndexPath:(NSIndexPath *) indexPath }]]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", @"") style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) { - if(isMUC) { + if(contact.isGroup) [[MLXMPPManager sharedInstance] leaveRoom:contact.contactJid withNick:contact.accountNickInGroup forAccountId:contact.accountId ]; - } - else { + else [[MLXMPPManager sharedInstance] removeContact:contact]; - } if(self.searchResults.count == 0) { [self.contactsTable beginUpdates]; - if ((indexPath.section == 1) && (indexPath.row <= [self.contacts count]) ) { - [self.contacts removeObjectAtIndex:indexPath.row]; - } - else if((indexPath.section==2) && (indexPath.row<=[self.offlineContacts count]) ) { - [self.offlineContacts removeObjectAtIndex:indexPath.row]; - } - else { - //nothing to delete just end - [self.contactsTable endUpdates]; - return; - } + [self.contacts removeObjectAtIndex:indexPath.row]; [self.contactsTable deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; @@ -541,74 +391,53 @@ -(void) deleteRowAtIndexPath:(NSIndexPath *) indexPath } }]]; - - alert.popoverPresentationController.sourceView=self.tableView; - + alert.popoverPresentationController.sourceView = self.tableView; [self presentViewController:alert animated:YES completion:nil]; } -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - if (editingStyle == UITableViewCellEditingStyleDelete) { +-(void) tableView:(UITableView*) tableView commitEditingStyle:(UITableViewCellEditingStyle) editingStyle forRowAtIndexPath:(NSIndexPath*) indexPath +{ + if(editingStyle == UITableViewCellEditingStyleDelete) [self deleteRowAtIndexPath:indexPath]; - } } -- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath +-(void) tableView:(UITableView*) tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath*) indexPath { - MLContact *contactDic; + MLContact* contactDic; if(self.searchResults.count>0) - { - contactDic= [self.searchResults objectAtIndex:indexPath.row]; - } - else { - if(indexPath.section==konlineSection) { - contactDic=[self.contacts objectAtIndex:indexPath.row]; - } - else { - contactDic=[self.offlineContacts objectAtIndex:indexPath.row]; - } - } - + contactDic = [self.searchResults objectAtIndex:indexPath.row]; + else + contactDic = [self.contacts objectAtIndex:indexPath.row]; [self performSegueWithIdentifier:@"showDetails" sender:contactDic]; } --(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +-(void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath { MLContact* row; if(self.searchResults.count>0) + row = [self.searchResults objectAtIndex:indexPath.row]; + else { - row= [self.searchResults objectAtIndex:indexPath.row]; - } else - { - if((indexPath.section==konlineSection)) - { - if(indexPath.row*) searchContactsWithString:(NSString*) search; --(NSMutableArray*) onlineContactsSortedBy:(NSString*) sort; +-(NSMutableArray*) contactList; -(NSArray*) resourcesForContact:(NSString*)contact ; -(NSArray*) getSoftwareVersionInfoForContact:(NSString*)contact resource:(NSString*)resource andAccount:(NSString*)account; -(void) setSoftwareVersionInfoForContact:(NSString*)contact @@ -66,14 +66,13 @@ extern NSString* const kMessageTypeUrl; withAppName:(NSString*)appName appVersion:(NSString*)appVersion andPlatformOS:(NSString*)platformOS; --(NSMutableArray*) offlineContacts; #pragma mark Ver string and Capabilities -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user andAccountNo:(NSString*) acctNo; -(NSString*) getVerForUser:(NSString*) user andResource:(NSString*) resource; -(void) setVer:(NSString*) ver forUser:(NSString*) user andResource:(NSString*) resource; --(NSSet*) getCapsforVer:(NSString*) ver; +-(NSSet* _Nullable) getCapsforVer:(NSString*) ver; -(void) setCaps:(NSSet*) caps forVer:(NSString*) ver; #pragma mark presence functions @@ -99,8 +98,6 @@ extern NSString* const kMessageTypeUrl; -(void) setAvatarHash:(NSString*) hash forContact:(NSString*) contact andAccount:(NSString*) accountNo; -(NSString*) getAvatarHashForContact:(NSString*) buddy andAccount:(NSString*) accountNo; --(BOOL) isBuddyOnline:(NSString*) buddy forAccount:(NSString*) accountNo; - -(BOOL) saveMessageDraft:(NSString*) buddy forAccount:(NSString*) accountNo withComment:(NSString*) comment; -(NSString*) loadMessageDraft:(NSString*) buddy forAccount:(NSString*) accountNo; @@ -120,8 +117,6 @@ extern NSString* const kMessageTypeUrl; -(BOOL) updateMucSubject:(NSString *) subject forAccount:(NSString *) accountNo andRoom:(NSString *) room; -(NSString*) mucSubjectforAccount:(NSString *) accountNo andRoom:(NSString *) room; --(void) setMessageId:(NSString*) messageid stanzaId:(NSString *) stanzaId; - /** Calls with YES if contact has already been added to the database for this account */ @@ -133,13 +128,13 @@ extern NSString* const kMessageTypeUrl; -(NSArray*) enabledAccountList; -(BOOL) isAccountEnabled:(NSString*) accountNo; -(BOOL) doesAccountExistUser:(NSString*) user andDomain:(NSString *) domain; --(NSNumber*) accountIDForUser:(NSString*) user andDomain:(NSString *) domain; +-(NSNumber* _Nullable) accountIDForUser:(NSString*) user andDomain:(NSString *) domain; --(NSDictionary*) detailsForAccount:(NSString*) accountNo; --(NSString*) jidOfAccount:(NSString*) accountNo; +-(NSMutableDictionary* _Nullable) detailsForAccount:(NSString*) accountNo; +-(NSString* _Nullable) jidOfAccount:(NSString*) accountNo; -(BOOL) updateAccounWithDictionary:(NSDictionary *) dictionary; --(NSNumber*) addAccountWithDictionary:(NSDictionary *) dictionary; +-(NSNumber* _Nullable) addAccountWithDictionary:(NSDictionary *) dictionary; -(BOOL) removeAccount:(NSString*) accountNo; @@ -149,19 +144,20 @@ extern NSString* const kMessageTypeUrl; */ -(BOOL) disableEnabledAccount:(NSString*) accountNo; --(NSMutableDictionary *) readStateForAccount:(NSString*) accountNo; --(void) persistState:(NSMutableDictionary *) state forAccount:(NSString*) accountNo; +-(NSMutableDictionary* _Nullable) readStateForAccount:(NSString*) accountNo; +-(void) persistState:(NSDictionary*) state forAccount:(NSString*) accountNo; #pragma mark - message Commands /** returns messages with the provided local id number */ --(MLMessage*) messageForHistoryID:(NSInteger) historyID; +-(NSArray*) messagesForHistoryIDs:(NSArray*) historyIDs; +-(MLMessage* _Nullable) messageForHistoryID:(NSNumber* _Nullable) historyID; /* adds a specified message to the database */ --(void) addMessageFrom:(NSString*) from to:(NSString*) to forAccount:(NSString*) accountNo withBody:(NSString*) message actuallyfrom:(NSString*) actualfrom sent:(BOOL) sent unread:(BOOL) unread messageId:(NSString *) messageid serverMessageId:(NSString *) stanzaid messageType:(NSString *) messageType andOverrideDate:(NSDate *) messageDate encrypted:(BOOL) encrypted backwards:(BOOL) backwards displayMarkerWanted:(BOOL) displayMarkerWanted withCompletion: (void (^)(BOOL, NSString*, NSNumber*))completion; +-(NSNumber*) addMessageFrom:(NSString*) from to:(NSString*) to forAccount:(NSString*) accountNo withBody:(NSString*) message actuallyfrom:(NSString*) actualfrom sent:(BOOL) sent unread:(BOOL) unread messageId:(NSString*) messageid serverMessageId:(NSString*) stanzaid messageType:(NSString*) messageType andOverrideDate:(NSDate*) messageDate encrypted:(BOOL) encrypted backwards:(BOOL) backwards displayMarkerWanted:(BOOL) displayMarkerWanted; /* Marks a message as sent. When the server acked it @@ -183,8 +179,16 @@ extern NSString* const kMessageTypeUrl; */ -(void) setMessageId:(NSString *) messageid previewText:(NSString *) text andPreviewImage:(NSString *) image; +-(void) setMessageId:(NSString*) messageid stanzaId:(NSString *) stanzaId; +-(void) setMessageHistoryId:(NSNumber*) historyId filetransferMimeType:(NSString*) mimeType filetransferSize:(NSNumber*) size; +-(void) setMessageHistoryId:(NSNumber*) historyId messageType:(NSString*) messageType; + -(void) clearMessages:(NSString *) accountNo; -(void) deleteMessageHistory:(NSNumber *) messageNo; +-(void) updateMessageHistory:(NSNumber*) messageNo withText:(NSString*) newText; +-(NSNumber* _Nullable) getHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from andAccount:(NSString*) accountNo; + +-(BOOL) checkLMCEligible:(NSNumber* _Nullable) historyID from:(NSString* _Nullable) from; #pragma mark - message history @@ -194,56 +198,36 @@ extern NSString* const kMessageTypeUrl; -(NSArray *) allMessagesForContact:(NSString* ) buddy forAccount:(NSString *) accountNo; --(NSMutableArray*) lastMessageForContact:(NSString *) contact forAccount:(NSString *) accountNo; +-(MLMessage*) lastMessageForContact:(NSString *) contact forAccount:(NSString *) accountNo; -(NSString*) lastStanzaIdForAccount:(NSString*) accountNo; -(void) setLastStanzaId:(NSString*) lastStanzaId forAccount:(NSString*) accountNo; -(NSArray *) messageHistoryListDates:(NSString *) buddy forAccount: (NSString *) accountNo; -(NSArray *) messageHistoryDateForContact:(NSString *) buddy forAccount:(NSString *) accountNo forDate:(NSString*) date; -/** - retrieves the date of the the last message to or from this contact - */ --(NSDate*) lastMessageDateForContact:(NSString*) contact andAccount:(NSString*) accountNo; - --(NSDate*) lastMessageDateAccount:(NSString*) accountNo; - - -(BOOL) messageHistoryClean:(NSString*) buddy forAccount:(NSString*) accountNo; --(BOOL) messageHistoryCleanAll; -(NSMutableArray *) messageHistoryContacts:(NSString*) accountNo; -(NSArray*) markMessagesAsReadForBuddy:(NSString*) buddy andAccount:(NSString*) accountNo tillStanzaId:(NSString* _Nullable) stanzaId wasOutgoing:(BOOL) outgoing; --(void) addMessageHistoryFrom:(NSString*) from to:(NSString*) to forAccount:(NSString*) accountNo withMessage:(NSString*) message actuallyFrom:(NSString*) actualfrom withId:(NSString *)messageId encrypted:(BOOL) encrypted withCompletion:(void (^)(BOOL, NSString*, NSNumber*)) completion; - -/** -retrieves the actual_from of the the last message from hisroty id -*/ --(NSString*)lastMessageActualFromByHistoryId:(NSNumber*) lastMsgHistoryId; +-(NSNumber*) addMessageHistoryFrom:(NSString*) from to:(NSString*) to forAccount:(NSString*) accountNo withMessage:(NSString*) message actuallyFrom:(NSString*) actualfrom withId:(NSString*) messageId encrypted:(BOOL) encrypted messageType:(NSString*) messageType mimeType:(NSString* _Nullable) mimeType size:(NSNumber* _Nullable) size; #pragma mark active contacts -(NSMutableArray*) activeContactsWithPinned:(BOOL) pinned; -(NSMutableArray*) activeContactDict; -(void) removeActiveBuddy:(NSString*) buddyname forAccount:(NSString*) accountNo; --(BOOL) addActiveBuddies:(NSString*) buddyname forAccount:(NSString*) accountNo; +-(void) addActiveBuddies:(NSString*) buddyname forAccount:(NSString*) accountNo; -(BOOL) isActiveBuddy:(NSString*) buddyname forAccount:(NSString*) accountNo; -(BOOL) updateActiveBuddy:(NSString*) buddyname setTime:(NSString *)timestamp forAccount:(NSString*) accountNo; #pragma mark count unread --(NSNumber*) countUserUnreadMessages:(NSString*) buddy forAccount:(NSString*) accountNo; +-(NSNumber*) countUserUnreadMessages:(NSString* _Nullable) buddy forAccount:(NSString* _Nullable) accountNo; -(NSNumber*) countUnreadMessages; //set all unread messages to read -(void) setAllMessagesAsRead; -/** - checks HTTP head on URL to determine the message type - */ --(NSString*) messageTypeForMessage:(NSString *) messageString withKeepThread:(BOOL) keepThread; - - -(void) muteJid:(NSString *) jid; -(void) unMuteJid:(NSString *) jid; -(BOOL) isMutedJid:(NSString *) jid; @@ -261,9 +245,6 @@ retrieves the actual_from of the the last message from hisroty id -(void) encryptForJid:(NSString*) jid andAccountNo:(NSString*) accountNo; -(void) disableEncryptForJid:(NSString*) jid andAccountNo:(NSString*) accountNo; --(void) createImageCache:(NSString *) path forUrl:(NSString*) url; --(void) deleteImageCacheForUrl:(NSString*) url; --(NSString* _Nullable) imageCacheForUrl:(NSString* _Nonnull) url; -(NSMutableArray*) allAttachmentsFromContact:(NSString*) contact forAccount:(NSString*) accountNo; -(NSDate*) lastInteractionOfJid:(NSString* _Nonnull) jid forAccountNo:(NSString* _Nonnull) accountNo; -(void) setLastInteraction:(NSDate*) lastInteractionTime forJid:(NSString* _Nonnull) jid andAccountNo:(NSString* _Nonnull) accountNo; @@ -285,6 +266,12 @@ retrieves the actual_from of the the last message from hisroty id accountNo:(NSString* _Nonnull) accountNo betweenBuddy:(NSString* _Nonnull) accountJid1 andBuddy:(NSString* _Nonnull) accountJid2; + +-(NSArray*) getAllCachedImages; +-(void) removeImageCacheTables; +-(NSArray*) getAllMessagesForFiletransferUrl:(NSString*) url; +-(void) upgradeImageMessagesToFiletransferMessages; + @end NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/DataLayer.m b/Monal/Classes/DataLayer.m index 4012601fd4..e27f441352 100644 --- a/Monal/Classes/DataLayer.m +++ b/Monal/Classes/DataLayer.m @@ -15,6 +15,7 @@ #import "XMPPMessage.h" #import "XMPPIQ.h" #import "XMPPDataForm.h" +#import "MLFiletransfer.h" @interface DataLayer() @property (readonly, strong) MLSQLite* db; @@ -38,13 +39,12 @@ @implementation DataLayer NSString *const kUsername = @"username"; -NSString *const kMessageType = @"messageType"; -NSString *const kMessageTypeGeo = @"Geo"; -NSString *const kMessageTypeImage = @"Image"; -NSString *const kMessageTypeMessageDraft = @"MessageDraft"; NSString *const kMessageTypeStatus = @"Status"; +NSString *const kMessageTypeMessageDraft = @"MessageDraft"; NSString *const kMessageTypeText = @"Text"; +NSString *const kMessageTypeGeo = @"Geo"; NSString *const kMessageTypeUrl = @"Url"; +NSString *const kMessageTypeFiletransfer = @"Filetransfer"; static NSString* dbPath; static NSDateFormatter* dbFormatter; @@ -139,40 +139,17 @@ -(NSArray*) accountList -(NSNumber*) enabledAccountCnts { - NSString* query = @"SELECT COUNT(*) FROM account WHERE enabled=1"; - return (NSNumber*)[self.db executeScalar:query]; + return (NSNumber*)[self.db executeScalar:@"SELECT COUNT(*) FROM account WHERE enabled=1;"]; } -(NSArray*) enabledAccountList { - NSString* query = @"select * from account where enabled=1 order by account_id asc"; - NSArray* toReturn = [self.db executeReader:query andArguments:@[]] ; - - if(toReturn!=nil) - { - DDLogVerbose(@" count: %lu", (unsigned long)[toReturn count]); - - return toReturn; - } - else - { - DDLogError(@"account list is empty or failed to read"); - - return nil; - } + return [self.db executeReader:@"SELECT * FROM account WHERE enabled=1 ORDER BY account_id ASC;"] ; } -(BOOL) isAccountEnabled:(NSString*) accountNo { - NSArray* enabledAccounts = [self enabledAccountList]; - for (NSDictionary* account in enabledAccounts) - { - if([[account objectForKey:@"account_id"] integerValue] == [accountNo integerValue]) - { - return YES; - } - } - return NO; + return [[self.db executeScalar:@"SELECT enabled FROM account WHERE account_id=?;" andArguments:@[accountNo]] boolValue]; } -(NSNumber*) accountIDForUser:(NSString *) user andDomain:(NSString *) domain @@ -201,11 +178,11 @@ -(BOOL) doesAccountExistUser:(NSString*) user andDomain:(NSString *) domain return result.count > 0; } --(NSDictionary*) detailsForAccount:(NSString*) accountNo +-(NSMutableDictionary*) detailsForAccount:(NSString*) accountNo { if(!accountNo) return nil; - NSArray* result = [self.db executeReader:@"SELECT account_id, directTLS, domain, enabled, lastStanzaId, other_port, resource, rosterVersion, selfsigned, server, username, rosterName FROM account WHERE account_id=?;" andArguments:@[accountNo]]; + NSArray* result = [self.db executeReader:@"SELECT * FROM account WHERE account_id=?;" andArguments:@[accountNo]]; if(result != nil && [result count]) { DDLogVerbose(@"count: %lu", (unsigned long)[result count]); @@ -234,7 +211,7 @@ -(NSString*) jidOfAccount:(NSString*) accountNo -(BOOL) updateAccounWithDictionary:(NSDictionary *) dictionary { - NSString* query = @"UPDATE account SET server=?, other_port=?, username=?, resource=?, domain=?, enabled=?, selfsigned=?, directTLS=?, rosterName=? WHERE account_id=?;"; + NSString* query = @"UPDATE account SET server=?, other_port=?, username=?, resource=?, domain=?, enabled=?, selfsigned=?, directTLS=?, rosterName=?, statusMessage=? WHERE account_id=?;"; NSString* server = (NSString *) [dictionary objectForKey:kServer]; NSString* port = (NSString *)[dictionary objectForKey:kPort]; @@ -247,6 +224,7 @@ -(BOOL) updateAccounWithDictionary:(NSDictionary *) dictionary [dictionary objectForKey:kSelfSigned], [dictionary objectForKey:kDirectTLS], [dictionary objectForKey:kRosterName] ? ((NSString*)[dictionary objectForKey:kRosterName]) : @"", + [dictionary objectForKey:@"statusMessage"] ? ((NSString*)[dictionary objectForKey:@"statusMessage"]) : @"", [dictionary objectForKey:kAccountID] ]; @@ -255,7 +233,7 @@ -(BOOL) updateAccounWithDictionary:(NSDictionary *) dictionary -(NSNumber*) addAccountWithDictionary:(NSDictionary*) dictionary { - NSString* query = @"INSERT INTO account (server, other_port, resource, domain, enabled, selfsigned, directTLS, username, rosterName) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?);"; + NSString* query = @"INSERT INTO account (server, other_port, resource, domain, enabled, selfsigned, directTLS, username, rosterName, statusMessage) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; NSString* server = (NSString*) [dictionary objectForKey:kServer]; NSString* port = (NSString*)[dictionary objectForKey:kPort]; @@ -268,7 +246,8 @@ -(NSNumber*) addAccountWithDictionary:(NSDictionary*) dictionary [dictionary objectForKey:kSelfSigned], [dictionary objectForKey:kDirectTLS], ((NSString *)[dictionary objectForKey:kUsername]), - [dictionary objectForKey:kRosterName] ? ((NSString*)[dictionary objectForKey:kRosterName]) : @"" + [dictionary objectForKey:kRosterName] ? ((NSString*)[dictionary objectForKey:kRosterName]) : @"", + [dictionary objectForKey:@"statusMessage"] ? ((NSString*)[dictionary objectForKey:@"statusMessage"]) : @"" ]; BOOL result = [self.db executeNonQuery:query andArguments:params]; // return the accountID @@ -285,7 +264,12 @@ -(BOOL) removeAccount:(NSString*) accountNo // remove all other traces of the account_id in one transaction return [self.db boolWriteTransaction:^{ [self.db executeNonQuery:@"DELETE FROM buddylist WHERE account_id=?;" andArguments:@[accountNo]]; + + NSArray* messageHistoryIDs = [self.db executeScalarReader:@"SELECT message_history_id FROM message_history WHERE messageType=? AND account_id=?;" andArguments:@[kMessageTypeFiletransfer, accountNo]]; + for(NSNumber* historyId in messageHistoryIDs) + [MLFiletransfer deleteFileForMessage:[self messageForHistoryID:historyId]]; [self.db executeNonQuery:@"DELETE FROM message_history WHERE account_id=?;" andArguments:@[accountNo]]; + [self.db executeNonQuery:@"DELETE FROM activechats WHERE account_id=?;" andArguments:@[accountNo]]; return [self.db executeNonQuery:@"DELETE FROM account WHERE account_id=?;" andArguments:@[accountNo]]; }]; @@ -329,7 +313,7 @@ -(NSMutableDictionary *) readStateForAccount:(NSString*) accountNo return nil; } --(void) persistState:(NSMutableDictionary*) state forAccount:(NSString*) accountNo +-(void) persistState:(NSDictionary*) state forAccount:(NSString*) accountNo { if(!accountNo || !state) return; NSString* query = @"update account set state=? where account_id=?"; @@ -364,12 +348,12 @@ -(BOOL) addContact:(NSString*) contact forAccount:(NSString*) accountNo nickname else toPass = cleanNickName; - NSString* query = @"INSERT INTO buddylist ('account_id', 'buddy_name', 'full_name', 'nick_name', 'new', 'online', 'dirty', 'muc', 'muc_nick') VALUES(?, ?, ?, ?, 1, 0, 0, ?, ?) ON CONFLICT(account_id, buddy_name) DO UPDATE SET nick_name=?;"; + NSString* query = @"INSERT INTO buddylist ('account_id', 'buddy_name', 'full_name', 'nick_name', 'muc', 'muc_nick') VALUES(?, ?, ?, ?, ?, ?) ON CONFLICT(account_id, buddy_name) DO UPDATE SET nick_name=?;"; if(!accountNo || !contact) return NO; else { - NSArray* params = @[accountNo, contact, @"", toPass, mucNick?@1:@0, mucNick ? mucNick : @"", toPass]; + NSArray* params = @[accountNo, contact, @"", toPass, mucNick ? @1 : @0, mucNick ? mucNick : @"", toPass]; BOOL success = [self.db executeNonQuery:query andArguments:params]; return success; } @@ -398,10 +382,10 @@ -(BOOL) resetContactsForAccount:(NSString*) accountNo if(!accountNo) return NO; return [self.db boolWriteTransaction:^{ - NSString* query2 = @"delete from buddy_resources where buddy_id in (select buddy_id from buddylist where account_id=?)"; + NSString* query2 = @"DELETE FROM buddy_resources WHERE buddy_id IN (SELECT buddy_id FROM buddylist WHERE account_id=?);"; NSArray* params = @[accountNo]; [self.db executeNonQuery:query2 andArguments:params]; - NSString* query = @"update buddylist set dirty=0, new=0, online=0, state='offline', status='' where account_id=?"; + NSString* query = @"UPDATE buddylist SET state='offline', status='' WHERE account_id=?;"; return [self.db executeNonQuery:query andArguments:params]; }]; } @@ -420,13 +404,12 @@ -(MLContact*) contactForUsername:(NSString*) username forAccount:(NSString*) acc ON a.buddy_name = b.buddy_name AND a.account_id = b.account_id \ WHERE b.buddy_name=? AND b.account_id=?;" andArguments:@[username, accountNo]]; if(results == nil || [results count] > 1) - DDLogWarn(@"DataLayerError unexpected contact count: username: %@, accountNo %@, count %luu, results: %@", username, accountNo, (unsigned long)(results ? [results count] : 0), results); - /*@throw [NSException exceptionWithName:@"DataLayerError" reason:@"unexpected contact count" userInfo:@{ + @throw [NSException exceptionWithName:@"DataLayerError" reason:@"unexpected contact count" userInfo:@{ @"username": username, @"accountNo": accountNo, @"count": [NSNumber numberWithInteger:[results count]], @"results": results ? results : @"(null)" - }];*/ + }]; //check if we know this contact and return a dummy one if not if([results count] == 0) @@ -444,7 +427,7 @@ -(MLContact*) contactForUsername:(NSString*) username forAccount:(NSString*) acc @"Muc": @NO, @"pinned": @NO, @"status": @"", - @"state": kSubNone, + @"state": @"offline", @"count": @0, @"isActiveChat": @NO }]; @@ -454,42 +437,23 @@ -(MLContact*) contactForUsername:(NSString*) username forAccount:(NSString*) acc } --(NSArray*) searchContactsWithString:(NSString*) search +-(NSArray*) searchContactsWithString:(NSString*) search { NSString* likeString = [NSString stringWithFormat:@"%%%@%%", search]; - NSString* query = @"SELECT buddy_name FROM buddylist WHERE buddy_name like ? OR full_name like ? OR nick_name like ? ORDER BY full_name, nick_name, buddy_name COLLATE NOCASE ASC;"; + NSString* query = @"SELECT buddy_name, account_id FROM buddylist WHERE buddy_name LIKE ? OR full_name LIKE ? OR nick_name LIKE ? ORDER BY full_name, nick_name, buddy_name COLLATE NOCASE ASC;"; NSArray* params = @[likeString, likeString, likeString]; - NSMutableArray* toReturn = [[NSMutableArray alloc] init]; + NSMutableArray* toReturn = [[NSMutableArray alloc] init]; for(NSDictionary* dic in [self.db executeReader:query andArguments:params]) [toReturn addObject:[self contactForUsername:dic[@"buddy_name"] forAccount:dic[@"account_id"]]]; return toReturn; } --(NSMutableArray*) onlineContactsSortedBy:(NSString*) sort -{ - NSString* query = @""; - - if([sort isEqualToString:@"Name"]) - { - query = @"SELECT buddy_name, account_id FROM buddylist WHERE online=1 AND subscription='both' ORDER BY nick_name, full_name, buddy_name COLLATE NOCASE ASC;"; - } - - if([sort isEqualToString:@"Status"]) - { - query = @"SELECT buddy_name, account_id FROM buddylist WHERE online=1 AND subscription='both' ORDER BY state, nick_name, full_name, buddy_name COLLATE NOCASE ASC;"; - } - - NSMutableArray* toReturn = [[NSMutableArray alloc] init]; - for(NSDictionary* dic in [self.db executeReader:query]) - [toReturn addObject:[self contactForUsername:dic[@"buddy_name"] forAccount:dic[@"account_id"]]]; - return toReturn; -} - --(NSMutableArray*) offlineContacts +-(NSMutableArray*) contactList { - NSString* query = @"SELECT buddy_name, a.account_id FROM buddylist AS A INNER JOIN account AS b ON a.account_id=b.account_id WHERE online=0 AND enabled=1 ORDER BY nick_name, full_name, buddy_name COLLATE NOCASE ASC;"; + //only list contacts having a roster entry (e.g. kSubBoth, kSubTo or kSubFrom) + NSString* query = @"SELECT buddy_name, a.account_id FROM buddylist AS A INNER JOIN account AS b ON a.account_id=b.account_id WHERE (a.subscription=? OR a.subscription=? OR a.subscription=?) AND b.enabled=1 ORDER BY nick_name, full_name, buddy_name COLLATE NOCASE ASC;"; NSMutableArray* toReturn = [[NSMutableArray alloc] init]; - for(NSDictionary* dic in [self.db executeReader:query]) + for(NSDictionary* dic in [self.db executeReader:query andArguments:@[kSubBoth, kSubTo, kSubFrom]]) [toReturn addObject:[self contactForUsername:dic[@"buddy_name"] forAccount:dic[@"account_id"]]]; return toReturn; } @@ -498,7 +462,7 @@ -(NSMutableArray*) offlineContacts -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user andAccountNo:(NSString*) acctNo { - NSString* query = @"select count(*) from buddylist as a inner join buddy_resources as b on a.buddy_id=b.buddy_id inner join ver_info as c on b.ver=c.ver where buddy_name=? and account_id=? and cap=?"; + NSString* query = @"SELECT COUNT(*) FROM buddylist AS a INNER JOIN buddy_resources AS b ON a.buddy_id=b.buddy_id INNER JOIN ver_info AS c ON b.ver=c.ver WHERE buddy_name=? AND account_id=? AND cap=?;"; NSArray *params = @[user, acctNo, cap]; NSNumber* count = (NSNumber*) [self.db executeScalar:query andArguments:params]; return [count integerValue]>0; @@ -506,7 +470,7 @@ -(BOOL) checkCap:(NSString*) cap forUser:(NSString*) user andAccountNo:(NSString -(NSString*) getVerForUser:(NSString*) user andResource:(NSString*) resource { - NSString* query = @"select ver from buddy_resources as A inner join buddylist as B on a.buddy_id=b.buddy_id where resource=? and buddy_name=?"; + NSString* query = @"SELECT ver FROM buddy_resources AS A INNER JOIN buddylist AS B ON a.buddy_id=b.buddy_id WHERE resource=? AND buddy_name=?;"; NSArray * params = @[resource, user]; NSString* ver = (NSString*) [self.db executeScalar:query andArguments:params]; return ver; @@ -640,35 +604,32 @@ -(void) setOnlineBuddy:(XMPPPresence*) presenceObj forAccount:(NSString*) accoun { [self.db voidWriteTransaction:^{ [self setResourceOnline:presenceObj forAccount:accountNo]; - if(![self isBuddyOnline:presenceObj.fromUser forAccount:accountNo]) - { - NSString* query = @"update buddylist set online=1, new=1, muc=? where account_id=? and buddy_name=?"; - NSArray* params = @[[NSNumber numberWithBool:[presenceObj check:@"{http://jabber.org/protocol/muc#user}x"]], accountNo, presenceObj.fromUser]; - [self.db executeNonQuery:query andArguments:params]; - } + NSString* query = @"UPDATE buddylist SET state='', muc=? WHERE account_id=? AND buddy_name=? AND state='offline';"; + NSArray* params = @[@([presenceObj check:@"{http://jabber.org/protocol/muc#user}x"]), accountNo, presenceObj.fromUser]; + [self.db executeNonQuery:query andArguments:params]; }]; } -(BOOL) setOfflineBuddy:(XMPPPresence*) presenceObj forAccount:(NSString*) accountNo { return [self.db boolWriteTransaction:^{ - NSString* query1 = @"select buddy_id from buddylist where account_id=? and buddy_name=?;"; + NSString* query1 = @"SELECT buddy_id FROM buddylist WHERE account_id=? AND buddy_name=?;"; NSArray* params=@[accountNo, presenceObj.fromUser]; NSString* buddyid = (NSString*)[self.db executeScalar:query1 andArguments:params]; if(buddyid == nil) return NO; - NSString* query2 = @"delete from buddy_resources where buddy_id=? and resource=?"; + NSString* query2 = @"DELETE FROM buddy_resources WHERE buddy_id=? AND resource=?;"; NSArray* params2 = @[buddyid, presenceObj.fromResource ? presenceObj.fromResource : @""]; if([self.db executeNonQuery:query2 andArguments:params2] == NO) return NO; //see how many left - NSString* resourceCount = [self.db executeScalar:@"select count(buddy_id) from buddy_resources where buddy_id=?;" andArguments:@[buddyid]]; + NSString* resourceCount = [self.db executeScalar:@"SELECT COUNT(buddy_id) FROM buddy_resources WHERE buddy_id=?;" andArguments:@[buddyid]]; if([resourceCount integerValue] < 1) { - NSString* query = @"update buddylist set online=0, state='offline', dirty=1 where account_id=? and buddy_name=?;"; + NSString* query = @"UPDATE buddylist SET state='offline' WHERE account_id=? AND buddy_name=?;"; NSArray* params4 = @[accountNo, presenceObj.fromUser]; BOOL retval = [self.db executeNonQuery:query andArguments:params4]; return retval; @@ -690,14 +651,14 @@ -(void) setBuddyState:(XMPPPresence*) presenceObj forAccount:(NSString*) account toPass = [presenceObj findFirst:@"show#"]; } - NSString* query = @"update buddylist set state=?, dirty=1 where account_id=? and buddy_name=?;"; + NSString* query = @"UPDATE buddylist SET state=? WHERE account_id=? AND buddy_name=?;"; [self.db executeNonQuery:query andArguments:@[toPass, accountNo, presenceObj.fromUser]]; } -(NSString*) buddyState:(NSString*) buddy forAccount:(NSString*) accountNo { - NSString* query = @"select state from buddylist where account_id=? and buddy_name=?"; + NSString* query = @"SELECT state FROM buddylist WHERE account_id=? AND buddy_name=?;"; NSArray* params = @[accountNo, buddy]; NSString* state = (NSString*)[self.db executeScalar:query andArguments:params]; return state; @@ -705,7 +666,7 @@ -(NSString*) buddyState:(NSString*) buddy forAccount:(NSString*) accountNo -(BOOL) hasContactRequestForAccount:(NSString*) accountNo andBuddyName:(NSString*) buddy { - NSString* query = @"SELECT count(*) FROM subscriptionRequests WHERE account_id=? AND buddy_name=?"; + NSString* query = @"SELECT COUNT(*) FROM subscriptionRequests WHERE account_id=? AND buddy_name=?"; NSNumber* result = (NSNumber*)[self.db executeScalar:query andArguments:@[accountNo, buddy]]; @@ -745,20 +706,20 @@ -(void) setBuddyStatus:(XMPPPresence*) presenceObj forAccount:(NSString*) accoun toPass = [presenceObj findFirst:@"status#"]; } - NSString* query = @"update buddylist set status=?, dirty=1 where account_id=? and buddy_name=?;"; + NSString* query = @"UPDATE buddylist SET status=? WHERE account_id=? AND buddy_name=?;"; [self.db executeNonQuery:query andArguments:@[toPass, accountNo, presenceObj.fromUser]]; } -(NSString*) buddyStatus:(NSString*) buddy forAccount:(NSString*) accountNo { - NSString* query = @"select status from buddylist where account_id=? and buddy_name=?"; + NSString* query = @"SELECT status FROM buddylist WHERE account_id=? AND buddy_name=?;"; NSString* iconname = (NSString *)[self.db executeScalar:query andArguments:@[accountNo, buddy]]; return iconname; } -(NSString *) getRosterVersionForAccount:(NSString*) accountNo { - NSString* query = @"SELECT rosterVersion from account where account_id=?"; + NSString* query = @"SELECT rosterVersion FROM account WHERE account_id=?;"; NSArray* params = @[ accountNo]; NSString * version=(NSString*)[self.db executeScalar:query andArguments:params]; return version; @@ -806,7 +767,7 @@ -(void) setFullName:(NSString*) fullName forContact:(NSString*) contact andAccou if(!toPass) return; - NSString* query = @"update buddylist set full_name=?, dirty=1 where account_id=? and buddy_name=?"; + NSString* query = @"UPDATE buddylist SET full_name=? WHERE account_id=? AND buddy_name=?;"; NSArray* params = @[toPass , accountNo, contact]; [self.db executeNonQuery:query andArguments:params]; } @@ -815,7 +776,7 @@ -(void) setAvatarHash:(NSString*) hash forContact:(NSString*) contact andAccount { [self.db voidWriteTransaction:^{ [self.db executeNonQuery:@"UPDATE account SET iconhash=? WHERE account_id=? AND printf('%s@%s', username, domain)=?;" andArguments:@[hash, accountNo, contact]]; - [self.db executeNonQuery:@"UPDATE buddylist SET iconhash=?, dirty=1 WHERE account_id=? AND buddy_name=?;" andArguments:@[hash, accountNo, contact]]; + [self.db executeNonQuery:@"UPDATE buddylist SET iconhash=? WHERE account_id=? AND buddy_name=?;" andArguments:@[hash, accountNo, contact]]; }]; } @@ -848,17 +809,9 @@ -(BOOL) isContactInList:(NSString*) buddy forAccount:(NSString*) accountNo return toreturn; } --(BOOL) isBuddyOnline:(NSString*) buddy forAccount:(NSString*) accountNo -{ - NSNumber* count = [self.db executeScalar:@"select count(buddy_id) from buddylist where account_id=? and buddy_name=? and online=1;" andArguments:@[accountNo, buddy]]; - if(count != nil && [count integerValue] > 0) - return YES; - return NO; -} - -(BOOL) saveMessageDraft:(NSString*) buddy forAccount:(NSString*) accountNo withComment:(NSString*) comment { - NSString* query = @"update buddylist set messageDraft=? where account_id=? and buddy_name=?"; + NSString* query = @"UPDATE buddylist SET messageDraft=? WHERE account_id=? AND buddy_name=?;"; NSArray* params = @[comment, accountNo, buddy]; BOOL success = [self.db executeNonQuery:query andArguments:params]; @@ -867,7 +820,7 @@ -(BOOL) saveMessageDraft:(NSString*) buddy forAccount:(NSString*) accountNo with -(NSString*) loadMessageDraft:(NSString*) buddy forAccount:(NSString*) accountNo { - NSString* query = @"SELECT messageDraft from buddylist where account_id=? and buddy_name=?"; + NSString* query = @"SELECT messageDraft FROM buddylist WHERE account_id=? AND buddy_name=?;"; NSArray* params = @[accountNo, buddy]; NSObject* messageDraft = [self.db executeScalar:query andArguments:params]; return (NSString*)messageDraft; @@ -946,15 +899,7 @@ -(BOOL) deleteMucFavorite:(NSNumber *) mucid forAccountId:(NSInteger) accountNo -(NSMutableArray*) mucFavoritesForAccount:(NSString*) accountNo { - NSMutableArray* favorites = [self.db executeReader:@"SELECT * FROM muc_favorites WHERE account_id=?;" andArguments:@[accountNo]]; - if(favorites != nil) { - DDLogVerbose(@"fetched muc favorites"); - } - else{ - DDLogVerbose(@"could not fetch muc favorites"); - - } - return favorites; + return [self.db executeReader:@"SELECT * FROM muc_favorites WHERE account_id=?;" andArguments:@[accountNo]]; } -(BOOL) updateMucSubject:(NSString *) subject forAccount:(NSString*) accountNo andRoom:(NSString *) room @@ -980,106 +925,104 @@ -(NSString*) mucSubjectforAccount:(NSString*) accountNo andRoom:(NSString *) roo #pragma mark message Commands --(MLMessage*) messageForHistoryID:(NSInteger) historyID +-(NSArray*) messagesForHistoryIDs:(NSArray*) historyIDs { - NSString* query = @"SELECT IFNULL(actual_from, message_from) AS af, message_from, message_to, account_id, message, received, displayed, displayMarkerWanted, encrypted, timestamp AS thetime, message_history_id, sent, messageid, messageType, previewImage, previewText, unread, errorType, errorReason, stanzaid FROM message_history WHERE message_history_id=?;"; - NSArray* params = @[[NSNumber numberWithInteger:historyID]]; + NSString* idList = [historyIDs componentsJoinedByString:@","]; + NSString* query = [NSString stringWithFormat:@"SELECT IFNULL(actual_from, message_from) AS af, timestamp AS thetime, * FROM message_history WHERE message_history_id IN(%@);", idList]; - for(NSDictionary* dic in [self.db executeReader:query andArguments:params]) - return [MLMessage messageFromDictionary:dic withDateFormatter:dbFormatter]; - return nil; + NSMutableArray* retval = [[NSMutableArray alloc] init]; + for(NSDictionary* dic in [self.db executeReader:query]) + [retval addObject:[MLMessage messageFromDictionary:dic withDateFormatter:dbFormatter]]; + return retval; } --(void) addMessageFrom:(NSString*) from to:(NSString*) to forAccount:(NSString*) accountNo withBody:(NSString*) message actuallyfrom:(NSString*) actualfrom sent:(BOOL) sent unread:(BOOL) unread messageId:(NSString *) messageid serverMessageId:(NSString *) stanzaid messageType:(NSString *) messageType andOverrideDate:(NSDate *) messageDate encrypted:(BOOL) encrypted backwards:(BOOL) backwards displayMarkerWanted:(BOOL) displayMarkerWanted withCompletion: (void (^)(BOOL, NSString*, NSNumber*))completion +-(MLMessage*) messageForHistoryID:(NSNumber*) historyID { - if(!from || !to || !message) - { - if(completion) - completion(NO, nil, nil); - return; - } - - NSString* typeToUse=messageType; - if(!typeToUse) typeToUse=kMessageTypeText; //default to insert - - [self.db beginWriteTransaction]; - if(![self hasMessageForStanzaId:stanzaid orMessageID:messageid toContact:actualfrom onAccount:accountNo]) - { - //this is always from a contact - NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; - NSDate* sourceDate=[NSDate date]; - NSDate* destinationDate; - if(messageDate) - { - //already GMT no need for conversion + if(historyID == nil) + return nil; + NSArray* result = [self messagesForHistoryIDs:@[historyID]]; + if(![result count]) + return nil; + return result[0]; +} - destinationDate = messageDate; - [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - } - else +-(NSNumber*) addMessageFrom:(NSString*) from to:(NSString*) to forAccount:(NSString*) accountNo withBody:(NSString*) message actuallyfrom:(NSString*) actualfrom sent:(BOOL) sent unread:(BOOL) unread messageId:(NSString*) messageid serverMessageId:(NSString*) stanzaid messageType:(NSString*) messageType andOverrideDate:(NSDate*) messageDate encrypted:(BOOL) encrypted backwards:(BOOL) backwards displayMarkerWanted:(BOOL) displayMarkerWanted +{ + if(!from || !to || !message) + return nil; + + return [self.db idWriteTransaction:^{ + if(![self hasMessageForStanzaId:stanzaid orMessageID:messageid toContact:from onAccount:accountNo]) { - NSTimeZone* sourceTimeZone = [NSTimeZone systemTimeZone]; - NSTimeZone* destinationTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - - NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate]; - NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate]; - NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset; + //this is always from a contact + NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + NSDate* sourceDate = [NSDate date]; + NSDate* destinationDate; + if(messageDate) + { + //already GMT no need for conversion + destinationDate = messageDate; + [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; + } + else + { + NSTimeZone* sourceTimeZone = [NSTimeZone systemTimeZone]; + NSTimeZone* destinationTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate]; - } - // note: if it isnt the same day we want to show the full day + NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate]; + NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate]; + NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset; - NSString* dateString = [formatter stringFromDate:destinationDate]; - - //do not do this in MUC - if(!messageType && [actualfrom isEqualToString:from]) - { - NSString* foundMessageType = [self messageTypeForMessage:message withKeepThread:YES]; - NSString* query; - NSArray* params; - if(backwards) + destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate]; + } + // note: if it isnt the same day we want to show the full day + NSString* dateString = [formatter stringFromDate:destinationDate]; + + //do not do this in MUC + if(!messageType && [actualfrom isEqualToString:from]) { - NSNumber* nextHisoryId = [NSNumber numberWithInt:[(NSNumber*)[self.db executeScalar:@"SELECT MIN(message_history_id) FROM message_history;"] intValue] - 1]; - query = @"insert into message_history (message_history_id, account_id, message_from, message_to, timestamp, message, actual_from, unread, sent, displayMarkerWanted, messageid, messageType, encrypted, stanzaid) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; - params = @[nextHisoryId, accountNo, from, to, dateString, message, actualfrom, [NSNumber numberWithBool:unread], [NSNumber numberWithBool:sent], [NSNumber numberWithBool:displayMarkerWanted], messageid?messageid:@"", foundMessageType, [NSNumber numberWithBool:encrypted], stanzaid?stanzaid:@""]; + NSString* query; + NSArray* params; + if(backwards) + { + NSNumber* nextHisoryId = [NSNumber numberWithInt:[(NSNumber*)[self.db executeScalar:@"SELECT MIN(message_history_id) FROM message_history;"] intValue] - 1]; + query = @"insert into message_history (message_history_id, account_id, message_from, message_to, timestamp, message, actual_from, unread, sent, displayMarkerWanted, messageid, messageType, encrypted, stanzaid) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + params = @[nextHisoryId, accountNo, from, to, dateString, message, actualfrom, [NSNumber numberWithBool:unread], [NSNumber numberWithBool:sent], [NSNumber numberWithBool:displayMarkerWanted], messageid?messageid:@"", messageType, [NSNumber numberWithBool:encrypted], stanzaid?stanzaid:@""]; + } + else + { + //we use autoincrement here instead of MAX(message_history_id) + 1 to be a little bit faster (but at the cost of "duplicated code") + query = @"insert into message_history (account_id, message_from, message_to, timestamp, message, actual_from, unread, sent, displayMarkerWanted, messageid, messageType, encrypted, stanzaid) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + params = @[accountNo, from, to, dateString, message, actualfrom, [NSNumber numberWithBool:unread], [NSNumber numberWithBool:sent], [NSNumber numberWithBool:displayMarkerWanted], messageid?messageid:@"", messageType, [NSNumber numberWithBool:encrypted], stanzaid?stanzaid:@""]; + } + DDLogVerbose(@"%@", query); + BOOL success = [self.db executeNonQuery:query andArguments:params]; + if(!success) + return (NSNumber*)nil; + NSNumber* historyId = [self.db lastInsertId]; + [self updateActiveBuddy:actualfrom setTime:dateString forAccount:accountNo]; + return historyId; } else { - //we use autoincrement here instead of MAX(message_history_id) + 1 to be a little bit faster (but at the cost of "duplicated code") - query = @"insert into message_history (account_id, message_from, message_to, timestamp, message, actual_from, unread, sent, displayMarkerWanted, messageid, messageType, encrypted, stanzaid) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; - params = @[accountNo, from, to, dateString, message, actualfrom, [NSNumber numberWithBool:unread], [NSNumber numberWithBool:sent], [NSNumber numberWithBool:displayMarkerWanted], messageid?messageid:@"", foundMessageType, [NSNumber numberWithBool:encrypted], stanzaid?stanzaid:@""]; - } - DDLogVerbose(@"%@", query); - BOOL success = [self.db executeNonQuery:query andArguments:params]; - NSNumber* historyId = [self.db lastInsertId]; - if(success) + NSString* query = @"insert into message_history (account_id, message_from, message_to, timestamp, message, actual_from, unread, sent, messageid, messageType, encrypted, stanzaid) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + NSArray* params = @[accountNo, from, to, dateString, message, actualfrom, [NSNumber numberWithInteger:unread], [NSNumber numberWithInteger:sent], messageid ? messageid : @"", messageType, [NSNumber numberWithInteger:encrypted], stanzaid?stanzaid:@"" ]; + DDLogVerbose(@"%@", query); + BOOL success = [self.db executeNonQuery:query andArguments:params]; + if(!success) + return (NSNumber*)nil; + NSNumber* historyId = [self.db lastInsertId]; [self updateActiveBuddy:actualfrom setTime:dateString forAccount:accountNo]; - [self.db endWriteTransaction]; - if(completion) - completion(success, messageType, historyId); + return historyId; + } } else { - NSString* query = @"insert into message_history (account_id, message_from, message_to, timestamp, message, actual_from, unread, sent, messageid, messageType, encrypted, stanzaid) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; - NSArray* params = @[accountNo, from, to, dateString, message, actualfrom, [NSNumber numberWithInteger:unread], [NSNumber numberWithInteger:sent], messageid?messageid:@"", typeToUse, [NSNumber numberWithInteger:encrypted], stanzaid?stanzaid:@"" ]; - DDLogVerbose(@"%@", query); - BOOL success = [self.db executeNonQuery:query andArguments:params]; - NSNumber* historyId = [self.db lastInsertId]; - if(success) - [self updateActiveBuddy:actualfrom setTime:dateString forAccount:accountNo]; - [self.db endWriteTransaction]; - if(completion) - completion(success, messageType, historyId); + DDLogError(@"Message(%@) %@ with stanzaid %@ already existing, ignoring history update", accountNo, messageid, stanzaid); + return (NSNumber*)nil; } - } - else - { - DDLogError(@"Message(%@) %@ with stanzaid %@ already existing, ignoring history update", accountNo, messageid, stanzaid); - [self.db endWriteTransaction]; - if(completion) - completion(NO, nil, nil); - } + }]; } -(BOOL) hasMessageForStanzaId:(NSString*) stanzaId orMessageID:(NSString*) messageId toContact:(NSString*) contact onAccount:(NSString*) accountNo @@ -1090,27 +1033,36 @@ -(BOOL) hasMessageForStanzaId:(NSString*) stanzaId orMessageID:(NSString*) messa return [self.db boolWriteTransaction:^{ if(stanzaId) { - NSObject* found = [self.db executeScalar:@"SELECT message_history_id FROM message_history WHERE account_id=? AND stanzaid!='' AND stanzaid=?;" andArguments:@[accountNo, stanzaId]]; - if(found) + DDLogVerbose(@"stanzaid provided"); + NSArray* found = [self.db executeReader:@"SELECT * FROM message_history WHERE account_id=? AND stanzaid!='' AND stanzaid=?;" andArguments:@[accountNo, stanzaId]]; + if([found count]) + { + DDLogVerbose(@"stanzaid provided and could be found: %@", found); return YES; + } } //we check message ids per contact to increase uniqueness and abort here if no contact was provided if(!contact) + { + DDLogVerbose(@"no contact given --> message not found"); return NO; + } NSNumber* historyId = (NSNumber*)[self.db executeScalar:@"SELECT message_history_id FROM message_history WHERE account_id=? AND message_from=? AND messageid=?;" andArguments:@[accountNo, contact, messageId]]; - if(historyId) + if(historyId != nil) { + DDLogVerbose(@"found by messageid"); if(stanzaId) { - DDLogVerbose(@"Updating stanzaid of message_history_id %@ to %@ for (account=%@, messageid=%@, contact=%@)...", historyId, stanzaId, accountNo, messageId, contact); + DDLogDebug(@"Updating stanzaid of message_history_id %@ to %@ for (account=%@, messageid=%@, contact=%@)...", historyId, stanzaId, accountNo, messageId, contact); //this entry needs an update of its stanzaid [self.db executeNonQuery:@"UPDATE message_history SET stanzaid=? WHERE message_history_id=?" andArguments:@[stanzaId, historyId]]; } return YES; } + DDLogVerbose(@"nothing worked --> message not found"); return NO; }]; } @@ -1133,7 +1085,7 @@ -(void) setMessageId:(NSString*) messageid sent:(BOOL) sent -(void) setMessageId:(NSString*) messageid received:(BOOL) received { - NSString* query = @"update message_history set received=?, sent=? where messageid=?"; + NSString* query = @"UPDATE message_history SET received=?, sent=? WHERE messageid=?;"; DDLogVerbose(@"setting received confrmed %@", messageid); [self.db executeNonQuery:query andArguments:@[[NSNumber numberWithBool:received], [NSNumber numberWithBool:YES], messageid]]; } @@ -1141,46 +1093,97 @@ -(void) setMessageId:(NSString*) messageid received:(BOOL) received -(void) setMessageId:(NSString*) messageid errorType:(NSString*) errorType errorReason:(NSString*) errorReason { //ignore error if the message was already received by *some* client - if([self.db executeScalar:@"SELECT messageid FROM message_history WHERE messageid=? AND received" andArguments:@[messageid]]) + if([self.db executeScalar:@"SELECT messageid FROM message_history WHERE messageid=? AND received;" andArguments:@[messageid]]) { DDLogVerbose(@"ignoring message error for %@ [%@, %@]", messageid, errorType, errorReason); return; } - NSString* query = @"update message_history set errorType=?, errorReason=? where messageid=?"; + NSString* query = @"UPDATE message_history SET errorType=?, errorReason=? WHERE messageid=?;"; DDLogVerbose(@"setting message error %@ [%@, %@]", messageid, errorType, errorReason); [self.db executeNonQuery:query andArguments:@[errorType, errorReason, messageid]]; } --(void) setMessageId:(NSString*) messageid messageType:(NSString *) messageType +-(void) setMessageHistoryId:(NSNumber*) historyId filetransferMimeType:(NSString*) mimeType filetransferSize:(NSNumber*) size { - NSString* query = @"update message_history set messageType=? where messageid=?"; - DDLogVerbose(@"setting message type %@", messageid); - [self.db executeNonQuery:query andArguments:@[messageType, messageid]]; + if(historyId == nil) + return; + NSString* query = @"UPDATE message_history SET messageType=?, filetransferMimeType=?, filetransferSize=? WHERE message_history_id=?;"; + DDLogVerbose(@"setting message type 'kMessageTypeFiletransfer', mime type '%@' and size %@ for history id %@", mimeType, size, historyId); + [self.db executeNonQuery:query andArguments:@[kMessageTypeFiletransfer, mimeType, size, historyId]]; } --(void) setMessageId:(NSString*) messageid previewText:(NSString *) text andPreviewImage:(NSString *) image +-(void) setMessageHistoryId:(NSNumber*) historyId messageType:(NSString*) messageType +{ + if(historyId == nil) + return; + NSString* query = @"UPDATE message_history SET messageType=? WHERE message_history_id=?;"; + DDLogVerbose(@"setting message type '%@' for history id %@", messageType, historyId); + [self.db executeNonQuery:query andArguments:@[messageType, historyId]]; +} + +-(void) setMessageId:(NSString*) messageid previewText:(NSString*) text andPreviewImage:(NSString*) image { if(!messageid) return; - NSString* query = @"update message_history set previewText=?, previewImage=? where messageid=?"; + NSString* query = @"UPDATE message_history SET previewText=?, previewImage=? WHERE messageid=?;"; DDLogVerbose(@"setting previews type %@", messageid); [self.db executeNonQuery:query andArguments:@[text?text:@"", image?image:@"", messageid]]; } --(void) setMessageId:(NSString*) messageid stanzaId:(NSString *) stanzaId +-(void) setMessageId:(NSString*) messageid stanzaId:(NSString*) stanzaId { - NSString* query = @"update message_history set stanzaid=? where messageid=?"; + NSString* query = @"UPDATE message_history SET stanzaid=? WHERE messageid=?;"; DDLogVerbose(@"setting message stanzaid %@", query); [self.db executeNonQuery:query andArguments:@[stanzaId, messageid]]; } -(void) clearMessages:(NSString*) accountNo { - [self.db executeNonQuery:@"DELETE FROM message_history WHERE account_id=?;" andArguments:@[accountNo]]; + [self.db voidWriteTransaction:^{ + NSArray* messageHistoryIDs = [self.db executeScalarReader:@"SELECT message_history_id FROM message_history WHERE messageType=? AND account_id=?;" andArguments:@[kMessageTypeFiletransfer, accountNo]]; + for(NSNumber* historyId in messageHistoryIDs) + [MLFiletransfer deleteFileForMessage:[self messageForHistoryID:historyId]]; + [self.db executeNonQuery:@"DELETE FROM message_history WHERE account_id=?;" andArguments:@[accountNo]]; + + [self.db executeNonQuery:@"DELETE FROM activechats WHERE account_id=?;" andArguments:@[accountNo]]; + }]; } -(void) deleteMessageHistory:(NSNumber*) messageNo { - [self.db executeNonQuery:@"DELETE FROM message_history WHERE message_history_id=?;" andArguments:@[messageNo]]; + [self.db voidWriteTransaction:^{ + MLMessage* msg = [self messageForHistoryID:messageNo]; + if([msg.messageType isEqualToString:kMessageTypeFiletransfer]) + [MLFiletransfer deleteFileForMessage:msg]; + [self.db executeNonQuery:@"DELETE FROM message_history WHERE message_history_id=?;" andArguments:@[messageNo]]; + }]; +} + +-(void) updateMessageHistory:(NSNumber*) messageNo withText:(NSString*) newText +{ + [self.db executeNonQuery:@"UPDATE message_history SET message=? WHERE message_history_id=?;" andArguments:@[newText, messageNo]]; +} + +-(NSNumber*) getHistoryIDForMessageId:(NSString*) messageid from:(NSString*) from andAccount:(NSString*) accountNo +{ + return [self.db executeScalar:@"SELECT message_history_id FROM message_history WHERE messageid=? AND message_from=? AND account_id=?;" andArguments:@[messageid, from, accountNo]]; +} + +-(BOOL) checkLMCEligible:(NSNumber*) historyID from:(NSString*) from +{ + MLMessage* msg = [self messageForHistoryID:historyID]; + if(from == nil || msg == nil) + return NO; + NSNumber* numberOfMessagesComingAfterThis = [self.db executeScalar:@"SELECT COUNT(message_history_id) FROM message_history WHERE message_history_id>? AND message_from=? AND message_to=? AND account_id=?;" andArguments:@[historyID, msg.from, msg.to, msg.accountId]]; + //only allow LMC for the 3 newest messages of this contact (or of us) + if( + numberOfMessagesComingAfterThis.intValue < 3 + && [msg.messageType isEqualToString:kMessageTypeText] + && [msg.from isEqualToString:from] + //not needed according to holger + //&& ([NSDate date].timeIntervalSince1970 - msg.timestamp.timeIntervalSince1970) < 120 + ) + return YES; + return NO; } -(NSArray*) messageHistoryListDates:(NSString*) buddy forAccount: (NSString*) accountNo @@ -1204,37 +1207,20 @@ -(NSArray*) messageHistoryListDates:(NSString*) buddy forAccount: (NSString*) ac { DDLogError(@"message history buddy date list is empty or failed to read"); - return nil; + return [[NSArray alloc] init]; } - } else return nil; + } else return [[NSArray alloc] init]; } -(NSArray*) messageHistoryDateForContact:(NSString*) contact forAccount:(NSString*) accountNo forDate:(NSString*) date { - NSString* query = @"select af, message_from, message_to, message, thetime, sent, message_history_id from (select ifnull(actual_from, message_from) as af, message_from, message_to, message, sent, timestamp as thetime, message_history_id, previewImage, previewText from message_history where account_id=? and (message_from=? or message_to=?) and date(timestamp)=? order by message_history_id desc) order by message_history_id asc"; - NSArray* params = @[accountNo, contact, contact, date]; - - DDLogVerbose(@"%@", query); - NSArray* results = [self.db executeReader:query andArguments:params]; - - NSMutableArray *toReturn =[[NSMutableArray alloc] initWithCapacity:results.count]; - [results enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - NSDictionary *dic = (NSDictionary *) obj; - [toReturn addObject:[MLMessage messageFromDictionary:dic withDateFormatter:dbFormatter]]; + return [self.db idWriteTransaction:^{ + NSString* query = @"SELECT message_history_id FROM message_history WHERE account_id=? AND (message_from=? OR message_to=?) AND DATE(timestamp)=? ORDER BY message_history_id ASC;"; + NSArray* params = @[accountNo, contact, contact, date]; + DDLogVerbose(@"%@", query); + NSArray* results = [self.db executeScalarReader:query andArguments:params]; + return [self messagesForHistoryIDs:results]; }]; - - if(toReturn!=nil) - { - - DDLogVerbose(@"count: %lu", (unsigned long)[toReturn count]); - - return toReturn; //[toReturn autorelease]; - } - else - { - DDLogError(@"message history is empty or failed to read"); - return nil; - } } -(NSArray*) allMessagesForContact:(NSString*) buddy forAccount:(NSString*) accountNo @@ -1255,50 +1241,23 @@ -(NSArray*) allMessagesForContact:(NSString*) buddy forAccount:(NSString*) accou else { DDLogError(@"message history is empty or failed to read"); - return nil; + return [[NSArray alloc] init]; } } -(BOOL) messageHistoryClean:(NSString*) buddy forAccount:(NSString*) accountNo { - //returns a buddy's message history - - NSString* query = @"delete from message_history where account_id=? and (message_from=? or message_to=?) "; - NSArray* params = @[accountNo, buddy, buddy]; - //DDLogVerbose(query); - if( [self.db executeNonQuery:query andArguments:params]) - - { - DDLogVerbose(@" cleaned messages for %@", buddy ); - return YES; - } - else - { - DDLogError(@"message history failed to clean"); - return NO; - } -} - - --(BOOL) messageHistoryCleanAll -{ - //cleans a buddy's message history - NSString* query = @"delete from message_history "; - if( [self.db executeNonQuery:query andArguments:@[]]) - { - DDLogVerbose(@" cleaned messages " ); - return YES; - } - else - { - DDLogError(@"message history failed to clean all"); - return NO; - } - + return [self.db boolWriteTransaction:^{ + NSArray* messageHistoryIDs = [self.db executeScalarReader:@"SELECT message_history_id FROM message_history WHERE messageType=? AND account_id=? AND (message_from=? OR message_to=?);" andArguments:@[kMessageTypeFiletransfer, accountNo, buddy, buddy]]; + for(NSNumber* historyId in messageHistoryIDs) + [MLFiletransfer deleteFileForMessage:[self messageForHistoryID:historyId]]; + return [self.db executeNonQuery:@"DELETE FROM message_history WHERE account_id=? AND (message_from=? OR message_to=?);" andArguments:@[accountNo, buddy, buddy]]; + }]; } -(NSMutableArray *) messageHistoryContacts:(NSString*) accountNo { + NSMutableArray* toReturn = [[NSMutableArray alloc] init]; //returns a list of buddy's with message history NSString* accountJid = [self jidOfAccount:accountNo]; if(accountJid) @@ -1308,13 +1267,10 @@ -(NSMutableArray *) messageHistoryContacts:(NSString*) accountNo NSArray* params = @[accountNo, accountNo, accountJid]; //DDLogVerbose(query); - NSMutableArray* toReturn = [[NSMutableArray alloc] init]; for(NSDictionary* dic in [self.db executeReader:query andArguments:params]) [toReturn addObject:[self contactForUsername:dic[@"buddy_name"] forAccount:dic[@"account_id"]]]; - return toReturn; } - else - return nil; + return toReturn; } //message history @@ -1339,52 +1295,36 @@ -(NSMutableArray*) messagesForContact:(NSString*) buddy forAccount:(NSString*) a //message history -(NSMutableArray*) messagesForContact:(NSString*) buddy forAccount:(NSString*) accountNo beforeMsgHistoryID:(NSNumber*) msgHistoryID { - if(!accountNo || !buddy || !msgHistoryID) { + if(!accountNo || !buddy || msgHistoryID == nil) return nil; - }; - NSString* query = @"select af, message_from, message_to, account_id, message, thetime, message_history_id, sent, messageid, messageType, received, displayed, displayMarkerWanted, encrypted, previewImage, previewText, unread, errorType, errorReason, stanzaid from (select ifnull(actual_from, message_from) as af, message_from, message_to, account_id, message, received, displayed, displayMarkerWanted, encrypted, timestamp as thetime, message_history_id, sent,messageid, messageType, previewImage, previewText, unread, errorType, errorReason, stanzaid from message_history where account_id=? and (message_from=? or message_to=?) and message_history_id default to 0 - if(datetoReturn == nil) - datetoReturn = [[NSDate date] initWithTimeIntervalSince1970:0]; - - return datetoReturn; -} - -(NSString*) lastStanzaIdForAccount:(NSString*) accountNo { return [self.db executeScalar:@"SELECT lastStanzaId FROM account WHERE account_id=?;" andArguments:@[accountNo]]; @@ -1535,28 +1447,6 @@ -(void) setLastStanzaId:(NSString*) lastStanzaId forAccount:(NSString*) accountN [self.db executeNonQuery:@"UPDATE account SET lastStanzaId=? WHERE account_id=?;" andArguments:@[lastStanzaId, accountNo]]; } --(NSDate*) lastMessageDateAccount:(NSString*) accountNo -{ - NSString* query = @"select timestamp from message_history where account_id=? order by timestamp desc limit 1"; - - NSObject* result = [self.db executeScalar:query andArguments:@[accountNo]]; - - NSDateFormatter* dateFromatter = [[NSDateFormatter alloc] init]; - NSLocale* enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - - [dateFromatter setLocale:enUSPOSIXLocale]; - [dateFromatter setDateFormat:@"yyyy'-'MM'-'dd HH':'mm':'ss"]; - [dateFromatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - - NSDate* datetoReturn = [dateFromatter dateFromString:(NSString *)result]; - return datetoReturn; -} - --(NSString*)lastMessageActualFromByHistoryId:(NSNumber*) lastMsgHistoryId -{ - return [self.db executeScalar:@"select actual_from from message_history where message_history_id=? order by timestamp desc limit 1" andArguments:@[lastMsgHistoryId]]; -} - #pragma mark active chats -(NSMutableArray*) activeContactsWithPinned:(BOOL) pinned @@ -1599,40 +1489,36 @@ -(void) removeActiveBuddy:(NSString*) buddyname forAccount:(NSString*) accountNo }]; } --(BOOL) addActiveBuddies:(NSString*) buddyname forAccount:(NSString*) accountNo +-(void) addActiveBuddies:(NSString*) buddyname forAccount:(NSString*) accountNo { if(!buddyname) - return NO; + return; - return [self.db boolWriteTransaction:^{ - // Check that we do not add a chat a second time to activechats - if([self isActiveBuddy:buddyname forAccount:accountNo]) - return YES; - + [self.db voidWriteTransaction:^{ NSString* accountJid = [self jidOfAccount:accountNo]; if(!accountJid) - return NO; - + return; if([accountJid isEqualToString:buddyname]) { // Something is broken DDLogWarn(@"We should never try to create a chat with our own jid"); - return NO; + return; } else { - // insert - NSString* query3 = @"INSERT INTO activechats (buddy_name, account_id, lastMessageTime) VALUES(?, ?, current_timestamp);"; - BOOL result = [self.db executeNonQuery:query3 andArguments:@[buddyname, accountNo]]; - return result; + // insert or update + NSString* query = @"INSERT INTO activechats (buddy_name, account_id, lastMessageTime) VALUES(?, ?, current_timestamp) ON CONFLICT(buddy_name, account_id) DO UPDATE SET lastMessageTime=current_timestamp;"; + [self.db executeNonQuery:query andArguments:@[buddyname, accountNo]]; + return; } }]; + return; } -(BOOL) isActiveBuddy:(NSString*) buddyname forAccount:(NSString*) accountNo { - NSString* query = @"select count(buddy_name) from activechats where account_id=? and buddy_name=? "; + NSString* query = @"SELECT COUNT(buddy_name) FROM activechats WHERE account_id=? AND buddy_name=?;"; NSNumber* count = (NSNumber*)[self.db executeScalar:query andArguments:@[accountNo, buddyname]]; if(count != nil) { @@ -1643,18 +1529,19 @@ -(BOOL) isActiveBuddy:(NSString*) buddyname forAccount:(NSString*) accountNo } } --(BOOL) updateActiveBuddy:(NSString*) buddyname setTime:(NSString *)timestamp forAccount:(NSString*) accountNo +-(BOOL) updateActiveBuddy:(NSString*) buddyname setTime:(NSString*) timestamp forAccount:(NSString*) accountNo { return [self.db boolWriteTransaction:^{ - NSString* query = @"select lastMessageTime from activechats where account_id=? and buddy_name=?"; + NSString* query = @"SELECT lastMessageTime FROM activechats WHERE account_id=? AND buddy_name=?;"; NSObject* result = [self.db executeScalar:query andArguments:@[accountNo, buddyname]]; NSString* lastTime = (NSString *) result; NSDate* lastDate = [dbFormatter dateFromString:lastTime]; NSDate* newDate = [dbFormatter dateFromString:timestamp]; - if(lastDate.timeIntervalSince1970 #import #import "HelperTools.h" +#import "MLXMPPManager.h" #import "MLPubSub.h" #import "MLUDPLogger.h" +#import "XMPPStanza.h" @import UserNotifications; @@ -24,6 +26,18 @@ void logException(NSException* exception) usleep(1000000); } ++(NSString*) extractXMPPError:(XMPPStanza*) stanza withDescription:(NSString*) description +{ + if(description == nil || [description isEqualToString:@""]) + description = @"XMPP Error"; + NSString* errorReason = [stanza findFirst:@"{urn:ietf:params:xml:ns:xmpp-stanzas}!text$"]; + NSString* errorText = [stanza findFirst:@"{urn:ietf:params:xml:ns:xmpp-stanzas}text#"]; + NSString* message = [NSString stringWithFormat:@"%@: %@", description, errorReason]; + if(errorText && ![errorText isEqualToString:@""]) + message = [NSString stringWithFormat:@"%@: %@ (%@)", description, errorReason, errorText]; + return message; +} + +(void) configureFileProtectionFor:(NSString*) file { #if TARGET_OS_IPHONE @@ -35,7 +49,7 @@ +(void) configureFileProtectionFor:(NSString*) file [fileManager setAttributes:@{NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication} ofItemAtPath:file error:&error]; if(error) { - DDLogError(@"Error configuring database file protection level for: %@", file); + DDLogError(@"Error configuring file protection level for: %@", file); @throw [NSException exceptionWithName:@"NSError" reason:[NSString stringWithFormat:@"%@", error] userInfo:@{@"error": error}]; } else @@ -92,12 +106,15 @@ +(BOOL) isInBackground if([HelperTools isAppExtension]) inBackground = YES; else + inBackground = [[MLXMPPManager sharedInstance] isBackgrounded]; + /* { [HelperTools dispatchSyncReentrant:^{ if([UIApplication sharedApplication].applicationState==UIApplicationStateBackground) inBackground = YES; } onQueue:dispatch_get_main_queue()]; } + */ return inBackground; } @@ -376,7 +393,7 @@ +(monal_void_block_t) startTimer:(double) timeout withHandler:(monal_void_block_ dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); if(timeout<=0.001) { - DDLogWarn(@"Timer timeout is smaller than 0.001, dispatching handler directly."); + //DDLogVerbose(@"Timer timeout is smaller than 0.001, dispatching handler directly."); if(handler) dispatch_async(q_background, ^{ handler(); @@ -501,14 +518,14 @@ + (NSString *)hexadecimalString:(NSData*) data } -+ (NSData *)dataWithHexString:(NSString *)hex ++(NSData*) dataWithHexString:(NSString*) hex { char buf[3]; buf[2] = '\0'; if( [hex length] % 2 !=00) { - NSLog(@"Hex strings should have an even number of digits"); - return nil; + DDLogError(@"Hex strings should have an even number of digits"); + return [[NSData alloc] init]; } unsigned char *bytes = malloc([hex length]/2); unsigned char *bp = bytes; @@ -518,8 +535,9 @@ + (NSData *)dataWithHexString:(NSString *)hex char *b2 = NULL; *bp++ = strtol(buf, &b2, 16); if(b2 != buf + 2) { - NSLog(@"String should be all hex digits");; - return nil; + DDLogError(@"String should be all hex digits"); + free(bytes); + return [[NSData alloc] init]; } } diff --git a/Monal/Classes/IPC.h b/Monal/Classes/IPC.h index 4cbdb90965..3a90644f85 100644 --- a/Monal/Classes/IPC.h +++ b/Monal/Classes/IPC.h @@ -22,6 +22,9 @@ typedef void (^IPC_response_handler_t)(NSDictionary*); +(void) terminate; -(void) sendMessage:(NSString*) name withData:(NSData* _Nullable) data to:(NSString*) destination; -(void) sendMessage:(NSString*) name withData:(NSData* _Nullable) data to:(NSString*) destination withResponseHandler:(IPC_response_handler_t _Nullable) responseHandler; +-(void) sendBroadcastMessage:(NSString*) name withData:(NSData* _Nullable) data; +-(void) sendBroadcastMessage:(NSString*) name withData:(NSData* _Nullable) data withResponseHandler:(IPC_response_handler_t _Nullable) responseHandler; + -(void) respondToMessage:(NSDictionary*) message withData:(NSData* _Nullable) data; @end diff --git a/Monal/Classes/IPC.m b/Monal/Classes/IPC.m index f677debca7..8359fa7c69 100755 --- a/Monal/Classes/IPC.m +++ b/Monal/Classes/IPC.m @@ -11,6 +11,9 @@ #include #import "IPC.h" #import "MLSQLite.h" +#import "HelperTools.h" + +#define MSG_TIMEOUT 2.0 @interface IPC() { @@ -32,7 +35,7 @@ -(void) incomingDarwinNotification:(NSString*) name; //forward notifications to the IPC instance that is waiting (the instance running the server thread) void darwinNotificationCenterCallback(CFNotificationCenterRef center, void* observer, CFNotificationName name, const void* object, CFDictionaryRef userInfo) { - [(__bridge IPC*)observer incomingDarwinNotification: (__bridge NSString*)name]; + [(__bridge IPC*)observer incomingDarwinNotification:(__bridge NSString*)name]; } @implementation IPC @@ -81,6 +84,16 @@ -(void) sendMessage:(NSString*) name withData:(NSData* _Nullable) data to:(NSStr _responseHandlers[id] = responseHandler; } +-(void) sendBroadcastMessage:(NSString*) name withData:(NSData* _Nullable) data +{ + [self sendMessage:name withData:data to:@"*" withResponseHandler:nil]; +} + +-(void) sendBroadcastMessage:(NSString*) name withData:(NSData* _Nullable) data withResponseHandler:(IPC_response_handler_t _Nullable) responseHandler +{ + [self sendMessage:name withData:data to:@"*" withResponseHandler:responseHandler]; +} + -(void) respondToMessage:(NSDictionary*) message withData:(NSData* _Nullable) data { [self writeIpcMessage:message[@"name"] withData:data andResponseId:message[@"id"] to:message[@"source"]]; @@ -88,6 +101,8 @@ -(void) respondToMessage:(NSDictionary*) message withData:(NSData* _Nullable) da -(id) initWithProcessName:(NSString*) processName { + self = [super init]; + NSFileManager* fileManager = [NSFileManager defaultManager]; NSURL* containerUrl = [fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroup]; _dbFile = [[containerUrl path] stringByAppendingPathComponent:@"ipc.sqlite"]; @@ -143,12 +158,13 @@ -(void) serverThreadMain DDLogInfo(@"Now running IPC server for '%@'", _processName); //register darwin notification handler for "im.monal.ipc.wakeup:" which is used to wake up readNextMessage using the NSCondition CFNotificationCenterAddObserver(_darwinNotificationCenterRef, (__bridge void*) self, &darwinNotificationCenterCallback, (__bridge CFNotificationName)[NSString stringWithFormat:@"im.monal.ipc.wakeup:%@", _processName], NULL, 0); + CFNotificationCenterAddObserver(_darwinNotificationCenterRef, (__bridge void*) self, &darwinNotificationCenterCallback, (__bridge CFNotificationName)@"im.monal.ipc.wakeup:*", NULL, 0); while(![[NSThread currentThread] isCancelled]) { NSDictionary* message = [self readNextMessage]; //this will be blocking if(!message) continue; - DDLogVerbose(@"Got IPC message: %@", message); + DDLogDebug(@"Got IPC message: %@", message); //use a dedicated serial queue for every IPC receiver to maintain IPC message ordering while not blocking other receivers or this serverThread) NSArray* parts = [message[@"name"] componentsSeparatedByString:@"."]; @@ -166,10 +182,16 @@ -(void) serverThreadMain if(_responseHandlers[message[@"response_to"]]) { IPC_response_handler_t responseHandler = (IPC_response_handler_t)_responseHandlers[message[@"response_to"]]; - [_responseHandlers removeObjectForKey:message[@"response_to"]]; //responses can only be sent (and handled) once - dispatch_async(_ipcQueues[queueName], ^{ - responseHandler(message); - }); + if(responseHandler) + { + //responses handlers are only valid for the maximum RTT of messages (+ some safety margin) + [HelperTools startTimer:(MSG_TIMEOUT*2 + 1) withHandler:^{ + [_responseHandlers removeObjectForKey:message[@"response_to"]]; + }]; + dispatch_async(_ipcQueues[queueName], ^{ + responseHandler(message); + }); + } } } else //publish all non-responses (using the message name as object allows for filtering by ipc message name) @@ -179,13 +201,14 @@ -(void) serverThreadMain } //unregister darwin notification handler CFNotificationCenterRemoveObserver(_darwinNotificationCenterRef, (__bridge void*) self, (__bridge CFNotificationName)[NSString stringWithFormat:@"im.monal.ipc.wakeup:%@", _processName], NULL); + CFNotificationCenterRemoveObserver(_darwinNotificationCenterRef, (__bridge void*) self, (__bridge CFNotificationName)@"im.monal.ipc.wakeup:*", NULL); DDLogInfo(@"IPC server for '%@' now terminated", _processName); } -(void) incomingDarwinNotification:(NSString*) name { - DDLogVerbose(@"Got incoming darwin notification: %@", name); - [_serverThreadCondition signal]; + DDLogDebug(@"Got incoming darwin notification: %@", name); + [_serverThreadCondition signal]; //wake up server thread to process new messages } -(NSDictionary*) readNextMessage @@ -220,11 +243,12 @@ -(NSDictionary*) readIpcMessageFor:(NSString*) destination [self.db executeNonQuery:@"DELETE FROM ipc WHERE timeout +@interface MLBackgroundSettings : UITableViewController @end diff --git a/Monal/Classes/MLBackgroundSettings.m b/Monal/Classes/MLBackgroundSettings.m index bb8e1b49fd..b7900d269a 100644 --- a/Monal/Classes/MLBackgroundSettings.m +++ b/Monal/Classes/MLBackgroundSettings.m @@ -64,44 +64,33 @@ -(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteg } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - - UITableViewCell* toreturn; - switch (indexPath.row) { - case 0: { - MLSettingCell* cell=[[MLSettingCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"AccountCell"]; - cell.parent= self; - cell.switchEnabled=YES; - cell.defaultKey=@"ChatBackgrounds"; - cell.textLabel.text=NSLocalizedString(@"Chat Backgrounds",@""); - toreturn=cell; - break; - } - - case 1: { - UITableViewCell* cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SelectCell"]; - cell.textLabel.text=NSLocalizedString(@"Select Background",@""); - cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; - toreturn=cell; - break; - } - - case 2: { - UITableViewCell* cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SelectCell"]; + if(indexPath.row == 0) + { + MLSettingCell* cell = [[MLSettingCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"AccountCell"]; + cell.parent = self; + cell.switchEnabled = YES; + cell.defaultKey = @"ChatBackgrounds"; + cell.textLabel.text = NSLocalizedString(@"Chat Backgrounds",@""); + return cell; + } + else if(indexPath.row == 1) + { + UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SelectCell"]; + cell.textLabel.text = NSLocalizedString(@"Select Background",@""); + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + return cell; + } + else + { + UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SelectCell"]; #if TARGET_OS_MACCATALYST - cell.textLabel.text=NSLocalizedString(@"Select File",@""); + cell.textLabel.text = NSLocalizedString(@"Select File",@""); #else - cell.textLabel.text=NSLocalizedString(@"Select From Photos",@""); + cell.textLabel.text = NSLocalizedString(@"Select From Photos",@""); #endif - cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; - toreturn=cell; - break; - } - - default: - break; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + return cell; } - - return toreturn; } @@ -118,10 +107,7 @@ -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath * [self showPhotos]; break; } - default: break; - } - } -(void) showPhotos @@ -129,13 +115,13 @@ -(void) showPhotos #if TARGET_OS_MACCATALYST //UTI @"public.data" for everything NSString *images = (NSString *)kUTTypeImage; - UIDocumentPickerViewController *imagePicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[images] inMode:UIDocumentPickerModeImport]; + UIDocumentPickerViewController *imagePicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[images] inMode:UIDocumentPickerModeImport]; imagePicker.allowsMultipleSelection=NO; imagePicker.delegate=self; [self presentViewController:imagePicker animated:YES completion:nil]; #else UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; - imagePicker.delegate =self; + imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if(granted) diff --git a/Monal/Classes/MLBaseCell.h b/Monal/Classes/MLBaseCell.h index d8e83e633c..2584058401 100644 --- a/Monal/Classes/MLBaseCell.h +++ b/Monal/Classes/MLBaseCell.h @@ -20,6 +20,8 @@ @interface MLBaseCell : UITableViewCell +-(id) init; + @property (nonatomic, assign) BOOL outBound; @property (nonatomic, assign) BOOL MUC; diff --git a/Monal/Classes/MLBaseCell.m b/Monal/Classes/MLBaseCell.m index 22547e8681..ef031ad939 100644 --- a/Monal/Classes/MLBaseCell.m +++ b/Monal/Classes/MLBaseCell.m @@ -11,6 +11,16 @@ @implementation MLBaseCell +-(id) init +{ + self = [super init]; + if(@available(iOS 13.0, *)) + [self.retry setImage:[UIImage systemImageNamed:@"info.circle"] forState:UIControlStateNormal]; + else + [self.retry setImage:[UIImage imageNamed:@"724-info"] forState:UIControlStateNormal]; + return self; +} + - (void)awakeFromNib { [super awakeFromNib]; @@ -32,9 +42,12 @@ - (void)setSelected:(BOOL)selected animated:(BOOL)animated { -(void) updateCellWithNewSender:(BOOL) newSender { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" if([self.parent respondsToSelector:@selector(retry:)]) { [self.retry addTarget:self.parent action:@selector(retry:) forControlEvents:UIControlEventTouchUpInside]; } +#pragma clang diagnostic pop self.retry.tag= [self.messageHistoryId integerValue]; diff --git a/Monal/Classes/MLChatImageCell.h b/Monal/Classes/MLChatImageCell.h index 988e59aeb3..74d97e5979 100644 --- a/Monal/Classes/MLChatImageCell.h +++ b/Monal/Classes/MLChatImageCell.h @@ -8,15 +8,17 @@ #import "MLBaseCell.h" +@class MLMessage; @interface MLChatImageCell : MLBaseCell @property (nonatomic, weak) IBOutlet UIImageView *thumbnailImage; @property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *imageHeight; -@property (nonatomic, assign) BOOL loading; +@property (nonatomic, assign) BOOL loading; +@property (nonatomic) MLMessage* msg; --(void) loadImageWithCompletion:(void (^)(void))completion; +-(void) loadImage; @end diff --git a/Monal/Classes/MLChatImageCell.m b/Monal/Classes/MLChatImageCell.m index 7fd854d133..9f7c4090dd 100644 --- a/Monal/Classes/MLChatImageCell.m +++ b/Monal/Classes/MLChatImageCell.m @@ -8,70 +8,67 @@ #import "MLChatImageCell.h" #import "MLImageManager.h" -@import QuartzCore; +#import "MLFiletransfer.h" +#import "MLMessage.h" +@import QuartzCore; +@import UIKit; @implementation MLChatImageCell -- (void)awakeFromNib { +-(void)awakeFromNib +{ [super awakeFromNib]; + // Initialization code - self.thumbnailImage.layer.cornerRadius=15.0f; - self.thumbnailImage.layer.masksToBounds=YES; + self.thumbnailImage.layer.cornerRadius = 15.0f; + self.thumbnailImage.layer.masksToBounds = YES; } --(void) loadImageWithCompletion:(void (^)(void))completion +-(void) loadImage { - if(self.link && self.thumbnailImage.image==nil && !self.loading) + if(self.msg.messageText && self.thumbnailImage.image == nil && !self.loading) { [self.spinner startAnimating]; self.loading=YES; - NSString *currentLink = self.link; - [[MLImageManager sharedInstance] imageForAttachmentLink:self.link withCompletion:^(NSData * _Nullable data) { - dispatch_async(dispatch_get_main_queue(), ^{ - if([currentLink isEqualToString:self.link]){ - if(!data) { - self.thumbnailImage.image=nil; - } - else if(!self.thumbnailImage.image) { - UIImage *image= [UIImage imageWithData:data]; - [self.thumbnailImage setImage:image]; - if (image.size.height>image.size.width) { - self.imageHeight.constant = 360; - } - } - self.loading=NO; - [self.spinner stopAnimating]; - if(completion) completion(); - } - - }); - }]; - - } - else { - + NSString* currentLink = self.msg.messageText; + NSDictionary* info = [MLFiletransfer getFileInfoForMessage:self.msg]; + if(info && [info[@"mimeType"] hasPrefix:@"image/"]) + { + if([currentLink isEqualToString:self.msg.messageText]) + { + UIImage* image = [[UIImage alloc] initWithContentsOfFile:info[@"cacheFile"]]; + [self.thumbnailImage setImage:image]; + self.link = currentLink; + if(image && image.size.height > image.size.width) + self.imageHeight.constant = 360; + self.loading = NO; + [self.spinner stopAnimating]; + } + } } } -- (void)setSelected:(BOOL)selected animated:(BOOL)animated { +-(void) setSelected:(BOOL) selected animated:(BOOL) animated +{ [super setSelected:selected animated:animated]; - // Configure the view for the selected state } --(BOOL) canPerformAction:(SEL)action withSender:(id)sender +-(BOOL) canPerformAction:(SEL) action withSender:(id) sender { - return (action == @selector(copy:)) ; + return (action == @selector(copy:)); } --(void) copy:(id)sender { - UIPasteboard *pboard = [UIPasteboard generalPasteboard]; +-(void) copy:(id) sender +{ + UIPasteboard* pboard = [UIPasteboard generalPasteboard]; pboard.image = self.thumbnailImage.image; } --(void)prepareForReuse{ +-(void) prepareForReuse +{ [super prepareForReuse]; - self.imageHeight.constant=200; + self.imageHeight.constant = 200; [self.spinner stopAnimating]; } diff --git a/Monal/Classes/MLConstants.h b/Monal/Classes/MLConstants.h index d0ffffbad4..ed6decd86b 100644 --- a/Monal/Classes/MLConstants.h +++ b/Monal/Classes/MLConstants.h @@ -18,6 +18,10 @@ static const DDLogLevel ddLogLevel = DDLogLevelVerbose; //configure app group constants #define kAppGroup @"group.monal" +//this is in seconds +#define SHORT_PING 4.0 +#define LONG_PING 16.0 + @class MLContact; //some typedefs used throughout the project @@ -35,37 +39,44 @@ typedef enum NotificationPrivacySettingOption { //some useful macros #define weakify(var) __weak __typeof__(var) AHKWeak_##var = var #define strongify(var) _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wshadow\"") __strong __typeof__(var) var = AHKWeak_##var; _Pragma("clang diagnostic pop") -#define nilWrapper(var) (var ? var : [NSNull null]) +#define nilWrapper(var) (var ? var : [NSNull null]) +#define nilExtractor(var) (var == [NSNull null] ? nil : var) //some xmpp related constants #define kRegServer @"yax.im" +#define kMessageDeletedBody @"eu.siacs.conversations.message_deleted" #define kXMLNS @"xmlns" #define kId @"id" #define kJid @"jid" +#define kMessageId @"kMessageId" #define kRegisterNameSpace @"jabber:iq:register" #define kDataNameSpace @"jabber:x:data" #define kBobNameSpace @"urn:xmpp:bob" -#define kStanzasNameSpace @"urn:ietf:params:xml:ns:xmpp-stanzas" - //all other constants needed #define kMonalNewMessageNotice @"kMLNewMessageNotice" +#define kMonalDeletedMessageNotice @"kMonalDeletedMessageNotice" #define kMonalDisplayedMessageNotice @"kMonalDisplayedMessageNotice" #define kMonalHistoryMessagesNotice @"kMonalHistoryMessagesNotice" #define kMLMessageSentToContact @"kMLMessageSentToContact" #define kMonalSentMessageNotice @"kMLSentMessageNotice" +#define kMonalMessageFiletransferUpdateNotice @"kMonalMessageFiletransferUpdateNotice" #define kMonalLastInteractionUpdatedNotice @"kMonalLastInteractionUpdatedNotice" #define kMonalMessageReceivedNotice @"kMonalMessageReceivedNotice" #define kMonalMessageDisplayedNotice @"kMonalMessageDisplayedNotice" #define kMonalMessageErrorNotice @"kMonalMessageErrorNotice" #define kMonalReceivedMucInviteNotice @"kMonalReceivedMucInviteNotice" +#define kXMPPError @"kXMPPError" +#define kScheduleBackgroundFetchingTask @"kScheduleBackgroundFetchingTask" +#define kMonalUpdateUnread @"kMonalUpdateUnread" #define kMLHasConnectedNotice @"kMLHasConnectedNotice" #define kMonalFinishedCatchup @"kMonalFinishedCatchup" #define kMonalFinishedOmemoBundleFetch @"kMonalFinishedOmemoBundleFetch" +#define kMonalUpdateBundleFetchStatus @"kMonalUpdateBundleFetchStatus" #define kMonalIdle @"kMonalIdle" #define kMonalPresentChat @"kMonalPresentChat" @@ -85,6 +96,7 @@ typedef enum NotificationPrivacySettingOption { // max count of char's in a single message (both: sending and receiving) #define kMonalChatMaxAllowedTextLen 2048 + #if TARGET_OS_MACCATALYST #define kMonalChatFetchedMsgCnt 75 #else diff --git a/Monal/Classes/MLContact.h b/Monal/Classes/MLContact.h index b40d9e297b..bb91f996dd 100644 --- a/Monal/Classes/MLContact.h +++ b/Monal/Classes/MLContact.h @@ -57,8 +57,6 @@ FOUNDATION_EXPORT NSString* const kAskSubscribe; */ @property (nonatomic, assign) NSInteger unreadCount; -@property (nonatomic, assign) BOOL isOnline; - @property (nonatomic, assign) BOOL isPinned; @property (nonatomic, assign) BOOL isActiveChat; diff --git a/Monal/Classes/MLContactCell.h b/Monal/Classes/MLContactCell.h index fb4f364861..8a4b1276f9 100644 --- a/Monal/Classes/MLContactCell.h +++ b/Monal/Classes/MLContactCell.h @@ -7,15 +7,8 @@ // #import "MLAttributedLabel.h" -typedef enum { - kStatusOnline=1, - kStatusOffline, - kStatusAway -} statusType; - @interface MLContactCell : UITableViewCell -@property (nonatomic, assign) NSInteger status; @property (nonatomic, assign) NSInteger count; @property (nonatomic, assign) NSInteger accountNo; @property (nonatomic, strong) NSString *username; @@ -25,15 +18,12 @@ typedef enum { @property (nonatomic, weak) IBOutlet UILabel *time; @property (nonatomic, weak) IBOutlet MLAttributedLabel *statusText; -@property (nonatomic, weak) IBOutlet UIImageView *statusOrb; @property (nonatomic, weak) IBOutlet UIImageView *userImage; @property (nonatomic, weak) IBOutlet UIButton *badge; @property (nonatomic, weak) IBOutlet UIImageView *muteBadge; @property (nonatomic, assign) BOOL isPinned; --(void) setOrb; - -(void) showStatusText:(NSString *) text; -(void) showStatusTextItalic:(NSString *) text withItalicRange:(NSRange)italicRange; -(void) setStatusTextLayout:(NSString *) text; diff --git a/Monal/Classes/MLContactCell.m b/Monal/Classes/MLContactCell.m index 52f1318782..e79a7bde05 100644 --- a/Monal/Classes/MLContactCell.m +++ b/Monal/Classes/MLContactCell.m @@ -21,33 +21,6 @@ -(void) awakeFromNib [super awakeFromNib]; } --(void) setOrb -{ - switch (_status) { - case kStatusAway: - { - self.statusOrb.image=[UIImage imageNamed:@"away"]; - self.imageView.alpha=1.0f; - break; - } - case kStatusOnline: - { - self.statusOrb.image=[UIImage imageNamed:@"available"]; - self.imageView.alpha=1.0f; - break; - } - case kStatusOffline: - { - self.statusOrb.image=[UIImage imageNamed:@"offline"]; - self.imageView.alpha=0.5f; - break; - } - - default: - break; - } -} - -(void) showStatusText:(NSString *) text { if(![self.statusText.text isEqualToString:text]) { diff --git a/Monal/Classes/MLContactCell.xib b/Monal/Classes/MLContactCell.xib index b52d275a1a..2b2b68141d 100644 --- a/Monal/Classes/MLContactCell.xib +++ b/Monal/Classes/MLContactCell.xib @@ -100,7 +100,6 @@ - diff --git a/Monal/Classes/MLDisplaySettingsViewController.h b/Monal/Classes/MLDisplaySettingsViewController.h deleted file mode 100644 index 9b69334244..0000000000 --- a/Monal/Classes/MLDisplaySettingsViewController.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// SettingsViewController.h -// Monal -// -// Created by Anurodh Pokharel on 6/14/13. -// -// - -#import -#import "MLSettingCell.h" - -@interface MLDisplaySettingsViewController : UITableViewController -{ - UITextField* _currentField; -} - -@property (nonatomic, strong) UITableView* settingsTable; - --(IBAction)close:(id)sender ; - -@end diff --git a/Monal/Classes/MLDisplaySettingsViewController.m b/Monal/Classes/MLDisplaySettingsViewController.m deleted file mode 100644 index 4368133079..0000000000 --- a/Monal/Classes/MLDisplaySettingsViewController.m +++ /dev/null @@ -1,221 +0,0 @@ -// -// SettingsViewController.m -// Monal -// -// Created by Anurodh Pokharel on 6/14/13. -// -// - -#import "HelperTools.h" -#import "MLDisplaySettingsViewController.h" -#import "MLConstants.h" -#import "DataLayer.h" - - -@interface MLDisplaySettingsViewController () - -@end - -@implementation MLDisplaySettingsViewController - -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - // Do any additional setup after loading the view. - self.navigationItem.title = NSLocalizedString(@"Display Settings",@""); - - _settingsTable = self.tableView; - _settingsTable.delegate = self; - _settingsTable.dataSource = self; - _settingsTable.backgroundView = nil; -} - --(void) viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; -} - --(void) viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - [[HelperTools defaultsDB] synchronize]; - - //update logs if needed - if(! [[HelperTools defaultsDB] boolForKey:@"Logging"]) - { - [[DataLayer sharedInstance] messageHistoryCleanAll]; - } -} - -- (void)didReceiveMemoryWarning -{ - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -#pragma mark tableview datasource delegate --(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return 3; -} - - --(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - NSString* sectionTitle = [self tableView:tableView titleForHeaderInSection:section]; - return [HelperTools MLCustomViewHeaderWithTitle:sectionTitle]; -} - -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - switch (section) { - case 0: - { - return NSLocalizedString(@"Status", @""); - break; - } - case 1: - { - return NSLocalizedString(@"Presence", @""); - break; - } - case 2: - { - return NSLocalizedString(@"General", @""); - break; - } - default: - { - return nil; - break; - } - } -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - switch (section) { - case 0: - { - return 1; - break; - } - case 1: - { - return 1; - break; - } - case 2: - { - return 4; - break; - } - default: - { - return 0; - } - break; - } -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - - MLSettingCell* cell=[[MLSettingCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"AccountCell"]; - cell.parent= self; - - switch (indexPath.section) { - case 0: - { - switch(indexPath.row) - { - case 0: - { - cell.textInputField.placeholder=NSLocalizedString(@"Status Message", @""); - cell.textInputField.keyboardType=UIKeyboardTypeAlphabet; - cell.defaultKey=@"StatusMessage"; - cell.textEnabled=YES; - return cell; - break; - } - } - } - - case 1: - { - switch(indexPath.row) - { - case 0: - { - cell.textLabel.text=NSLocalizedString(@"Away", @""); - cell.defaultKey=@"Away"; - cell.switchEnabled=YES; - break; - } - } - return cell; - break; - } - - case 2: - { - switch(indexPath.row) - { - case 0: - { - cell.textLabel.text=NSLocalizedString(@"Message Preview", @""); - cell.defaultKey=@"MessagePreview"; - cell.switchEnabled=YES; - break; - } - case 1: - { - cell.textLabel.text=NSLocalizedString(@"Log Chats", @""); - cell.defaultKey=@"Logging"; - cell.switchEnabled=YES; - break; - } - case 2: - { - cell.textLabel.text = NSLocalizedString(@"Offline Contacts", @""); - cell.defaultKey = @"OfflineContact"; - cell.switchEnabled = YES; - break; - } - case 3: - { - cell.textLabel.text = NSLocalizedString(@"Sort By Status", @""); - cell.defaultKey = @"SortContacts"; - cell.switchEnabled = YES; - break; - } - } - return cell; - break; - } - default: - { - return nil; - break; - } - break; - } - - return nil; -} - --(IBAction)close:(id)sender -{ - [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; -} - -@end diff --git a/Monal/Classes/MLEditGroupViewController.h b/Monal/Classes/MLEditGroupViewController.h index 790555ffbe..a7c3c58da0 100644 --- a/Monal/Classes/MLEditGroupViewController.h +++ b/Monal/Classes/MLEditGroupViewController.h @@ -9,7 +9,7 @@ #import #import "MLConstants.h" -@interface MLEditGroupViewController : UITableViewController +@interface MLEditGroupViewController : UITableViewController { NSInteger _selectedRow; } diff --git a/Monal/Classes/MLEditGroupViewController.m b/Monal/Classes/MLEditGroupViewController.m index 5876ddf7a7..987c047024 100644 --- a/Monal/Classes/MLEditGroupViewController.m +++ b/Monal/Classes/MLEditGroupViewController.m @@ -14,6 +14,7 @@ #import "UIColor+Theme.h" #import "MLButtonCell.h" #import "MLXMPPManager.h" +#import "xmpp.h" #import "MLAccountPickerViewController.h" @interface MLEditGroupViewController () @@ -136,102 +137,81 @@ -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteg - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *toreturn ; - - switch (indexPath.section) + if(indexPath.section == 0) { - case 0: + UITableViewCell *accountCell =[tableView dequeueReusableCellWithIdentifier:@"AccountPickerCell"]; + accountCell.textLabel.text=[NSString stringWithFormat:NSLocalizedString(@"Using Account: %@",@""), [[MLXMPPManager sharedInstance] getAccountNameForConnectedRow:_selectedRow]]; + accountCell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; + return accountCell; + } + else if(indexPath.section == 1) + { + if(indexPath.row == 0) { - UITableViewCell *accountCell =[tableView dequeueReusableCellWithIdentifier:@"AccountPickerCell"]; - accountCell.textLabel.text=[NSString stringWithFormat:NSLocalizedString(@"Using Account: %@",@""), [[MLXMPPManager sharedInstance] getAccountNameForConnectedRow:_selectedRow]]; - accountCell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; - toreturn=accountCell; - break; - } + MLTextInputCell* thecell=(MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; + thecell.textInput.placeholder=NSLocalizedString(@"Room",@""); - case 1: + thecell.textInput.keyboardType = UIKeyboardTypeEmailAddress; + thecell.textInput.inputAccessoryView =self.keyboardToolbar; + thecell.textInput.delegate=self; + self.roomField= thecell.textInput; + self.roomField.text=[_groupData objectForKey:@"room"]; + return thecell; + } + else if(indexPath.row == 1) { - - switch (indexPath.row) - { - case 0:{ - MLTextInputCell* thecell=(MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; - thecell.textInput.placeholder=NSLocalizedString(@"Room",@""); - - thecell.textInput.keyboardType = UIKeyboardTypeEmailAddress; - thecell.textInput.inputAccessoryView =self.keyboardToolbar; - thecell.textInput.delegate=self; - self.roomField= thecell.textInput; - self.roomField.text=[_groupData objectForKey:@"room"]; - toreturn=thecell; - break; - } - case 1:{ - MLTextInputCell* thecell=(MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; - thecell.textInput.placeholder=NSLocalizedString(@"Nickname",@""); - thecell.textInput.inputAccessoryView =self.keyboardToolbar; - self.nickField= thecell.textInput; - thecell.textInput.delegate=self; - self.nickField.text=[_groupData objectForKey:@"nick"]; - toreturn=thecell; - break; - } - case 2:{ - MLTextInputCell* thecell=(MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; - thecell.textInput.placeholder=NSLocalizedString(@"Password",@""); - thecell.textInput.inputAccessoryView =self.keyboardToolbar; - thecell.textInput.secureTextEntry=YES; - - self.passField= thecell.textInput; - // self.roomField.text=[_groupData objectForKey:@"room"]; - toreturn=thecell; - break; - } - case 3:{ - MLSwitchCell* thecell=(MLSwitchCell *)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; - - thecell.cellLabel.text=NSLocalizedString(@"Favorite",@""); - thecell.textInputField.hidden=YES; - self.favSwitch= thecell.toggleSwitch; - [self.favSwitch addTarget:self action:@selector(toggleFav) forControlEvents:UIControlEventTouchUpInside]; - if(self.groupData) self.favSwitch.on=YES; - toreturn=thecell; - break; - } - case 4:{ - MLSwitchCell* thecell=(MLSwitchCell *)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; - - thecell.cellLabel.text=NSLocalizedString(@"Auto Join",@""); - thecell.textInputField.hidden=YES; - self.autoSwitch= thecell.toggleSwitch; - [self.autoSwitch addTarget:self action:@selector(toggleJoin) forControlEvents:UIControlEventTouchUpInside]; - NSNumber *on=[_groupData objectForKey:@"autojoin"]; - - if(on.intValue==1) - { - self.autoSwitch.on=YES; - } - - toreturn=thecell; - break; - } - - } - - break; + MLTextInputCell* thecell=(MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; + thecell.textInput.placeholder=NSLocalizedString(@"Nickname",@""); + thecell.textInput.inputAccessoryView =self.keyboardToolbar; + self.nickField= thecell.textInput; + thecell.textInput.delegate=self; + self.nickField.text=[_groupData objectForKey:@"nick"]; + return thecell; } + else if(indexPath.row == 2) + { + MLTextInputCell* thecell=(MLTextInputCell *)[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; + thecell.textInput.placeholder=NSLocalizedString(@"Password",@""); + thecell.textInput.inputAccessoryView =self.keyboardToolbar; + thecell.textInput.secureTextEntry=YES; - case 2: + self.passField= thecell.textInput; + // self.roomField.text=[_groupData objectForKey:@"room"]; + return thecell; + } + else if(indexPath.row == 3) { - //save button - UITableViewCell *buttonCell =[tableView dequeueReusableCellWithIdentifier:@"addButton"]; - toreturn= buttonCell; - break; + MLSwitchCell* thecell=(MLSwitchCell *)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; + + thecell.cellLabel.text=NSLocalizedString(@"Favorite",@""); + thecell.textInputField.hidden=YES; + self.favSwitch= thecell.toggleSwitch; + [self.favSwitch addTarget:self action:@selector(toggleFav) forControlEvents:UIControlEventTouchUpInside]; + if(self.groupData) self.favSwitch.on=YES; + return thecell; } + else + { + MLSwitchCell* thecell=(MLSwitchCell *)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; + + thecell.cellLabel.text=NSLocalizedString(@"Auto Join",@""); + thecell.textInputField.hidden=YES; + self.autoSwitch= thecell.toggleSwitch; + [self.autoSwitch addTarget:self action:@selector(toggleJoin) forControlEvents:UIControlEventTouchUpInside]; + NSNumber *on=[_groupData objectForKey:@"autojoin"]; + if(on.intValue==1) + self.autoSwitch.on=YES; + + return thecell; + } + } + else + { + //save button + UITableViewCell *buttonCell =[tableView dequeueReusableCellWithIdentifier:@"addButton"]; + return buttonCell; } - - return toreturn; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath diff --git a/Monal/Classes/MLEncryptedPayload.m b/Monal/Classes/MLEncryptedPayload.m index a1f23c4d62..8d95b90e65 100644 --- a/Monal/Classes/MLEncryptedPayload.m +++ b/Monal/Classes/MLEncryptedPayload.m @@ -19,16 +19,24 @@ @implementation MLEncryptedPayload -(MLEncryptedPayload *) initWithBody:(NSData *) body key:(NSData *) key iv:(NSData *) iv authTag:(NSData *) authTag { - self=[super init]; - self.body=body; - self.key= key; - self.iv= iv; - self.authTag=authTag; + assert(body != nil); + assert(key != nil); + assert(iv != nil); + assert(authTag != nil); + + self = [super init]; + self.body = body; + self.key = key; + self.iv = iv; + self.authTag = authTag; return self; } -(MLEncryptedPayload *) initWithKey:(NSData *) key iv:(NSData *) iv { + assert(key != nil); + assert(iv != nil); + self = [super init]; self.body = nil; self.key = key; diff --git a/Monal/Classes/MLFiletransfer.h b/Monal/Classes/MLFiletransfer.h new file mode 100644 index 0000000000..5986474b4a --- /dev/null +++ b/Monal/Classes/MLFiletransfer.h @@ -0,0 +1,26 @@ +// +// MLFiletransfer.h +// monalxmpp +// +// Created by Thilo Molitor on 12.11.20. +// Copyright Β© 2020 Monal.im. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@class MLMessage; +@class xmpp; + +@interface MLFiletransfer : NSObject + ++(void) doStartupCleanup; ++(void) checkMimeTypeAndSizeForHistoryID:(NSNumber*) historyId; ++(void) downloadFileForHistoryID:(NSNumber*) historyId; ++(NSDictionary* _Nullable) getFileInfoForMessage:(MLMessage* _Nullable) msg; ++(void) deleteFileForMessage:(MLMessage* _Nullable) msg; ++(void) uploadFile:(NSURL*) fileUrl onAccount:(xmpp*) account withEncryption:(BOOL) encrypt andCompletion:(void (^)(NSString* _Nullable url, NSString* _Nullable mimeType, NSNumber* _Nullable size, NSError* _Nullable error)) completion; ++(void) uploadUIImage:(UIImage*) image onAccount:(xmpp*) account withEncryption:(BOOL) encrypt andCompletion:(void (^)(NSString* _Nullable url, NSString* _Nullable mimeType, NSNumber* _Nullable size, NSError* _Nullable error)) completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/MLFiletransfer.m b/Monal/Classes/MLFiletransfer.m new file mode 100644 index 0000000000..46aa476b0d --- /dev/null +++ b/Monal/Classes/MLFiletransfer.m @@ -0,0 +1,570 @@ +// +// MLFiletransfer.m +// monalxmpp +// +// Created by Thilo Molitor on 12.11.20. +// Copyright Β© 2020 Monal.im. All rights reserved. +// + +#import +#import "MLConstants.h" +#import "MLFiletransfer.h" +#import "HelperTools.h" +#import "DataLayer.h" +#import "MLEncryptedPayload.h" +#import "xmpp.h" +#import "AESGcm.h" + +@import MobileCoreServices; + +static NSFileManager* fileManager; +static NSString* documentCache; +static NSMutableSet* checklist; + +@implementation MLFiletransfer + ++(void) initialize +{ + fileManager = [NSFileManager defaultManager]; + documentCache = [[[fileManager containerURLForSecurityApplicationGroupIdentifier:kAppGroup] path] stringByAppendingPathComponent:@"documentCache"]; + NSError* error; + [fileManager createDirectoryAtURL:[NSURL fileURLWithPath:documentCache] withIntermediateDirectories:YES attributes:nil error:&error]; + if(error) + @throw [NSException exceptionWithName:@"NSError" reason:[NSString stringWithFormat:@"%@", error] userInfo:@{@"error": error}]; + [HelperTools configureFileProtectionFor:documentCache]; + checklist = [[NSMutableSet alloc] init]; +} + ++(void) checkMimeTypeAndSizeForHistoryID:(NSNumber*) historyId +{ + MLMessage* msg = [[DataLayer sharedInstance] messageForHistoryID:historyId]; + if(!msg) + { + DDLogError(@"historyId %@ does not yield an MLMessage object, aborting", historyId); + return; + } + //make sure we don't check or download this twice + @synchronized(checklist) + { + if([checklist containsObject:historyId]) + { + DDLogDebug(@"Already checking/downloading this content, ignoring"); + return; + } + [checklist addObject:historyId]; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + DDLogInfo(@"Requesting mime-type and size for historyID %@ from http server", historyId); + NSString* url = [self genCanonicalUrl:msg.messageText]; + NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; + request.HTTPMethod = @"HEAD"; + request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; + + NSURLSession* session = [NSURLSession sharedSession]; + [[session dataTaskWithRequest:request completionHandler:^(NSData* _Nullable data, NSURLResponse* _Nullable response, NSError* _Nullable error) { + NSDictionary* headers = ((NSHTTPURLResponse*)response).allHeaderFields; + NSString* mimeType = [[headers objectForKey:@"Content-Type"] lowercaseString]; + NSNumber* contentLength = [headers objectForKey:@"Content-Length"] ? [NSNumber numberWithInt:([[headers objectForKey:@"Content-Length"] intValue])] : @(-1); + if(!mimeType) //default mime type if none was returned by http server + mimeType = @"application/octet-stream"; + + //try to deduce the content type from a given file extension if needed and possible + if([mimeType isEqualToString:@"application/octet-stream"]) + { + NSURLComponents* urlComponents = [NSURLComponents componentsWithString:url]; + if(urlComponents) + mimeType = [self getMimeTypeOfOriginalFile:urlComponents.path]; + } + + //make sure we *always* have a mime type + if(!mimeType) + mimeType = @"application/octet-stream"; + + DDLogInfo(@"Got http mime-type and size for historyID %@: %@ (%@)", historyId, mimeType, contentLength); + DDLogDebug(@"Updating db and sending out kMonalMessageFiletransferUpdateNotice"); + + //update db with content type and size + [[DataLayer sharedInstance] setMessageHistoryId:historyId filetransferMimeType:mimeType filetransferSize:contentLength]; + + //send out update notification (and update used MLMessage object directly instead of reloading it from db after updating the db) + msg.filetransferMimeType = mimeType; + msg.filetransferSize = contentLength; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalMessageFiletransferUpdateNotice object:nil userInfo:@{@"message": msg}]; + + //check done, remove from "currently checking/downloading list" + [checklist removeObject:historyId]; + + //try to autodownload if sizes match + //TODO JIM: these are the settings used for size checks and autodownload allowed checks + if( + [[HelperTools defaultsDB] boolForKey:@"AutodownloadFiletransfers"] && + [contentLength intValue] >= 0 && //-1 means we don't know the size --> don't autodownload files of unknown sizes + [contentLength integerValue] <= [[HelperTools defaultsDB] integerForKey:@"AutodownloadFiletransfersMaxSize"] && + //TODO JIM: this should be checked for image filetransfers only or mabe removed entirely in favor of AutodownloadFiletransfers + [[HelperTools defaultsDB] boolForKey:@"ShowImages"] + ) + { + DDLogInfo(@"Autodownloading file"); + [self downloadFileForHistoryID:historyId]; + } + }] resume]; + }); +} + ++(void) downloadFileForHistoryID:(NSNumber*) historyId +{ + MLMessage* msg = [[DataLayer sharedInstance] messageForHistoryID:historyId]; + if(!msg) + { + DDLogError(@"historyId %@ does not yield an MLMessage object, aborting", historyId); + return; + } + //make sure we don't check or download this twice + @synchronized(checklist) + { + if([checklist containsObject:historyId]) + { + DDLogDebug(@"Already checking/downloading this content, ignoring"); + return; + } + [checklist addObject:historyId]; + } + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + DDLogInfo(@"Downloading file for historyID %@", historyId); + NSString* url = [self genCanonicalUrl:msg.messageText]; + NSURLComponents* urlComponents = [NSURLComponents componentsWithString:msg.messageText]; + if(!urlComponents) + { + DDLogError(@"url components decoding failed"); + [self setErrorType:NSLocalizedString(@"Download error", @"") andErrorText:NSLocalizedString(@"Failed to decode download link", @"") forMessageId:msg.messageId]; + [checklist removeObject:historyId]; + return; + } + + NSURLSession* session = [NSURLSession sharedSession]; + NSURLSessionDownloadTask* task = [session downloadTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSURL* _Nullable location, NSURLResponse* _Nullable response, NSError* _Nullable error) { + NSDictionary* headers = ((NSHTTPURLResponse*)response).allHeaderFields; + NSString* mimeType = [[headers objectForKey:@"Content-Type"] lowercaseString]; + if(!mimeType) + mimeType = @"application/octet-stream"; + + //try to deduce the content type from a given file extension if needed and possible + if([mimeType isEqualToString:@"application/octet-stream"]) + mimeType = [self getMimeTypeOfOriginalFile:urlComponents.path]; + + //make sure we *always* have a mime type + if(!mimeType) + mimeType = @"application/octet-stream"; + + NSString* cacheFile = [self calculateCacheFileForNewUrl:msg.messageText andMimeType:mimeType]; + + //encrypted filetransfer + if([[urlComponents.scheme lowercaseString] isEqualToString:@"aesgcm"]) + { + DDLogInfo(@"Decrypting encrypted filetransfer"); + if(urlComponents.fragment.length < 88) + { + DDLogError(@"File download failed: %@", error); + [self setErrorType:NSLocalizedString(@"Download error", @"") andErrorText:NSLocalizedString(@"Failed to decode encrypted link", @"") forMessageId:msg.messageId]; + [checklist removeObject:historyId]; + return; + } + int ivLength = 24; + //format is iv+32byte key + NSData* key = [HelperTools dataWithHexString:[urlComponents.fragment substringWithRange:NSMakeRange(ivLength, 64)]]; + NSData* iv = [HelperTools dataWithHexString:[urlComponents.fragment substringToIndex:ivLength]]; + + //decrypt data with given key and iv + NSData* encryptedData = [NSData dataWithContentsOfURL:location]; + if(encryptedData && encryptedData.length > 0 && key && iv) + { + NSData* decryptedData = [AESGcm decrypt:encryptedData withKey:key andIv:iv withAuth:nil]; + [decryptedData writeToFile:cacheFile options:NSDataWritingAtomic error:&error]; + if(error) + { + DDLogError(@"File download failed: %@", error); + [self setErrorType:NSLocalizedString(@"Download error", @"") andErrorText:NSLocalizedString(@"Failed to write decrypted download into cache directory", @"") forMessageId:msg.messageId]; + [checklist removeObject:historyId]; + return; + } + [HelperTools configureFileProtectionFor:cacheFile]; + } + } + else //cleartext filetransfer + { + //copy file to our document cache + DDLogInfo(@"Copying downloaded file to document cache at %@", cacheFile); + [fileManager moveItemAtPath:[location path] toPath:cacheFile error:&error]; + if(error) + { + DDLogError(@"File download failed: %@", error); + [self setErrorType:NSLocalizedString(@"Download error", @"") andErrorText:NSLocalizedString(@"Failed to copy downloaded file into cache directory", @"") forMessageId:msg.messageId]; + [checklist removeObject:historyId]; + return; + } + [HelperTools configureFileProtectionFor:cacheFile]; + } + + DDLogDebug(@"Updating db and sending out kMonalMessageFiletransferUpdateNotice"); + + //update db with content type and size + NSNumber* filetransferSize = @([[fileManager attributesOfItemAtPath:cacheFile error:nil] fileSize]); + [[DataLayer sharedInstance] setMessageHistoryId:historyId filetransferMimeType:mimeType filetransferSize:filetransferSize]; + + //send out update notification (and update used MLMessage object directly instead of reloading it from db after updating the db) + msg.filetransferMimeType = mimeType; + msg.filetransferSize = filetransferSize; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalMessageFiletransferUpdateNotice object:nil userInfo:@{@"message": msg}]; + + //download done, remove from "currently checking/downloading list" + [checklist removeObject:historyId]; + }]; + [task resume]; + }); +} + ++(NSDictionary*) getFileInfoForMessage:(MLMessage*) msg +{ + if(![msg.messageType isEqualToString:kMessageTypeFiletransfer]) + return nil; + NSURLComponents* urlComponents = [NSURLComponents componentsWithString:msg.messageText]; + NSString* filename = [[NSUUID UUID] UUIDString]; //default is a dummy filename (used when the filename can not be extracted from url) + if(urlComponents != nil && urlComponents.path) + filename = [urlComponents.path lastPathComponent]; + NSString* cacheFile = [self retrieveCacheFileForUrl:msg.messageText andMimeType:(msg.filetransferMimeType && ![msg.filetransferMimeType isEqualToString:@""] ? msg.filetransferMimeType : nil)]; + + //return every information we have + if(!cacheFile) + { + //if we have mimeype and size the http head request was already done, else we did not even do a head request + if(msg.filetransferMimeType != nil && msg.filetransferSize != nil) + return @{ + @"url": msg.messageText, + @"filename": filename, + @"needsDownloading": @YES, + @"mimeType": msg.filetransferMimeType, + @"size": msg.filetransferSize, + }; + else + return @{ + @"url": msg.messageText, + @"filename": filename, + @"needsDownloading": @YES, + }; + } + return @{ + @"url": msg.messageText, + @"filename": filename, + @"cacheId": [cacheFile lastPathComponent], + @"cacheFile": cacheFile, + @"needsDownloading": @NO, + @"mimeType": [self getMimeTypeOfCacheFile:cacheFile], + @"size": @([[fileManager attributesOfItemAtPath:cacheFile error:nil] fileSize]), + }; +} + ++(void) deleteFileForMessage:(MLMessage*) msg +{ + if(![msg.messageType isEqualToString:kMessageTypeFiletransfer]) + return; + DDLogInfo(@"Deleting file for url %@", msg.messageText); + NSDictionary* info = [self getFileInfoForMessage:msg]; + if(info) + { + DDLogDebug(@"Deleting file in cache: %@", info[@"cacheFile"]); + [fileManager removeItemAtPath:info[@"cacheFile"] error:nil]; + } +} + ++(void) uploadFile:(NSURL*) fileUrl onAccount:(xmpp*) account withEncryption:(BOOL) encrypt andCompletion:(void (^)(NSString* _Nullable url, NSString* _Nullable mimeType, NSNumber* _Nullable size, NSError* _Nullable error)) completion +{ + DDLogInfo(@"Uploading file stored at %@", [fileUrl path]); + + //copy file to our document cache (temporary filename because the upload url is unknown yet) + NSString* tempname = [NSString stringWithFormat:@"%@.tmp", [[NSUUID UUID] UUIDString]]; + NSError* error; + NSString* file = [documentCache stringByAppendingPathComponent:tempname]; + DDLogDebug(@"Tempstoring file at %@", file); + [fileManager copyItemAtPath:[fileUrl path] toPath:file error:&error]; + if(error) + { + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + [HelperTools configureFileProtectionFor:file]; + + [self internalUploadHandlerForTmpFile:file userFacingFilename:[fileUrl lastPathComponent] mimeType:[self getMimeTypeOfOriginalFile:[fileUrl path]] onAccount:account withEncryption:encrypt andCompletion:completion]; +} + ++(void) uploadUIImage:(UIImage*) image onAccount:(xmpp*) account withEncryption:(BOOL) encrypt andCompletion:(void (^)(NSString* _Nullable url, NSString* _Nullable mimeType, NSNumber* _Nullable size, NSError* _Nullable error)) completion +{ + double jpegQuality = 0.75; //TODO JIM: make this configurable in upload/download settings + + DDLogInfo(@"Uploading image from UIImage object"); + + //copy file to our document cache (temporary filename because the upload url is unknown yet) + NSString* tempname = [NSString stringWithFormat:@"%@.tmp", [[NSUUID UUID] UUIDString]]; + NSError* error; + NSString* file = [documentCache stringByAppendingPathComponent:tempname]; + DDLogDebug(@"Tempstoring jpeg encoded file having quality %f at %@", jpegQuality, file); + NSData* imageData = UIImageJPEGRepresentation(image, jpegQuality); + [imageData writeToFile:file options:NSDataWritingAtomic error:&error]; + if(error) + { + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + [HelperTools configureFileProtectionFor:file]; + + [self internalUploadHandlerForTmpFile:file userFacingFilename:[NSString stringWithFormat:@"%@.jpg", [[NSUUID UUID] UUIDString]] mimeType:@"image/jpeg" onAccount:account withEncryption:encrypt andCompletion:completion]; +} + ++(void) doStartupCleanup +{ + //delete leftover tmp files + NSArray* directoryContents = [fileManager contentsOfDirectoryAtPath:documentCache error:nil]; + NSPredicate* filter = [NSPredicate predicateWithFormat:@"self ENDSWITH '.tmp'"]; + for(NSString* file in [directoryContents filteredArrayUsingPredicate:filter]) + { + DDLogInfo(@"Deleting leftover tmp file at %@", [documentCache stringByAppendingPathComponent:file]); + [fileManager removeItemAtPath:[documentCache stringByAppendingPathComponent:file] error:nil]; + } + + //*** migrate old image store to new fileupload store if needed*** + if(![[HelperTools defaultsDB] boolForKey:@"ImageCacheMigratedToFiletransferCache"]) + { + DDLogInfo(@"Migrating old image store to new filetransfer cache"); + + //first of all upgrade all message types (needed to make getFileInfoForMessage: work later on) + [[DataLayer sharedInstance] upgradeImageMessagesToFiletransferMessages]; + + //copy all images listed in old imageCache db tables to our new filetransfer store + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString* documentsDirectory = [paths objectAtIndex:0]; + NSString* cachePath = [documentsDirectory stringByAppendingPathComponent:@"imagecache"]; + for(NSDictionary* img in [[DataLayer sharedInstance] getAllCachedImages]) + { + //extract old url, file and mime type + NSURLComponents* urlComponents = [NSURLComponents componentsWithString:img[@"url"]]; + if(!urlComponents) + continue; + NSString* mimeType = [self getMimeTypeOfOriginalFile:urlComponents.path]; + NSString* oldFile = [cachePath stringByAppendingPathComponent:img[@"path"]]; + NSString* newFile = [self calculateCacheFileForNewUrl:img[@"url"] andMimeType:mimeType]; + + DDLogInfo(@"Migrating old image cache file %@ (having mimeType %@) for URL %@ to new cache at %@", oldFile, mimeType, img[@"url"], newFile); + if([fileManager fileExistsAtPath:oldFile]) + { + [fileManager copyItemAtPath:oldFile toPath:newFile error:nil]; + [HelperTools configureFileProtectionFor:newFile]; + [fileManager removeItemAtPath:oldFile error:nil]; + } + else + DDLogWarn(@"Old file not existing --> not moving file, but still updating db entries"); + + //update every history_db entry with new filetransfer metadata + //(this will flip the message type to kMessageTypeFiletransfer and set correct mimeType and size values) + NSArray* messageList = [[DataLayer sharedInstance] getAllMessagesForFiletransferUrl:img[@"url"]]; + if(![messageList count]) + { + DDLogWarn(@"No messages in history db having this url, deleting file completely"); + [fileManager removeItemAtPath:newFile error:nil]; + } + else + { + DDLogInfo(@"Updating every history db entry with new filetransfer metadata: %lu messages", [messageList count]); + for(MLMessage* msg in messageList) + { + NSDictionary* info = [self getFileInfoForMessage:msg]; + DDLogDebug(@"FILETRANSFER INFO: %@", info); + //don't update mime type and size if we still need to download the file (both is unknown in this case) + if(info && ![info[@"needsDownloading"] boolValue]) + [[DataLayer sharedInstance] setMessageHistoryId:msg.messageDBId filetransferMimeType:info[@"mimeType"] filetransferSize:info[@"size"]]; + } + } + } + + //remove old db tables completely + [[DataLayer sharedInstance] removeImageCacheTables]; + [[HelperTools defaultsDB] setBool:YES forKey:@"ImageCacheMigratedToFiletransferCache"]; + DDLogInfo(@"Migration done"); + } +} + +#pragma mark - internal methods + ++(NSString*) retrieveCacheFileForUrl:(NSString*) url andMimeType:(NSString*) mimeType +{ + NSString* urlPart = [HelperTools hexadecimalString:[HelperTools sha256:[url dataUsingEncoding:NSUTF8StringEncoding]]]; + if(mimeType) + { + NSString* mimePart = [HelperTools hexadecimalString:[mimeType dataUsingEncoding:NSUTF8StringEncoding]]; + + //the cache filename consists of a hash of the upload url (in hex) followed of the file mimetype (also in hex) as file extension + NSString* cacheFile = [documentCache stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", urlPart, mimePart]]; + + //file having the supplied mimeType exists + if([fileManager fileExistsAtPath:cacheFile]) + return cacheFile; + } + + //check for files having a different mime type but the same base url + NSArray* directoryContents = [fileManager contentsOfDirectoryAtPath:documentCache error:nil]; + NSPredicate* filter = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"self BEGINSWITH '%@.'", urlPart]]; + for(NSString* file in [directoryContents filteredArrayUsingPredicate:filter]) + return [documentCache stringByAppendingPathComponent:file]; + + //nothing found + return nil; +} + ++(NSString*) calculateCacheFileForNewUrl:(NSString*) url andMimeType:(NSString*) mimeType +{ + //the cache filename consists of a hash of the upload url (in hex) followed of the file mimetype (also in hex) as file extension + NSString* urlPart = [HelperTools hexadecimalString:[HelperTools sha256:[url dataUsingEncoding:NSUTF8StringEncoding]]]; + NSString* mimePart = [HelperTools hexadecimalString:[mimeType dataUsingEncoding:NSUTF8StringEncoding]]; + return [documentCache stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", urlPart, mimePart]]; +} + ++(NSString*) genCanonicalUrl:(NSString*) url +{ + NSURLComponents* urlComponents = [NSURLComponents componentsWithString:url]; + if(!urlComponents) + { + DDLogWarn(@"Failed to get url components, returning url possibly still including an urlfragment!"); + return url; + } + if([[urlComponents.scheme lowercaseString] isEqualToString:@"aesgcm"]) + urlComponents.scheme = @"https"; + urlComponents.fragment = @""; //make sure we don't leak urlfragments to upload server + return urlComponents.string; +} + ++(NSString*) getMimeTypeOfOriginalFile:(NSString*) file +{ + CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[file pathExtension], NULL); + NSString* mimeType = (__bridge_transfer NSString*)UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType); + CFRelease(UTI); + if(!mimeType) + mimeType = @"application/octet-stream"; + return mimeType; +} + ++(NSString*) getMimeTypeOfCacheFile:(NSString*) file +{ + return [[NSString alloc] initWithData:[HelperTools dataWithHexString:[file pathExtension]] encoding:NSUTF8StringEncoding]; +} + ++(void) setErrorType:(NSString*) errorType andErrorText:(NSString*) errorText forMessageId:(NSString*) messageId +{ + //update db + [[DataLayer sharedInstance] + setMessageId:messageId + errorType:errorType + errorReason:errorText + ]; + + //inform chatview of error + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalMessageErrorNotice object:nil userInfo:@{ + @"MessageID": messageId, + @"errorType": errorType, + @"errorReason": errorText + }]; +} + ++(void) internalUploadHandlerForTmpFile:(NSString*) file userFacingFilename:(NSString*) userFacingFilename mimeType:(NSString*) mimeType onAccount:(xmpp*) account withEncryption:(BOOL) encrypted andCompletion:(void (^)(NSString* _Nullable url, NSString* _Nullable mimeType, NSNumber* _Nullable size, NSError* _Nullable)) completion +{ + //TODO: allow real file based transfers instead of NSData based transfers + DDLogDebug(@"Reading file data into NSData object"); + NSError* error; + NSData* fileData = [[NSData alloc] initWithContentsOfFile:file options:0 error:&error]; + if(error) + { + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + + //encrypt data (TODO: do this in a streaming fashion, e.g. from file to tmpfile and stream this tmpfile via http afterwards) + MLEncryptedPayload* encryptedPayload; + if(encrypted) + { + DDLogInfo(@"Encrypting file data before upload"); + encryptedPayload = [AESGcm encrypt:fileData keySize:32]; + if(encryptedPayload) + { + NSMutableData* encryptedData = [encryptedPayload.body mutableCopy]; + [encryptedData appendData:encryptedPayload.authTag]; + fileData = encryptedData; + } + else + { + NSError* error = [NSError errorWithDomain:@"MonalError" code:0 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to encrypt file", @"")}]; + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + } + + //make sure we don't leak information about encrypted files + NSString* sendMimeType = mimeType; + if(encrypted) + sendMimeType = @"application/octet-stream"; + + DDLogDebug(@"Requesting file upload slot for mimeType %@", sendMimeType); + [account requestHTTPSlotWithParams:@{ + kData:fileData, + kFileName:userFacingFilename, + kContentType:sendMimeType + } andCompletion:^(NSString *url, NSError *error) { + if(error) + { + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + + NSURLComponents* urlComponents = [NSURLComponents componentsWithString:url]; + if(url && urlComponents) + { + //build aesgcm url containing "aesgcm" url-scheme and IV and AES-key in urlfragment + if(encrypted) + { + urlComponents.scheme = @"aesgcm"; + urlComponents.fragment = [NSString stringWithFormat:@"%@%@", + [HelperTools hexadecimalString:encryptedPayload.iv], + //extract real aes key without authtag (32 bytes = 256bit) + //TODO: DOES THIS MAKE SENSE (WHY NO AUTH TAG??) + [HelperTools hexadecimalString:[encryptedPayload.key subdataWithRange:NSMakeRange(0, 32)]]]; + //[HelperTools hexadecimalString:encryptedPayload.key]]; + url = urlComponents.string; + } + + //move the tempfile to our cache location + NSString* cacheFile = [self calculateCacheFileForNewUrl:url andMimeType:mimeType]; + DDLogInfo(@"Moving (possibly encrypted) file to our document cache at %@", cacheFile); + [fileManager moveItemAtPath:file toPath:cacheFile error:&error]; + if(error) + { + NSError* error = [NSError errorWithDomain:@"MonalError" code:0 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to uploaded file to file cache directory", @"")}]; + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + [HelperTools configureFileProtectionFor:cacheFile]; + + return completion(url, mimeType, [NSNumber numberWithInteger:fileData.length], nil); + } + else + { + NSError* error = [NSError errorWithDomain:@"MonalError" code:0 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to parse URL returned by HTTP upload server", @"")}]; + [fileManager removeItemAtPath:file error:nil]; //remove temporary file + DDLogError(@"File upload failed: %@", error); + return completion(nil, nil, nil, error); + } + }]; +} + +@end diff --git a/Monal/Classes/MLIQProcessor.m b/Monal/Classes/MLIQProcessor.m index 74f9a2d4e3..8a063f1db4 100644 --- a/Monal/Classes/MLIQProcessor.m +++ b/Monal/Classes/MLIQProcessor.m @@ -89,14 +89,14 @@ +(void) processErrorIq:(XMPPIQ*) iqNode forAccount:(xmpp*) account DDLogWarn(@"Got unhandled IQ error: %@", iqNode); } -+(void) postError:(NSString*) description withIqNode:(XMPPIQ*) iqNode andAccount:(xmpp*) account ++(void) postError:(NSString*) description withIqNode:(XMPPIQ*) iqNode andAccount:(xmpp*) account andIsSevere:(BOOL) isSevere { - NSString* errorReason = [iqNode findFirst:@"{urn:ietf:params:xml:ns:xmpp-stanzas}!text$"]; - NSString* errorText = [iqNode findFirst:@"{urn:ietf:params:xml:ns:xmpp-stanzas}text#"]; - NSString* message = [NSString stringWithFormat:@"%@: %@", description, errorReason]; - if(errorText && ![errorText isEqualToString:@""]) - message = [NSString stringWithFormat:@"%@ %@: %@", description, errorReason, errorText]; - [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:@[account, message]]; + NSString* message; + if(iqNode) + message = [HelperTools extractXMPPError:iqNode withDescription:description]; + else + message = description; + [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:account userInfo:@{@"message": message, @"isSevere":@(isSevere)}]; } $$handler(handleCatchup, $_ID(xmpp*, account), $_ID(XMPPIQ*, iqNode)) @@ -150,7 +150,7 @@ +(void) postError:(NSString*) description withIqNode:(XMPPIQ*) iqNode andAccount DDLogWarn(@"Binding our resource returned an error: %@", [iqNode findFirst:@"error"]); if([iqNode check:@"/"]) { - [self postError:@"XMPP Bind Error" withIqNode:iqNode andAccount:account]; + [self postError:NSLocalizedString(@"XMPP Bind Error", @"") withIqNode:iqNode andAccount:account andIsSevere:YES]; [account disconnect]; } else if([iqNode check:@"/"]) @@ -210,7 +210,7 @@ +(void) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode if([iqNode check:@"/"]) { DDLogWarn(@"Roster query returned an error: %@", [iqNode findFirst:@"error"]); - [self postError:@"XMPP Roster Error" withIqNode:iqNode andAccount:account]; + [self postError:NSLocalizedString(@"XMPP Roster Error", @"") withIqNode:iqNode andAccount:account andIsSevere:NO]; return; } @@ -263,7 +263,7 @@ +(void) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode if([iqNode check:@"/"]) { DDLogError(@"Disco info query to our account returned an error: %@", [iqNode findFirst:@"error"]); - [self postError:@"XMPP Account Info Error" withIqNode:iqNode andAccount:account]; + [self postError:NSLocalizedString(@"XMPP Account Info Error", @"") withIqNode:iqNode andAccount:account andIsSevere:NO]; return; } @@ -331,7 +331,7 @@ +(void) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode if([iqNode check:@"/"]) { DDLogError(@"Disco info query to our server returned an error: %@", [iqNode findFirst:@"error"]); - [self postError:@"XMPP Disco Info Error" withIqNode:iqNode andAccount:account]; + [self postError:NSLocalizedString(@"XMPP Disco Info Error", @"") withIqNode:iqNode andAccount:account andIsSevere:NO]; return; } @@ -396,6 +396,31 @@ +(void) processRosterWithAccount:(xmpp*) account andIqNode:(XMPPIQ*) iqNode [[DataLayer sharedInstance] setCaps:features forVer:ver]; $$ +$$handler(handleMamPrefs, $_ID(xmpp*, account), $_ID(XMPPIQ*, iqNode)) + if([iqNode check:@"/"]) + { + DDLogError(@"MAM prefs query returned an error: %@", [iqNode findFirst:@"error"]); + [self postError:NSLocalizedString(@"XMPP mam preferences error", @"") withIqNode:iqNode andAccount:account andIsSevere:NO]; + return; + } + if([iqNode check:@"{urn:xmpp:mam:2}prefs@default"]) + [[NSNotificationCenter defaultCenter] postNotificationName:kMLMAMPref object:self userInfo:@{@"mamPref": [iqNode findFirst:@"{urn:xmpp:mam:2}prefs@default"]}]; + else + { + DDLogError(@"MAM prefs query returned unexpected result: %@", iqNode); + [self postError:NSLocalizedString(@"Unexpected mam preferences result", @"") withIqNode:nil andAccount:account andIsSevere:NO]; + } +$$ + +$$handler(handleSetMamPrefs, $_ID(xmpp*, account), $_ID(XMPPIQ*, iqNode)) + if([iqNode check:@"/"]) + { + DDLogError(@"Seting MAM prefs returned an error: %@", [iqNode findFirst:@"error"]); + [self postError:NSLocalizedString(@"XMPP mam preferences error", @"") withIqNode:iqNode andAccount:account andIsSevere:NO]; + return; + } +$$ + +(void) iqVersionResult:(XMPPIQ*) iqNode forAccount:(xmpp*) account { NSString* iqAppName = [iqNode findFirst:@"{jabber:iq:version}query/name#"]; diff --git a/Monal/Classes/MLImageManager.h b/Monal/Classes/MLImageManager.h index 2628f1215d..83751442f4 100644 --- a/Monal/Classes/MLImageManager.h +++ b/Monal/Classes/MLImageManager.h @@ -10,7 +10,7 @@ @import UIKit; -@interface MLImageManager : NSObject +@interface MLImageManager : NSObject /** chatview inbound background image @@ -41,13 +41,9 @@ -(UIImage *_Nullable) getBackground; --(void) imageForAttachmentLink:(NSString *_Nonnull) url withCompletion:(void (^_Nullable)(NSData * _Nullable data)) completionHandler; --(void) imageURLForAttachmentLink:(NSString *_Nonnull) url withCompletion:(void (^_Nullable)(NSURL * _Nullable url)) completionHandler; - /** Purge cache in the event of a memory warning */ -(void) purgeCache; --(void) saveImageData:(NSData* _Nonnull) data forLink:(NSString* _Nonnull) link; @end diff --git a/Monal/Classes/MLImageManager.m b/Monal/Classes/MLImageManager.m index 114a2c5b72..5e299c4f05 100644 --- a/Monal/Classes/MLImageManager.m +++ b/Monal/Classes/MLImageManager.m @@ -14,18 +14,15 @@ @interface MLImageManager() -@property (nonatomic, strong) NSCache *iconCache; -@property (nonatomic, strong) NSCache *imageCache; - -@property (nonatomic, strong) NSOperationQueue *fileQueue; -@property (nonatomic, strong) UIImage *noIcon; +@property (nonatomic, strong) NSCache* iconCache; +@property (nonatomic, strong) UIImage* noIcon; @end @implementation MLImageManager #pragma mark initilization -+ (MLImageManager* )sharedInstance ++(MLImageManager*) sharedInstance { static dispatch_once_t once; static MLImageManager* sharedInstance; @@ -52,7 +49,6 @@ + (MLImageManager* )sharedInstance -(id) init { self=[super init]; - self.fileQueue = [[NSOperationQueue alloc] init]; return self; } @@ -69,17 +65,10 @@ -(NSCache*) iconCache return _iconCache; } --(NSCache*) imageCache -{ - if(!_imageCache) _imageCache=[[NSCache alloc] init]; - return _iconCache; -} - -(void) purgeCache { _iconCache=nil; _noIcon=nil; - _imageCache=nil; } @@ -246,188 +235,7 @@ -(UIImage *) getBackground return self.chatBackground; } - --(void) filePathForURL:(NSString *)url wuthCompletion:(void (^)(NSString * _Nullable path)) completionHandler { - NSString* path = [[DataLayer sharedInstance] imageCacheForUrl:url]; - if(completionHandler) completionHandler(path); -} - --(NSString *) savefilePathforURL:(NSString *)url { - NSString *filename = [NSUUID UUID].UUIDString; - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *writePath = [documentsDirectory stringByAppendingPathComponent:@"imagecache"]; - writePath = [writePath stringByAppendingPathComponent:filename]; - - [[DataLayer sharedInstance] createImageCache:filename forUrl:url]; - - return writePath; -} - --(NSString *) saveTempfilePathforURL:(NSString *)url { - NSString *filename = [NSUUID UUID].UUIDString; - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *writePath = [documentsDirectory stringByAppendingPathComponent:@"tempImage"]; - writePath = [writePath stringByAppendingPathComponent:filename]; - - return writePath; -} - --(void) imageForAttachmentLink:(NSString *) url withCompletion:(void (^)(NSData * _Nullable data)) completionHandler -{ - NSData *cachedData = [self.imageCache objectForKey:url]; - if(cachedData) { - if(completionHandler) completionHandler(cachedData); - } - - [self filePathForURL:url wuthCompletion:^(NSString * _Nullable path) { - - if(path) { - [self.fileQueue addOperationWithBlock:^{ - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *readPath = [documentsDirectory stringByAppendingPathComponent:@"imagecache"]; - readPath = [readPath stringByAppendingPathComponent:path]; - NSData *data =[NSData dataWithContentsOfFile:readPath]; - if(data) [self.imageCache setObject:data forKey:url]; - if(completionHandler) completionHandler(data); - }]; - } else { - if ([url hasPrefix:@"aesgcm://"]) - { - [self attachmentDataFromEncryptedLink:url withCompletion:completionHandler]; - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDownloadTask *task=[session downloadTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { - NSData *downloaded= [NSData dataWithContentsOfURL:location]; - //cache data - [self.fileQueue addOperationWithBlock:^{ - NSString *path = [self savefilePathforURL:url]; - [downloaded writeToFile:path atomically:YES]; - if(downloaded) [self.imageCache setObject:downloaded forKey:url]; - if(completionHandler) completionHandler(downloaded); - }]; - }]; - - [task resume]; - }); - } - } - - }]; - - // return task.progress; -} - -/** - Writes once to the regular location and again to the temp location. -Provides temp url - */ --(void) imageURLForAttachmentLink:(NSString *) url withCompletion:(void (^_Nullable)(NSURL * _Nullable path)) completionHandler -{ - [self filePathForURL:url wuthCompletion:^(NSString * _Nullable path) { - dispatch_async(dispatch_get_main_queue(), ^{ - if(path) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *readPath = [documentsDirectory stringByAppendingPathComponent:@"imagecache"]; - readPath = [readPath stringByAppendingPathComponent:path]; - NSData *data =[NSData dataWithContentsOfFile:readPath]; - if(data) [self.imageCache setObject:data forKey:url]; - NSString *tempPath = [self saveTempfilePathforURL:url]; - [data writeToFile:tempPath atomically:YES]; - - if(completionHandler) completionHandler([NSURL fileURLWithPath:tempPath]); - } else { - if ([url hasPrefix:@"aesgcm://"]) - { - [self attachmentDataFromEncryptedLink:url withCompletion:^(NSData * _Nullable data) { - NSString *tempPath = [self saveTempfilePathforURL:url]; - [data writeToFile:tempPath atomically:YES]; - if(completionHandler) completionHandler([NSURL fileURLWithPath:tempPath]); - }]; - } else { - NSURLSession *session = [NSURLSession sharedSession]; - [[session downloadTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { - NSData *downloaded= [NSData dataWithContentsOfURL:location]; - //cache data - NSString *path = [self savefilePathforURL:url]; - [downloaded writeToFile:path atomically:YES]; - - NSString *tempPath = [self saveTempfilePathforURL:url]; - [downloaded writeToFile:tempPath atomically:YES]; - if(downloaded) [self.imageCache setObject:downloaded forKey:url]; - if(completionHandler) completionHandler([NSURL fileURLWithPath:tempPath]); - }] resume]; - } - } - }); - }]; -} - --(void) saveImageData:(NSData* _Nonnull) data forLink:(NSString* _Nonnull) link -{ - [self.fileQueue addOperationWithBlock:^{ - NSString *path = [self savefilePathforURL:link]; - [data writeToFile:path atomically:YES]; - [self.imageCache setObject:data forKey:link]; - }]; -} - -- (void) attachmentDataFromEncryptedLink:(NSString *) link withCompletion:(void (^)(NSData * _Nullable data)) completionHandler { - if ([link hasPrefix:@"aesgcm://"]) - { - NSString *cleanLink= [link stringByReplacingOccurrencesOfString:@"aesgcm://" withString:@"https://"]; - NSArray *parts = [cleanLink componentsSeparatedByString:@"#"]; - if(parts.count>1) { - NSString *url = parts[0]; - NSString *crypto = parts[1]; - if(crypto.length>=88) { - dispatch_async(dispatch_get_main_queue(), ^{ - - int ivLength=24; - if(crypto.length==96) ivLength=32; //note 32 aka, 16 byte iv wont wont work on catalyst - - NSString *ivHex =[crypto substringToIndex:ivLength]; - //format is - //iv+32byte key - NSString *keyHex =[crypto substringWithRange:NSMakeRange(ivLength, 64)]; - NSURLSession *session = [NSURLSession sharedSession]; - [[session downloadTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { - - //decrypt - NSData *key = [HelperTools dataWithHexString:keyHex]; - NSData *iv = [HelperTools dataWithHexString:ivHex]; - - NSData *decrypted; - NSData *downloaded= [NSData dataWithContentsOfURL:location]; - if(downloaded && downloaded.length>0 && key && iv) { - decrypted=[AESGcm decrypt:downloaded withKey:key andIv:iv withAuth:nil]; - [self.fileQueue addOperationWithBlock:^{ - NSString *path = [self savefilePathforURL:link]; - [decrypted writeToFile:path atomically:YES]; - if(decrypted) [self.imageCache setObject:decrypted forKey:link]; - if(completionHandler) completionHandler(decrypted); - }]; - } else { - DDLogError(@"no data from aesgcm link, error %@", error); - } - - }] resume]; - }); - - } else { - DDLogError(@"aesgcm key and iv too short %@", link); - } - } else { - DDLogError(@"aesgcm url missing parts %@", link); - } - } -} - - +/* - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten @@ -435,5 +243,5 @@ - (void)URLSession:(NSURLSession *)session totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ } - +*/ @end diff --git a/Monal/Classes/MLInfoCell.m b/Monal/Classes/MLInfoCell.m index 701a3ba48c..f02b4b1317 100644 --- a/Monal/Classes/MLInfoCell.m +++ b/Monal/Classes/MLInfoCell.m @@ -71,7 +71,16 @@ -(void)setType:(NSString *)type if([type isEqualToString:@"connect"]) { //self.imageView.image=[UIImage imageNamed:@"connect"]; - _spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; + if (@available(iOS 13.0, *)) { + _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge]; + + } + else + { +#if !TARGET_OS_MACCATALYST + _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; +#endif + } CGRect frame = _spinner.frame; frame.origin.x+=5; frame.origin.y+=2.5; diff --git a/Monal/Classes/MLKeysTableViewController.m b/Monal/Classes/MLKeysTableViewController.m index e1298a6da1..506b67f773 100644 --- a/Monal/Classes/MLKeysTableViewController.m +++ b/Monal/Classes/MLKeysTableViewController.m @@ -10,6 +10,8 @@ #import "MLXMPPManager.h" #import "MLKeyCell.h" #import "MLOMEMO.h" +#import "SignalAddress.h" +#import "MLSignalStore.h" @interface MLKeysTableViewController () diff --git a/Monal/Classes/MLLogFormatter.m b/Monal/Classes/MLLogFormatter.m index 7ad8ef7991..338db692e0 100755 --- a/Monal/Classes/MLLogFormatter.m +++ b/Monal/Classes/MLLogFormatter.m @@ -23,7 +23,7 @@ static DDQualityOfServiceName _qos_name(NSUInteger qos) { } } -static NSString* _loglevel_name(NSUInteger flag) { +static inline NSString* _loglevel_name(NSUInteger flag) { if(flag & DDLogLevelOff) return @" OFF"; else if(flag & DDLogLevelError) diff --git a/Monal/Classes/MLLogInViewController.h b/Monal/Classes/MLLogInViewController.h index fc861f820b..4be61aade6 100644 --- a/Monal/Classes/MLLogInViewController.h +++ b/Monal/Classes/MLLogInViewController.h @@ -7,16 +7,19 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN -@interface MLLogInViewController : UIViewController -@property (nonatomic, weak) IBOutlet UITextField *jid; -@property (nonatomic, weak) IBOutlet UITextField *password; -@property (nonatomic, weak) IBOutlet UIButton *loginButton; -@property (nonatomic, weak) IBOutlet UIScrollView *scrollView; -@property (nonatomic, weak) IBOutlet UIView *contentView; -@property (nonatomic, weak) IBOutlet UIImageView *topImage; +@interface MLLogInViewController : UIViewController +@property (nonatomic, weak) IBOutlet UITextField* jid; +@property (nonatomic, weak) IBOutlet UITextField* password; +@property (nonatomic, weak) IBOutlet UIButton* loginButton; +@property (nonatomic, weak) IBOutlet UIScrollView* scrollView; +@property (nonatomic, weak) IBOutlet UIView* contentView; +@property (nonatomic, weak) IBOutlet UIImageView* topImage; +@property (weak, nonatomic) IBOutlet UIButton* qrScanButton; + -(IBAction) login:(id)sender; -(IBAction) registerAccount:(id)sender; diff --git a/Monal/Classes/MLLogInViewController.m b/Monal/Classes/MLLogInViewController.m index df65fbb232..f719d25685 100644 --- a/Monal/Classes/MLLogInViewController.m +++ b/Monal/Classes/MLLogInViewController.m @@ -10,11 +10,16 @@ #import "MBProgressHUD.h" #import "DataLayer.h" #import "MLXMPPManager.h" +#import "xmpp.h" + @import SAMKeychain; @import QuartzCore; @import SafariServices; +@class MLQRCodeScanner; + @interface MLLogInViewController () + @property (nonatomic, strong) MBProgressHUD *loginHUD; @property (nonatomic, weak) UITextField *activeField; @property (nonatomic, strong) NSString *accountno; @@ -35,9 +40,20 @@ - (void) viewWillAppear:(BOOL)animated NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(connected) name:kMonalFinishedCatchup object:nil]; +#ifndef DISABLE_OMEMO + [nc addObserver:self selector:@selector(updateBundleFetchStatus:) name:kMonalUpdateBundleFetchStatus object:nil]; +#endif [nc addObserver:self selector:@selector(omemoBundleFetchFinished) name:kMonalFinishedOmemoBundleFetch object:nil]; [nc addObserver:self selector:@selector(error) name:kXMPPError object:nil]; + if(@available(iOS 13.0, *)) + { + // nothing to do here + } + else + { + [self.qrScanButton setTitle:NSLocalizedString(@"QR", "MLLoginView: QR-Code Button iOS12 only") forState:UIControlStateNormal]; + } [self registerForKeyboardNotifications]; } @@ -58,8 +74,8 @@ -(IBAction) registerAccount:(id)sender; -(IBAction) login:(id)sender { - self.loginHUD= [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - self.loginHUD.label.text=NSLocalizedString(@"Logging in",@""); + self.loginHUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + self.loginHUD.label.text = NSLocalizedString(@"Logging in",@""); self.loginHUD.mode=MBProgressHUDModeIndeterminate; self.loginHUD.removeFromSuperViewOnHide=YES; @@ -79,8 +95,8 @@ -(IBAction) login:(id)sender if(!user || !domain) { - self.loginHUD.hidden=YES; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Invalid Credentials",@"") message:NSLocalizedString(@"Your XMPP account should be in in the format user@domain. For special configurations, use manual setup.",@"") preferredStyle:UIAlertControllerStyleAlert]; + self.loginHUD.hidden = YES; + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Invalid Credentials", @"") message:NSLocalizedString(@"Your XMPP account should be in in the format user@domain. For special configurations, use manual setup.", @"") preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close",@"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [alert dismissViewControllerAnimated:YES completion:nil]; }]]; @@ -88,10 +104,10 @@ -(IBAction) login:(id)sender return; } - if(password.length==0) + if(password.length == 0) { - self.loginHUD.hidden=YES; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Invalid Credentials",@"") message:NSLocalizedString(@"Please enter a password.",@"") preferredStyle:UIAlertControllerStyleAlert]; + self.loginHUD.hidden = YES; + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Invalid Credentials",@"") message:NSLocalizedString(@"Please enter a password.",@"") preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close",@"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [alert dismissViewControllerAnimated:YES completion:nil]; }]]; @@ -99,7 +115,7 @@ -(IBAction) login:(id)sender return; } - NSMutableDictionary *dic = [[NSMutableDictionary alloc] init]; + NSMutableDictionary* dic = [[NSMutableDictionary alloc] init]; [dic setObject:domain.lowercaseString forKey:kDomain]; [dic setObject:user.lowercaseString forKey:kUsername]; [dic setObject:[HelperTools encodeRandomResource] forKey:kResource]; @@ -114,10 +130,17 @@ -(IBAction) login:(id)sender [SAMKeychain setPassword:password forService:@"Monal" account:self.accountno]; [[MLXMPPManager sharedInstance] connectAccount:self.accountno]; } + + // open privacy settings + if(![[HelperTools defaultsDB] boolForKey:@"HasSeenPrivacySettings"]) { + [self performSegueWithIdentifier:@"showPrivacySettings" sender:self]; + return; + } } -(void) connected { + [[HelperTools defaultsDB] setBool:YES forKey:@"HasSeenLogin"]; #ifndef DISABLE_OMEMO dispatch_async(dispatch_get_main_queue(), ^{ self.loginHUD.label.text = NSLocalizedString(@"Loading omemo bundles", @""); @@ -127,6 +150,15 @@ -(void) connected #endif } +#ifndef DISABLE_OMEMO +-(void) updateBundleFetchStatus:(NSNotification*) notification +{ + dispatch_async(dispatch_get_main_queue(), ^{ + self.loginHUD.label.text = [NSString stringWithFormat:NSLocalizedString(@"Loading omemo bundles: %@ / %@", @""), notification.userInfo[@"completed"], notification.userInfo[@"all"]]; + }); +} +#endif + -(void) omemoBundleFetchFinished { dispatch_async(dispatch_get_main_queue(), ^{ @@ -143,7 +175,7 @@ -(void) error { dispatch_async(dispatch_get_main_queue(), ^{ self.loginHUD.hidden=YES; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error",@"") message:NSLocalizedString(@"We were not able to connect your account. Please check your credentials and make sure you are connected to the internet.", @"") preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", @"") message:NSLocalizedString(@"We were not able to connect your account. Please check your credentials and make sure you are connected to the internet.", @"") preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [alert dismissViewControllerAnimated:YES completion:nil]; }]]; @@ -155,8 +187,8 @@ -(void) error -(IBAction) useWithoutAccount:(id)sender { - [self dismissViewControllerAnimated:YES completion:nil]; [[HelperTools defaultsDB] setBool:YES forKey:@"HasSeenLogin"]; + [self dismissViewControllerAnimated:YES completion:nil]; } -(IBAction) tapAction:(id)sender @@ -191,7 +223,6 @@ - (void)registerForKeyboardNotifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil]; - } // Called when the UIKeyboardDidShowNotification is sent. @@ -228,13 +259,34 @@ -(void) dealloc -(void) removeObservers { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; [nc removeObserver:self]; } -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - [self removeObservers]; + if([segue.identifier isEqualToString:@"scanQRCode"]) + { + MLQRCodeScanner* qrCodeScanner = (MLQRCodeScanner*)segue.destinationViewController; + qrCodeScanner.loginDelegate = self; + } + else if([segue.identifier isEqualToString:@"showPrivacySettings"]) + { + // nothing todo + } + else + { + [self removeObservers]; + } +} + +-(void) MLQRCodeAccountLoginScannedWithJid:(NSString*) jid password:(NSString*) password +{ + // Insert jid and password into text fields + self.jid.text = jid; + self.password.text = password; + // Close QR-Code scanner + [self.navigationController popViewControllerAnimated:YES]; } diff --git a/Monal/Classes/MLMAMPrefTableViewController.m b/Monal/Classes/MLMAMPrefTableViewController.m index fc98094c34..06177800fe 100644 --- a/Monal/Classes/MLMAMPrefTableViewController.m +++ b/Monal/Classes/MLMAMPrefTableViewController.m @@ -9,21 +9,22 @@ #import "MLMAMPrefTableViewController.h" @interface MLMAMPrefTableViewController () -@property (nonatomic, strong) NSMutableArray *mamPref; -@property (nonatomic, strong) NSString *currentPref; - +@property (nonatomic, strong) NSMutableArray* mamPref; +@property (nonatomic, strong) NSString* currentPref; @end @implementation MLMAMPrefTableViewController -- (void)viewDidLoad { +-(void) viewDidLoad +{ [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePrefs:) name:kMLMAMPref object:nil]; - + [self.xmppAccount getMAMPrefs]; } -- (void)didReceiveMemoryWarning { +-(void) didReceiveMemoryWarning +{ [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @@ -31,80 +32,74 @@ - (void)didReceiveMemoryWarning { -(void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; - self.mamPref =[[NSMutableArray alloc] init]; - self.navigationItem.title= self.xmppAccount.connectionProperties.identity.domain; - [self.mamPref addObject:@{@"Title":NSLocalizedString(@"All messages are archived by default.",@""), @"value":@"always"}]; - [self.mamPref addObject:@{@"Title":NSLocalizedString(@"Messages never archived by default.",@""), @"value":@"never"}]; - [self.mamPref addObject:@{@"Title":NSLocalizedString(@"Archive only if the contact is in roster",@""), @"value":@"roster"}]; - - [self.xmppAccount getMAMPrefs]; + self.navigationItem.title = self.xmppAccount.connectionProperties.identity.domain; + self.mamPref = [[NSMutableArray alloc] init]; + [self.mamPref addObject:@{@"Title":NSLocalizedString(@"Always archive", @""), @"Description":NSLocalizedString(@"All messages are archived by default.", @""), @"value":@"always"}]; + [self.mamPref addObject:@{@"Title":NSLocalizedString(@"Never archive", @""), @"Description":NSLocalizedString(@"Messages never archived by default.", @""), @"value":@"never"}]; + [self.mamPref addObject:@{@"Title":NSLocalizedString(@"Only contacts", @""), @"Description":NSLocalizedString(@"Archive only if the contact is in contact list", @""), @"value":@"roster"}]; } -(void) updatePrefs:(NSNotification *) notification { dispatch_async(dispatch_get_main_queue(), ^{ - NSDictionary *dic= (NSDictionary *) notification.object; - self.currentPref =[dic objectForKey:@"mamPref"]; + NSDictionary* dic = (NSDictionary*)notification.userInfo; + self.currentPref = [dic objectForKey:@"mamPref"]; [self.tableView reloadData]; }); } #pragma mark - Table view data source -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { +-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView +{ return 1; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { +-(NSInteger) tableView:(UITableView*) tableView numberOfRowsInSection:(NSInteger) section +{ return self.mamPref.count; } -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"serverCell" forIndexPath:indexPath]; - NSDictionary *dic = [self.mamPref objectAtIndex:indexPath.row]; - cell.textLabel.text= [dic objectForKey:@"Title"]; - cell.detailTextLabel.text= [dic objectForKey:@"Description"]; - - if([[dic objectForKey:@"value"] isEqualToString:self.currentPref]) - { - cell.accessoryType=UITableViewCellAccessoryCheckmark; - } else { - cell.accessoryType=UITableViewCellAccessoryNone; - } +-(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath +{ + UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"serverCell" forIndexPath:indexPath]; + NSDictionary* dic = [self.mamPref objectAtIndex:indexPath.row]; + cell.textLabel.text = dic[@"Title"]; + cell.detailTextLabel.text = dic[@"Description"]; + if([dic[@"value"] isEqualToString:self.currentPref]) + cell.accessoryType = UITableViewCellAccessoryCheckmark; + else + cell.accessoryType = UITableViewCellAccessoryNone; return cell; } --(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +-(void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; switch(indexPath.row) { - case 0:{ - self.currentPref=@"always"; + case 0: + self.currentPref = @"always"; break; - } - case 1:{ - self.currentPref=@"never"; + case 1: + self.currentPref = @"never"; break; - } - case 2:{ - self.currentPref=@"roster"; + case 2: + self.currentPref = @"roster"; break; - } } [self.xmppAccount setMAMPrefs:self.currentPref]; - [self.tableView reloadData]; } --(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +-(NSString*) tableView:(UITableView*) tableView titleForHeaderInSection:(NSInteger) section { - return NSLocalizedString(@"Select Message Archive Management (MAM) Preferences ",@""); + return NSLocalizedString(@"Select Message Archive Management (MAM) Preferences ", @""); } @end diff --git a/Monal/Classes/MLMessage.h b/Monal/Classes/MLMessage.h index f90950650f..9dc366535a 100644 --- a/Monal/Classes/MLMessage.h +++ b/Monal/Classes/MLMessage.h @@ -44,6 +44,8 @@ The of the message in the DB , should be int @property (nonatomic, copy) NSString *to; @property (nonatomic, copy) NSString* messageType; +@property (nonatomic, copy) NSString* filetransferMimeType; +@property (nonatomic, copy) NSNumber* filetransferSize; @property (nonatomic, copy) NSString *messageText; @@ -85,9 +87,6 @@ The of the message in the DB , should be int @property (nonatomic, assign) BOOL hasBeenDisplayed; - -@property (nonatomic, assign) BOOL shouldShowAlert; - /** values only set if in a response the message was marked as error. if hasBeenReceived is true, these should be ignored @@ -107,6 +106,8 @@ The of the message in the DB , should be int */ +(MLMessage *) messageFromDictionary:(NSDictionary *) dic withDateFormatter:(NSDateFormatter *) formatter; +-(void) updateWithMessage:(MLMessage*) msg; + @end NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/MLMessage.m b/Monal/Classes/MLMessage.m index 2786d41418..f4d14e114e 100644 --- a/Monal/Classes/MLMessage.m +++ b/Monal/Classes/MLMessage.m @@ -10,9 +10,9 @@ @implementation MLMessage -+(MLMessage *) messageFromDictionary:(NSDictionary *) dic withDateFormatter:(NSDateFormatter *) formatter ++(MLMessage*) messageFromDictionary:(NSDictionary*) dic withDateFormatter:(NSDateFormatter*) formatter { - MLMessage *message = [[MLMessage alloc] init]; + MLMessage* message = [[MLMessage alloc] init]; message.accountId = [NSString stringWithFormat:@"%@", [dic objectForKey:@"account_id"]]; message.from = [dic objectForKey:@"message_from"]; @@ -41,7 +41,37 @@ +(MLMessage *) messageFromDictionary:(NSDictionary *) dic withDateFormatter:(NSD message.errorType = [dic objectForKey:@"errorType"]; message.errorReason = [dic objectForKey:@"errorReason"]; + message.filetransferMimeType = [dic objectForKey:@"filetransferMimeType"]; + message.filetransferSize = [dic objectForKey:@"filetransferSize"]; + return message; } +-(void) updateWithMessage:(MLMessage*) msg +{ + self.accountId = msg.accountId; + self.from = msg.from; + self.actualFrom = msg.actualFrom; + self.messageText = msg.messageText; + self.to = msg.to; + self.messageId = msg.messageId; + self.stanzaId = msg.stanzaId; + self.messageDBId = msg.messageDBId; + self.timestamp = msg.timestamp; + self.messageType = msg.messageType; + self.hasBeenDisplayed = msg.hasBeenDisplayed; + self.hasBeenReceived = msg.hasBeenReceived; + self.hasBeenSent = msg.hasBeenSent; + self.encrypted = msg.encrypted; + self.unread = msg.unread; + self.displayMarkerWanted = msg.displayMarkerWanted; + self.previewText = msg.previewText; + self.previewImage = msg.previewImage; + self.errorType = msg.errorType; + self.errorReason = msg.errorReason; + self.filetransferMimeType = msg.filetransferMimeType; + self.filetransferSize = msg.filetransferSize; +} + + @end diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index b6ca52309c..85aae92dfc 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -16,6 +16,7 @@ #import "XMPPIQ.h" #import "MLPubSub.h" #import "MLOMEMO.h" +#import "MLFiletransfer.h" @interface MLPubSub () -(void) handleHeadlineMessage:(XMPPMessage*) messageNode; @@ -83,7 +84,10 @@ +(void) processMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) stanzaid = [messageNode findFirst:@"{urn:xmpp:sid:0}stanza-id@id"]; if([messageNode check:@"{http://jabber.org/protocol/muc#user}x/invite"]) + { [[NSNotificationCenter defaultCenter] postNotificationName:kMonalReceivedMucInviteNotice object:nil userInfo:@{@"from": messageNode.from}]; + return; + } NSString* decrypted; if([messageNode check:@"/{jabber:client}message/{eu.siacs.conversations.axolotl}encrypted/header"]) @@ -131,13 +135,10 @@ +(void) processMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) unread = NO; } - NSString* messageType = nil; + NSString* messageType = kMessageTypeText; BOOL encrypted = NO; NSString* body = [messageNode findFirst:@"body#"]; - if(body && [body isEqualToString:[messageNode findFirst:@"{jabber:x:oob}x/url#"]]) - messageType = kMessageTypeImage; - if(decrypted) { body = decrypted; @@ -146,21 +147,40 @@ +(void) processMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) if(!body && [messageNode check:@"//subject#"]) { - messageType = kMessageTypeStatus; - + NSString* subject = [messageNode findFirst:@"//subject#"]; NSString* currentSubject = [[DataLayer sharedInstance] mucSubjectforAccount:account.accountNo andRoom:messageNode.fromUser]; - if(![[messageNode findFirst:@"//subject#"] isEqualToString:currentSubject]) - { - [[DataLayer sharedInstance] updateMucSubject:[messageNode findFirst:@"//subject#"] forAccount:account.accountNo andRoom:messageNode.fromUser]; - [self postPersistActionForAccount:account andMessage:messageNode andOuterMessage:outerMessageNode andSuccess:YES andEncrypted:encrypted andShowAlert:showAlert andBody:[messageNode findFirst:@"//subject#"] andMessageType:messageType andActualFrom:actualFrom andHistoryId:nil]; - } + if(!subject || [subject isEqualToString:currentSubject]) + return; + + [[DataLayer sharedInstance] updateMucSubject:subject forAccount:account.accountNo andRoom:messageNode.fromUser]; + //TODO: this stuff has to be changed (why send a kMonalNewMessageNotice instead of a special kMonalMucSubjectChanged one?) + MLMessage* message = [account parseMessageToMLMessage:messageNode withBody:subject andEncrypted:encrypted andMessageType:kMessageTypeStatus andActualFrom:actualFrom]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalNewMessageNotice object:account userInfo:@{ + @"message": message, + @"subject": subject, + }]; return; } + //messages with oob tag are filetransfers (but only if they are https urls) + NSString* lowercaseBody = [body lowercaseString]; + if(body && [body isEqualToString:[messageNode findFirst:@"{jabber:x:oob}x/url#"]] && [lowercaseBody hasPrefix:@"https://"]) + messageType = kMessageTypeFiletransfer; + //messages without spaces are potentially special ones + else if([body rangeOfString:@" "].location == NSNotFound) + { + if([lowercaseBody hasPrefix:@"geo:"]) + messageType = kMessageTypeGeo; + //encrypted messages having one single string prefixed with "aesgcm:" are filetransfers, too (tribal knowledge) + else if(encrypted && [lowercaseBody hasPrefix:@"aesgcm://"]) + messageType = kMessageTypeFiletransfer; + } + DDLogInfo(@"Got message of type: %@", messageType); + NSString* messageId = [messageNode findFirst:@"/@id"]; if(!messageId.length) { - DDLogError(@"Empty ID using random UUID"); + DDLogWarn(@"Empty ID using random UUID"); messageId = [[NSUUID UUID] UUIDString]; } @@ -168,45 +188,123 @@ +(void) processMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) //because mam always sorts the messages in a page by timestamp in ascending order //we don't want to call postPersistAction, too, beause we don't want to display push notifications for old messages if([outerMessageNode check:@"{urn:xmpp:mam:2}result"] && [[outerMessageNode findFirst:@"{urn:xmpp:mam:2}result@queryid"] hasPrefix:@"MLhistory:"]) - [account addMessageToMamPageArray:messageNode forOuterMessageNode:outerMessageNode withBody:body andEncrypted:encrypted andShowAlert:showAlert andMessageType:messageType]; - else { - [[DataLayer sharedInstance] addMessageFrom:messageNode.fromUser - to:messageNode.toUser - forAccount:account.accountNo - withBody:[body copy] - actuallyfrom:actualFrom - sent:YES - unread:unread - messageId:messageId - serverMessageId:stanzaid - messageType:messageType - andOverrideDate:[messageNode findFirst:@"{urn:xmpp:delay}delay@stamp|datetime"] - encrypted:encrypted - backwards:NO - displayMarkerWanted:[messageNode check:@"{urn:xmpp:chat-markers:0}markable"] - withCompletion:^(BOOL success, NSString* newMessageType, NSNumber* historyId) { - [self postPersistActionForAccount:account andMessage:messageNode andOuterMessage:outerMessageNode andSuccess:success andEncrypted:encrypted andShowAlert:showAlert andBody:body andMessageType:newMessageType andActualFrom:actualFrom andHistoryId:historyId]; - }]; + DDLogInfo(@"Adding message to mam page array to be inserted into history later on"); + [account addMessageToMamPageArray:messageNode forOuterMessageNode:outerMessageNode withBody:body andEncrypted:encrypted andMessageType:messageType]; + return; + } + + if(body) + { + NSNumber* historyId = nil; - //check if we have an outgoing message sent from another client on our account - //if true we can mark all messages from this buddy as already read by us (using the other client) - //WARNING: kMonalMessageDisplayedNotice goes to chatViewController, kMonalDisplayedMessageNotice goes to MLNotificationManager and activeChatsViewController/chatViewController - //e.g.: kMonalMessageDisplayedNotice means "remote party read our message" and kMonalDisplayedMessageNotice means "we read a message" - if(body && stanzaid && ![messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) + //handle LMC + BOOL deleteMessage = NO; + if([messageNode check:@"{urn:xmpp:message-correct:0}replace"]) { - DDLogInfo(@"Got outgoing message to contact '%@' sent by another client, removing all notifications for unread messages of this contact", messageNode.toUser); - NSArray* unread = [[DataLayer sharedInstance] markMessagesAsReadForBuddy:messageNode.toUser andAccount:account.accountNo tillStanzaId:stanzaid wasOutgoing:NO]; - DDLogDebug(@"Marked as read: %@", unread); - - //remove notifications of all remotely read messages (indicated by sending a response message) - for(MLMessage* msg in unread) - [[NSNotificationCenter defaultCenter] postNotificationName:kMonalDisplayedMessageNotice object:account userInfo:@{@"message":msg}]; + NSString* messageIdToReplace = [messageNode findFirst:@"{urn:xmpp:message-correct:0}replace@id"]; + historyId = [[DataLayer sharedInstance] getHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser andAccount:account.accountNo]; + if([[DataLayer sharedInstance] checkLMCEligible:historyId from:messageNode.fromUser]) + { + if(![body isEqualToString:kMessageDeletedBody]) + [[DataLayer sharedInstance] updateMessageHistory:historyId withText:body]; + else + deleteMessage = YES; + } + else + historyId = nil; + } + + //handle normal messages or LMC messages that can not be found (but ignore deletion LMCs) + if(historyId == nil && ![body isEqualToString:kMessageDeletedBody]) + { + historyId = [[DataLayer sharedInstance] + addMessageFrom:messageNode.fromUser + to:messageNode.toUser + forAccount:account.accountNo + withBody:[body copy] + actuallyfrom:actualFrom + sent:YES + unread:unread + messageId:messageId + serverMessageId:stanzaid + messageType:messageType + andOverrideDate:[messageNode findFirst:@"{urn:xmpp:delay}delay@stamp|datetime"] + encrypted:encrypted + backwards:NO + displayMarkerWanted:[messageNode check:@"{urn:xmpp:chat-markers:0}markable"] + ]; + } + + MLMessage* message = [[DataLayer sharedInstance] messageForHistoryID:historyId]; + if(message != nil && historyId != nil) //check historyId to make static analyzer happy + { + if( + [[HelperTools defaultsDB] boolForKey:@"SendReceivedMarkers"] && + ([messageNode check:@"{urn:xmpp:receipts}request"] || [messageNode check:@"{urn:xmpp:chat-markers:0}markable"]) && + ![messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid] + ) + { + XMPPMessage* receiptNode = [[XMPPMessage alloc] init]; + //the message type is needed so that the store hint is accepted by the server --> mirror the incoming type + receiptNode.attributes[@"type"] = [messageNode findFirst:@"/@type"]; + receiptNode.attributes[@"to"] = messageNode.fromUser; + if([messageNode check:@"{urn:xmpp:receipts}request"]) + [receiptNode setReceipt:[messageNode findFirst:@"/@id"]]; + if([messageNode check:@"{urn:xmpp:chat-markers:0}markable"]) + [receiptNode setChatmarkerReceipt:[messageNode findFirst:@"/@id"]]; + [receiptNode setStoreHint]; + [account send:receiptNode]; + } + + //check if we have an outgoing message sent from another client on our account + //if true we can mark all messages from this buddy as already read by us (using the other client) + //WARNING: kMonalMessageDisplayedNotice goes to chatViewController, kMonalDisplayedMessageNotice goes to MLNotificationManager and activeChatsViewController/chatViewController + //e.g.: kMonalMessageDisplayedNotice means "remote party read our message" and kMonalDisplayedMessageNotice means "we read a message" + if(body && stanzaid && ![messageNode.toUser isEqualToString:account.connectionProperties.identity.jid]) + { + DDLogInfo(@"Got outgoing message to contact '%@' sent by another client, removing all notifications for unread messages of this contact", messageNode.toUser); + NSArray* unread = [[DataLayer sharedInstance] markMessagesAsReadForBuddy:messageNode.toUser andAccount:account.accountNo tillStanzaId:stanzaid wasOutgoing:NO]; + DDLogDebug(@"Marked as read: %@", unread); + + //remove notifications of all remotely read messages (indicated by sending a response message) + for(MLMessage* msg in unread) + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalDisplayedMessageNotice object:account userInfo:@{@"message":msg}]; + + //update unread count in active chats list + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalContactRefresh object:account userInfo:@{ + @"contact": [[DataLayer sharedInstance] contactForUsername:messageNode.toUser forAccount:account.accountNo] + }]; + } - //update unread count in active chats list - [[NSNotificationCenter defaultCenter] postNotificationName:kMonalContactRefresh object:account userInfo:@{ - @"contact": [[DataLayer sharedInstance] contactForUsername:messageNode.toUser forAccount:account.accountNo] - }]; + if(deleteMessage) + { + [[DataLayer sharedInstance] deleteMessageHistory:historyId]; + + DDLogInfo(@"Sending out kMonalDeletedMessageNotice notification for historyId %@", historyId); + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalDeletedMessageNotice object:account userInfo:@{ + @"message": message, + }]; + } + else + { + if(![messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid]) + [[DataLayer sharedInstance] addActiveBuddies:messageNode.fromUser forAccount:account.accountNo]; + else + [[DataLayer sharedInstance] addActiveBuddies:messageNode.toUser forAccount:account.accountNo]; + + DDLogInfo(@"Sending out kMonalNewMessageNotice notification for historyId %@", historyId); + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalNewMessageNotice object:account userInfo:@{ + @"message": message, + @"historyId": historyId, + @"showAlert": @(showAlert), + }]; + + //try to automatically determine content type of filetransfers + //TODO JIM: this should be the config key to enable/disable auto downloads + if(messageType == kMessageTypeFiletransfer && [[HelperTools defaultsDB] boolForKey:@"AutodownloadFiletransfers"]) + [MLFiletransfer checkMimeTypeAndSizeForHistoryID:historyId]; + } } } } @@ -230,7 +328,7 @@ +(void) processMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) [[DataLayer sharedInstance] setMessageId:id received:YES]; } //Post notice - [[NSNotificationCenter defaultCenter] postNotificationName:kMonalMessageReceivedNotice object:self userInfo:@{@"MessageID": id}]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalMessageReceivedNotice object:self userInfo:@{kMessageId:id}]; } //incoming chat markers from contact @@ -325,42 +423,4 @@ +(void) processMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) } } -+(void) postPersistActionForAccount:(xmpp*) account andMessage:(XMPPMessage*) messageNode andOuterMessage:(XMPPMessage*) outerMessageNode andSuccess:(BOOL) success andEncrypted:(BOOL) encrypted andShowAlert:(BOOL) showAlert andBody:(NSString*) body andMessageType:(NSString*) newMessageType andActualFrom:(NSString*) actualFrom andHistoryId:(NSNumber*) historyId -{ - if(success) - { - if( - [[HelperTools defaultsDB] boolForKey:@"SendReceivedMarkers"] && - ([messageNode check:@"{urn:xmpp:receipts}request"] || [messageNode check:@"{urn:xmpp:chat-markers:0}markable"]) && - ![messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid] - ) - { - XMPPMessage* receiptNode = [[XMPPMessage alloc] init]; - //the message type is needed so that the store hint is accepted by the server --> mirror the incoming type - receiptNode.attributes[@"type"] = [messageNode findFirst:@"/@type"]; - receiptNode.attributes[@"to"] = messageNode.fromUser; - if([messageNode check:@"{urn:xmpp:receipts}request"]) - [receiptNode setReceipt:[messageNode findFirst:@"/@id"]]; - if([messageNode check:@"{urn:xmpp:chat-markers:0}markable"]) - [receiptNode setChatmarkerReceipt:[messageNode findFirst:@"/@id"]]; - [receiptNode setStoreHint]; - [account send:receiptNode]; - } - - if(![messageNode.fromUser isEqualToString:account.connectionProperties.identity.jid]) - [[DataLayer sharedInstance] addActiveBuddies:messageNode.fromUser forAccount:account.accountNo]; - else - [[DataLayer sharedInstance] addActiveBuddies:messageNode.toUser forAccount:account.accountNo]; - - DDLogInfo(@"sending out kMonalNewMessageNotice notification"); - MLMessage* message = [account parseMessageToMLMessage:messageNode withBody:body andEncrypted:encrypted andShowAlert:showAlert andMessageType:newMessageType andActualFrom:actualFrom]; - if(historyId) - [[NSNotificationCenter defaultCenter] postNotificationName:kMonalNewMessageNotice object:account userInfo:@{@"message":message, @"historyId":historyId}]; - else - [[NSNotificationCenter defaultCenter] postNotificationName:kMonalNewMessageNotice object:account userInfo:@{@"message":message}]; - } - else - DDLogError(@"error adding message"); -} - @end diff --git a/Monal/Classes/MLNewViewController.m b/Monal/Classes/MLNewViewController.m index 7f625bef0a..69399b560f 100644 --- a/Monal/Classes/MLNewViewController.m +++ b/Monal/Classes/MLNewViewController.m @@ -53,7 +53,7 @@ -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender }; } else if([segue.identifier isEqualToString:@"acceptContact"]) { - MLSubscriptionTableViewController* newScreen = (MLSubscriptionTableViewController *)segue.destinationViewController; + //MLSubscriptionTableViewController* newScreen = (MLSubscriptionTableViewController *)segue.destinationViewController; } } diff --git a/Monal/Classes/MLNotificationManager.m b/Monal/Classes/MLNotificationManager.m index 86842f3b91..46662c0f43 100644 --- a/Monal/Classes/MLNotificationManager.m +++ b/Monal/Classes/MLNotificationManager.m @@ -6,13 +6,15 @@ // // -#import "MonalAppDelegate.h" #import "HelperTools.h" #import "MLNotificationManager.h" #import "MLImageManager.h" #import "MLMessage.h" #import "MLXEPSlashMeHandler.h" #import "MLConstants.h" +#import "xmpp.h" +#import "MLFiletransfer.h" + @import UserNotifications; @import CoreServices; @@ -36,29 +38,80 @@ -(id) init { self = [super init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNewMessage:) name:kMonalNewMessageNotice object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleFiletransferUpdate:) name:kMonalMessageFiletransferUpdateNotice object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDeletedMessage:) name:kMonalDeletedMessageNotice object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleDisplayedMessage:) name:kMonalDisplayedMessageNotice object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleXMPPError:) name:kXMPPError object:nil]; self.notificationPrivacySetting = (NotificationPrivacySettingOption)[[HelperTools defaultsDB] integerForKey:@"NotificationPrivacySetting"]; return self; } +-(void) handleXMPPError:(NSNotification*) notification +{ + //severe errors will be shown as notification (in addition to the banner shown if the app is in foreground) + if([notification.userInfo[@"isSevere"] boolValue]) + { + xmpp* xmppAccount = notification.object; + DDLogError(@"SEVERE XMPP Error(%@): %@", xmppAccount.connectionProperties.identity.jid, notification.userInfo[@"message"]); + NSString* idval = xmppAccount.connectionProperties.identity.jid; //use this to only show the newest error notification per account + UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; + content.title = xmppAccount.connectionProperties.identity.jid; + content.body = notification.userInfo[@"message"]; + content.sound = [UNNotificationSound defaultSound]; + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:idval content:content trigger:nil]; + [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { + if(error) + DDLogError(@"Error posting xmppError notification: %@", error); + }]; + } +} + #pragma mark message signals --(void) handleNewMessage:(NSNotification*) notification +-(void) handleFiletransferUpdate:(NSNotification*) notification { MLMessage* message = [notification.userInfo objectForKey:@"message"]; + NSString* idval = [self identifierWithMessage:message]; + //check if we already show any notifications and update them if necessary (e.g. publish a second notification having the same id) + [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:^(NSArray* requests) { + for(UNNotificationRequest* request in requests) + if([request.identifier isEqualToString:idval]) + { + [self internalMessageHandlerWithMessage:message showAlert:YES andSound:YES]; + } + }]; + [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:^(NSArray* notifications) { + for(UNNotification* notification in notifications) + if([notification.request.identifier isEqualToString:idval]) + { + [self internalMessageHandlerWithMessage:message showAlert:YES andSound:NO]; + } + }]; +} + +-(void) handleNewMessage:(NSNotification*) notification +{ + MLMessage* message = [notification.userInfo objectForKey:@"message"]; + BOOL showAlert = notification.userInfo[@"showAlert"] ? [notification.userInfo[@"showAlert"] boolValue] : NO; + [self internalMessageHandlerWithMessage:message showAlert:showAlert andSound:YES]; +} + +-(void) internalMessageHandlerWithMessage:(MLMessage*) message showAlert:(BOOL) showAlert andSound:(BOOL) sound +{ if([message.messageType isEqualToString:kMessageTypeStatus]) return; - DDLogVerbose(@"notification manager got new message notice: %@", message.messageText); + DDLogVerbose(@"notification manager should show notification for: %@", message.messageText); BOOL muted = [[DataLayer sharedInstance] isMutedJid:message.actualFrom]; - if(!muted && message.shouldShowAlert) + if(!muted && showAlert) { if([HelperTools isInBackground]) { - DDLogVerbose(@"notification manager got new message notice in background: %@", message.messageText); - [self showModernNotificaion:notification]; + DDLogVerbose(@"notification manager should show notification in background: %@", message.messageText); + [self showModernNotificaionForMessage:message withSound:sound]; } else { @@ -67,9 +120,13 @@ -(void) handleNewMessage:(NSNotification*) notification ![message.from isEqualToString:self.currentContact.contactJid] && ![message.to isEqualToString:self.currentContact.contactJid] ) - [self showModernNotificaion:notification]; + [self showModernNotificaionForMessage:message withSound:sound]; + else + DDLogDebug(@"not showing notification: chat is open"); } } + else + DDLogDebug(@"not showing notification: showAlert is NO (or this contact got muted)"); } -(void) handleDisplayedMessage:(NSNotification*) notification @@ -87,10 +144,25 @@ -(void) handleDisplayedMessage:(NSNotification*) notification [center removeDeliveredNotificationsWithIdentifiers:@[idval]]; //update app badge - dispatch_async(dispatch_get_main_queue(), ^{ - MonalAppDelegate* appDelegate = (MonalAppDelegate*) [UIApplication sharedApplication].delegate; - [appDelegate updateUnread]; - }); + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalUpdateUnread object:nil]; +} + +-(void) handleDeletedMessage:(NSNotification*) notification +{ + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + MLMessage* message = [notification.userInfo objectForKey:@"message"]; + + if([message.messageType isEqualToString:kMessageTypeStatus]) + return; + + DDLogVerbose(@"notification manager got deleted message notice: %@", message.messageId); + NSString* idval = [self identifierWithMessage:message]; + + [center removePendingNotificationRequestsWithIdentifiers:@[idval]]; + [center removeDeliveredNotificationsWithIdentifiers:@[idval]]; + + //update app badge + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalUpdateUnread object:nil]; } -(NSString*) identifierWithMessage:(MLMessage*) message @@ -110,40 +182,39 @@ -(void) publishNotificationContent:(UNMutableNotificationContent*) content withI //this will add a badge having a minimum of 1 to make sure people see that something happened (even after swiping away all notifications) NSNumber* unreadMsgCnt = [[DataLayer sharedInstance] countUnreadMessages]; NSInteger unread = 0; - if(unreadMsgCnt) + if(unreadMsgCnt != nil) unread = [unreadMsgCnt integerValue]; DDLogVerbose(@"Raw badge value: %lu", (long)unread); if(!unread) unread = 1; //use this as fallback to always show a badge if a notification is shown - DDLogInfo(@"Adding badge value: %lu", (long)unread); + DDLogDebug(@"Adding badge value: %lu", (long)unread); content.badge = [NSNumber numberWithInteger:unread]; - DDLogVerbose(@"notification manager: publishing notification: %@", content.body); - UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:idval content:content trigger:nil]; + //scheduling the notification in 1.5 seconds will make it possible to be deleted by XEP-0333 chat-markers received directly after the message + //this is useful in catchup scenarios + DDLogVerbose(@"notification manager: publishing notification in 1.5 seconds: %@", content.body); + UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:idval content:content trigger:[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1.5 repeats: NO]]; [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { if(error) DDLogError(@"Error posting local notification: %@", error); }]; } --(void) showModernNotificaion:(NSNotification*) notification +-(void) showModernNotificaionForMessage:(MLMessage*) message withSound:(BOOL) sound { - MLMessage* message = [notification.userInfo objectForKey:@"message"]; UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; MLContact* contact = [[DataLayer sharedInstance] contactForUsername:message.from forAccount:message.accountId]; // Only show contact name if allowed - if(self.notificationPrivacySetting <= DisplayOnlyName) { + if(self.notificationPrivacySetting <= DisplayOnlyName) + { content.title = [contact contactDisplayName]; - if(![message.from isEqualToString:message.actualFrom]) - { content.subtitle = [NSString stringWithFormat:@"%@ says:", message.actualFrom]; - } - } else { - content.title = NSLocalizedString(@"New Message", @""); } + else + content.title = NSLocalizedString(@"New Message", @""); NSString* idval = [self identifierWithMessage:message]; // only show msgText if allowed @@ -171,7 +242,7 @@ -(void) showModernNotificaion:(NSNotification*) notification @"messageId": message.messageId }; - if([[HelperTools defaultsDB] boolForKey:@"Sound"]) + if(sound && [[HelperTools defaultsDB] boolForKey:@"Sound"]) { NSString* filename = [[HelperTools defaultsDB] objectForKey:@"AlertSoundFile"]; if(filename) @@ -180,35 +251,50 @@ -(void) showModernNotificaion:(NSNotification*) notification content.sound = [UNNotificationSound defaultSound]; } - if([message.messageType isEqualToString:kMessageTypeImage]) + if([message.messageType isEqualToString:kMessageTypeFiletransfer]) { - [[MLImageManager sharedInstance] imageURLForAttachmentLink:message.messageText withCompletion:^(NSURL * _Nullable url) { - if(url) + NSDictionary* info = [MLFiletransfer getFileInfoForMessage:message]; + if(info && [info[@"mimeType"] hasPrefix:@"image/"]) + { + UNNotificationAttachment* attachment; + if(![info[@"needsDownloading"] boolValue]) { + NSString* typeHint = (NSString*)kUTTypePNG; + if([info[@"mimeType"] isEqualToString:@"image/jpeg"]) + typeHint = (NSString*)kUTTypeJPEG; + if([info[@"mimeType"] isEqualToString:@"image/png"]) + typeHint = (NSString*)kUTTypePNG; + if([info[@"mimeType"] isEqualToString:@"image/png"]) + typeHint = (NSString*)kUTTypeGIF; NSError *error; - UNNotificationAttachment* attachment = [UNNotificationAttachment attachmentWithIdentifier:[[NSUUID UUID] UUIDString] URL:url options:@{UNNotificationAttachmentOptionsTypeHintKey:(NSString*) kUTTypePNG} error:&error]; - if(attachment) - content.attachments = @[attachment]; + attachment = [UNNotificationAttachment attachmentWithIdentifier:info[@"cacheId"] URL:[NSURL fileURLWithPath:info[@"cacheFile"]] options:@{UNNotificationAttachmentOptionsTypeHintKey:typeHint} error:&error]; if(error) DDLogError(@"Error %@", error); } - - if(!content.attachments) - content.body = NSLocalizedString(@"Sent an Image πŸ“·", @""); - else + if(attachment) + { + content.attachments = @[attachment]; content.body = @""; + } + else + content.body = NSLocalizedString(@"Sent an Image πŸ“·", @""); + DDLogDebug(@"Publishing notification with id %@", idval); [self publishNotificationContent:content withID:idval]; - }]; - return; + return; + } + else //TODO JIM: add support for more mime types + content.body = NSLocalizedString(@"Sent a File πŸ“", @""); } else if([message.messageType isEqualToString:kMessageTypeUrl]) content.body = NSLocalizedString(@"Sent a Link πŸ”—", @""); else if([message.messageType isEqualToString:kMessageTypeGeo]) - content.body = NSLocalizedString(@"Sent a location πŸ“", @""); - } else { - content.body = NSLocalizedString(@"Open app to see more", @""); + content.body = NSLocalizedString(@"Sent a Location πŸ“", @""); } + else + content.body = NSLocalizedString(@"Open app to see more", @""); + + DDLogDebug(@"Publishing notification with id %@", idval); [self publishNotificationContent:content withID:idval]; } diff --git a/Monal/Classes/MLNotificationSettingsViewController.m b/Monal/Classes/MLNotificationSettingsViewController.m index ced9de3eab..ecc3558b13 100644 --- a/Monal/Classes/MLNotificationSettingsViewController.m +++ b/Monal/Classes/MLNotificationSettingsViewController.m @@ -9,6 +9,7 @@ #import "MLNotificationSettingsViewController.h" #import "MLSwitchCell.h" #import "MLXMPPManager.h" +#import "xmpp.h" #import "MLPush.h" #import "DataLayer.h" diff --git a/Monal/Classes/MLOMEMO.h b/Monal/Classes/MLOMEMO.h index 573e87d199..eb3e4cf300 100644 --- a/Monal/Classes/MLOMEMO.h +++ b/Monal/Classes/MLOMEMO.h @@ -7,22 +7,22 @@ // #import -#import "MLXMPPConnection.h" -#import "xmpp.h" -#import "XMPPMessage.h" -#import "MLSignalStore.h" NS_ASSUME_NONNULL_BEGIN +@class MLSignalStore; +@class SignalAddress; @class xmpp; +@class XMPPMessage; @class XMPPIQ; @interface MLOMEMO : NSObject { NSLock* signalLock; } @property (nonatomic, strong) MLSignalStore* monalSignalStore; -@property (nonatomic, assign) NSNumber* hasCatchUpDone; -@property (nonatomic, strong) NSNumber* openBundleFetchCnt; +@property (nonatomic, assign) BOOL hasCatchUpDone; +@property (nonatomic) unsigned long openBundleFetchCnt; +@property (nonatomic) unsigned long closedBundleFetchCnt; -(MLOMEMO*) initWithAccount:(xmpp*) account; @@ -30,16 +30,16 @@ NS_ASSUME_NONNULL_BEGIN * handle omemo iq's */ -(void) sendOMEMOBundle; --(void) queryOMEMOBundleFrom:(NSString *) jid andDevice:(NSString *) deviceid; +-(void) queryOMEMOBundleFrom:(NSString*) jid andDevice:(NSString*) deviceid; -(void) sendOMEMODeviceWithForce:(BOOL) force; --(void) sendOMEMODevice:(NSSet *) receivedDevices force:(BOOL) force; --(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString *) source; +-(void) sendOMEMODevice:(NSSet*) receivedDevices force:(BOOL) force; +-(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString*) source; /* * encrypting / decrypting messages */ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString* _Nullable) message toContact:(NSString*) toContact; --(NSString *) decryptMessage:(XMPPMessage *) messageNode; +-(NSString* _Nullable) decryptMessage:(XMPPMessage*) messageNode; -(void) sendKeyTransportElementIfNeeded:(NSString*) jid removeBrokenSessionForRid:(NSString*) rid; @@ -49,6 +49,7 @@ NS_ASSUME_NONNULL_BEGIN -(BOOL) isTrustedIdentity:(SignalAddress*)address identityKey:(NSData*)identityKey; -(void) updateTrust:(BOOL) trust forAddress:(SignalAddress*)address; -(NSData *) getIdentityForAddress:(SignalAddress*)address; +-(void) sendLocalDevicesIfNeeded; @end diff --git a/Monal/Classes/MLOMEMO.m b/Monal/Classes/MLOMEMO.m index 5b390ee85c..b68d08b2f0 100644 --- a/Monal/Classes/MLOMEMO.m +++ b/Monal/Classes/MLOMEMO.m @@ -7,7 +7,10 @@ // #import "MLOMEMO.h" +#import "MLXMPPConnection.h" #import "MLHandler.h" +#import "xmpp.h" +#import "XMPPMessage.h" #import "SignalAddress.h" #import "MLSignalStore.h" #import "SignalContext.h" @@ -18,18 +21,15 @@ #import "MLPubSub.h" #import "DataLayer.h" - @interface MLOMEMO () -@property (atomic, strong) SignalContext* _signalContext; +@property (atomic, strong) SignalContext* signalContext; -// TODO: rename senderJID to accountJid -@property (nonatomic, strong) NSString* _senderJid; -@property (nonatomic, strong) NSString* _accountNo; -@property (nonatomic, strong) MLXMPPConnection* _connection; +@property (nonatomic, strong) NSString* accountJid; @property (nonatomic, strong) xmpp* account; -@property (nonatomic, assign) BOOL deviceListExists; +@property (nonatomic, strong) NSMutableSet* ownReceivedDeviceList; +@property (nonatomic, assign) BOOL loggedIn; // jid -> @[deviceID1, deviceID2] @property (nonatomic, strong) NSMutableDictionary* devicesWithBrokenSession; @@ -45,13 +45,13 @@ @implementation MLOMEMO -(MLOMEMO*) initWithAccount:(xmpp*) account; { self = [super init]; - self._senderJid = account.connectionProperties.identity.jid; - self._accountNo = account.accountNo; - self._connection = account.connectionProperties; + self.accountJid = account.connectionProperties.identity.jid; self.account = account; - self.deviceListExists = YES; - self.hasCatchUpDone = [NSNumber numberWithInt:0]; - self.openBundleFetchCnt = [NSNumber numberWithInt:0]; + self.ownReceivedDeviceList = [[NSMutableSet alloc] init]; + self.loggedIn = NO; + self.hasCatchUpDone = NO; + self.openBundleFetchCnt = 0; + self.closedBundleFetchCnt = 0; self.devicesWithBrokenSession = [[NSMutableDictionary alloc] init]; @@ -68,8 +68,9 @@ -(void) loggedIn:(NSNotification *) notification { if(!dic) return; NSString* accountNo = [dic objectForKey:@"AccountNo"]; if(!accountNo) return; - if([self._accountNo isEqualToString:accountNo]) { - self.deviceListExists = NO; + if([self.account.accountNo isEqualToString:accountNo]) { + self.loggedIn = YES; + // We don't have to clear ownReceivedDeviceList as it would have been cleared by a reconnect } } @@ -77,16 +78,11 @@ -(void) catchupDone:(NSNotification *) notification { xmpp* notiAccount = notification.object; if(!notiAccount) return; - if([self._accountNo isEqualToString:notiAccount.accountNo]) { - self.hasCatchUpDone = [NSNumber numberWithInt:1]; - if(self.deviceListExists == NO) { - // we need to publish a new devicelist if we did not receive our own list after a new connection - [self sendOMEMOBundle]; - [self sendOMEMODeviceWithForce:YES]; - self.deviceListExists = YES; - } - if(self.openBundleFetchCnt.intValue == 0) + if([self.account.accountNo isEqualToString:notiAccount.accountNo]) { + self.hasCatchUpDone = YES; + if(!self.openBundleFetchCnt && self.loggedIn) // check if we have a session were we loggedIn { + [self sendLocalDevicesIfNeeded]; [[NSNotificationCenter defaultCenter] postNotificationName:kMonalFinishedOmemoBundleFetch object:self]; } } @@ -94,32 +90,57 @@ -(void) catchupDone:(NSNotification *) notification { -(void) setupSignal { - self.monalSignalStore = [[MLSignalStore alloc] initWithAccountId:self._accountNo]; + self.monalSignalStore = [[MLSignalStore alloc] initWithAccountId:self.account.accountNo]; // signal store SignalStorage* signalStorage = [[SignalStorage alloc] initWithSignalStore:self.monalSignalStore]; // signal context - self._signalContext = [[SignalContext alloc] initWithStorage:signalStorage]; - // signal helper - SignalKeyHelper* signalHelper = [[SignalKeyHelper alloc] initWithContext:self._signalContext]; + self.signalContext = [[SignalContext alloc] initWithStorage:signalStorage]; // init MLPubSub handler [self.account.pubsub registerForNode:@"eu.siacs.conversations.axolotl.devicelist" withHandler:$newHandler(self, devicelistHandler)]; + [self createLocalIdentiyKeyPairIfNeeded:[[NSSet alloc] init]]; +} + +-(BOOL) createLocalIdentiyKeyPairIfNeeded:(NSSet*) ownDeviceIds +{ if(self.monalSignalStore.deviceid == 0) { - // Generate a new device id - // TODO: check if device id is unique - self.monalSignalStore.deviceid = [signalHelper generateRegistrationId]; + // signal helper + SignalKeyHelper* signalHelper = [[SignalKeyHelper alloc] initWithContext:self.signalContext]; + + do + { + // Generate a new device id + self.monalSignalStore.deviceid = [signalHelper generateRegistrationId]; + } while([ownDeviceIds containsObject:[NSNumber numberWithUnsignedInt:self.monalSignalStore.deviceid]]); // Create identity key pair self.monalSignalStore.identityKeyPair = [signalHelper generateIdentityKeyPair]; self.monalSignalStore.signedPreKey = [signalHelper generateSignedPreKeyWithIdentity:self.monalSignalStore.identityKeyPair signedPreKeyId:1]; + SignalAddress* address = [[SignalAddress alloc] initWithName:self.accountJid deviceId:self.monalSignalStore.deviceid]; + [self.monalSignalStore saveIdentity:address identityKey:self.monalSignalStore.identityKeyPair.publicKey]; + return YES; + } + return NO; +} + +-(void) sendLocalDevicesIfNeeded +{ + if([self.ownReceivedDeviceList count] == 0) { + // we need to publish a new devicelist if we did not receive our own list after a new connection // Generate single use keys [self generateNewKeysIfNeeded]; [self sendOMEMOBundle]; - SignalAddress* address = [[SignalAddress alloc] initWithName:self._senderJid deviceId:self.monalSignalStore.deviceid]; - [self.monalSignalStore saveIdentity:address identityKey:self.monalSignalStore.identityKeyPair.publicKey]; + [self sendOMEMODeviceWithForce:YES]; + [self.ownReceivedDeviceList addObject:[NSNumber numberWithInt:(self.monalSignalStore.deviceid)]]; + } + else + { + // Generate single use keys + [self generateNewKeysIfNeeded]; + [self sendOMEMODevice:self.ownReceivedDeviceList force:NO]; } } @@ -140,6 +161,8 @@ -(void) setupSignal -(void) sendOMEMOBundle { + if(self.monalSignalStore.deviceid == 0) + return; [self publishKeysViaPubSub:@{ @"signedPreKeyPublic":self.monalSignalStore.signedPreKey.keyPair.publicKey, @"signedPreKeySignature":self.monalSignalStore.signedPreKey.signature, @@ -158,7 +181,7 @@ -(BOOL) generateNewKeysIfNeeded int preKeyCount = [self.monalSignalStore getPreKeyCount]; if(preKeyCount < MIN_OMEMO_KEYS) { - SignalKeyHelper* signalHelper = [[SignalKeyHelper alloc] initWithContext:self._signalContext]; + SignalKeyHelper* signalHelper = [[SignalKeyHelper alloc] initWithContext:self.signalContext]; // Generate new keys so that we have a total of MAX_OMEMO_KEYS keys again int lastPreyKedId = [self.monalSignalStore getHighestPreyKeyId]; @@ -178,11 +201,14 @@ -(void) queryOMEMOBundleFrom:(NSString *) jid andDevice:(NSString *) deviceid { NSString* bundleNode = [NSString stringWithFormat:@"eu.siacs.conversations.axolotl.bundles:%@", deviceid]; - self.openBundleFetchCnt = [NSNumber numberWithInt:(self.openBundleFetchCnt.intValue + 1)]; + self.openBundleFetchCnt++; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalUpdateBundleFetchStatus object:self userInfo:@{ + @"completed": @(self.closedBundleFetchCnt), + @"all": @(self.openBundleFetchCnt + self.closedBundleFetchCnt) + }]; [self.account.pubsub fetchNode:bundleNode from:jid withItemsList:nil andHandler:$newHandler(self, handleBundleFetchResult, $ID(rid, deviceid))]; } - $$handler(handleBundleFetchResult, $_ID(xmpp*, account), $_ID(NSString*, jid), $_ID(XMPPIQ*, errorIq), $_ID(NSDictionary*, data), $_ID(NSString*, rid)) if(errorIq) { @@ -207,15 +233,24 @@ -(void) queryOMEMOBundleFrom:(NSString *) jid andDevice:(NSString *) deviceid if(receivedKeys) [account.omemo processOMEMOKeys:receivedKeys forJid:jid andRid:rid]; } - if(account.omemo.openBundleFetchCnt.intValue > 1) + if(account.omemo.openBundleFetchCnt > 1 && account.omemo.loggedIn) { - account.omemo.openBundleFetchCnt = [NSNumber numberWithInt:(account.omemo.openBundleFetchCnt.intValue - 1)]; + account.omemo.openBundleFetchCnt--; + account.omemo.closedBundleFetchCnt++; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalUpdateBundleFetchStatus object:account.omemo userInfo:@{ + @"completed": @(account.omemo.closedBundleFetchCnt), + @"all": @(account.omemo.openBundleFetchCnt + account.omemo.closedBundleFetchCnt) + }]; } else { - account.omemo.openBundleFetchCnt = [NSNumber numberWithInt:0]; - if(account.omemo.hasCatchUpDone.intValue == 1) + account.omemo.openBundleFetchCnt = 0; + account.omemo.closedBundleFetchCnt = 0; + if(account.omemo.hasCatchUpDone && account.omemo.loggedIn) + { + [account.omemo sendLocalDevicesIfNeeded]; [[NSNotificationCenter defaultCenter] postNotificationName:kMonalFinishedOmemoBundleFetch object:self]; + } } $$ @@ -223,9 +258,9 @@ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString * { if(receivedDevices) { - NSAssert([self._senderJid caseInsensitiveCompare:self._connection.identity.jid] == NSOrderedSame, @"connection jid should be equal to the senderJid"); + NSAssert([self.accountJid caseInsensitiveCompare:self.account.connectionProperties.identity.jid] == NSOrderedSame, @"connection jid should be equal to the senderJid"); - if(![[DataLayer sharedInstance] isContactInList:source forAccount:self._accountNo] && ![source isEqualToString:self._senderJid]) + if(![[DataLayer sharedInstance] isContactInList:source forAccount:self.account.accountNo] && ![source isEqualToString:self.accountJid]) return; NSArray* existingDevices = [self.monalSignalStore knownDevicesForAddressName:source]; @@ -245,7 +280,7 @@ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString * if(![receivedDevices containsObject:deviceId]) { // only delete other devices from signal store && keep our own entry - if(!([source isEqualToString:self._senderJid] && deviceId.intValue == self.monalSignalStore.deviceid)) + if(!([source isEqualToString:self.accountJid] && deviceId.intValue == self.monalSignalStore.deviceid)) [self deleteDeviceForSource:source andRid:deviceId.intValue]; // Remove device from broken sessions if needed @@ -260,10 +295,18 @@ -(void) processOMEMODevices:(NSSet*) receivedDevices from:(NSString * } // TODO: delete deviceid from new session array // Send our own device id when it is missing on the server - if(!source || [source caseInsensitiveCompare:self._senderJid] == NSOrderedSame) + if(!source || [source caseInsensitiveCompare:self.accountJid] == NSOrderedSame) { - self.deviceListExists = YES; - [self sendOMEMODevice:receivedDevices force:NO]; + if(receivedDevices.count > 0) + { + // save own receivedDevices for catchupDone handling + [self.ownReceivedDeviceList unionSet:receivedDevices]; + } + if(self.hasCatchUpDone == true && !self.openBundleFetchCnt) + { + // the catchup done handler or the bundleFetch handler will send our own devices while logging in + [self sendOMEMODevice:receivedDevices force:NO]; + } } } } @@ -281,7 +324,7 @@ -(BOOL) knownDevicesForAddressNameExist:(NSString*) addressName -(void) deleteDeviceForSource:(NSString*) source andRid:(int) rid { // We should not delete our own device - if([source isEqualToString:self._senderJid] && rid == self.monalSignalStore.deviceid) + if([source isEqualToString:self.accountJid] && rid == self.monalSignalStore.deviceid) return; SignalAddress* address = [[SignalAddress alloc] initWithName:source deviceId:rid]; @@ -307,7 +350,7 @@ -(NSData *) getIdentityForAddress:(SignalAddress*)address -(void) sendOMEMODeviceWithForce:(BOOL) force { - NSArray* ownCachedDevices = [self knownDevicesForAddressName:self._senderJid]; + NSArray* ownCachedDevices = [self knownDevicesForAddressName:self.accountJid]; NSSet* ownCachedDevicesSet = [[NSSet alloc] initWithArray:ownCachedDevices]; [self sendOMEMODevice:ownCachedDevicesSet force:force]; } @@ -327,11 +370,15 @@ -(void) sendOMEMODevice:(NSSet*) receivedDevices force:(BOOL) force [self sendOMEMOBundle]; [self publishDevicesViaPubSub:devices]; } + if(devices.count > 0) + { + [self.ownReceivedDeviceList unionSet:devices]; + } } -(void) processOMEMOKeys:(MLXMLNode*) iqNode forJid:(NSString*) jid andRid:(NSString*) rid { - assert(self._signalContext); + assert(self.signalContext); { if(!rid) return; @@ -356,13 +403,12 @@ -(void) processOMEMOKeys:(MLXMLNode*) iqNode forJid:(NSString*) jid andRid:(NSSt uint32_t device = (uint32_t)[rid intValue]; SignalAddress* address = [[SignalAddress alloc] initWithName:jid deviceId:device]; - SignalSessionBuilder* builder = [[SignalSessionBuilder alloc] initWithAddress:address context:self._signalContext]; + SignalSessionBuilder* builder = [[SignalSessionBuilder alloc] initWithAddress:address context:self.signalContext]; NSMutableArray* preKeys = [[NSMutableArray alloc] init]; NSArray* preKeyIds = [bundle find:@"prekeys/preKeyPublic@preKeyId|int"]; for(NSNumber* preKey in preKeyIds) { - NSString* query = [NSString stringWithFormat:@"prekeys/preKeyPublic#|base64", preKey]; - NSData* key = [bundle findFirst:query]; + NSData* key = [bundle findFirst:[NSString stringWithFormat:@"prekeys/preKeyPublic#|base64", preKey]]; if(!key) continue; NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; @@ -427,7 +473,6 @@ -(void) sendKeyTransportElementIfNeeded:(NSString*) jid removeBrokenSessionForRi XMPPMessage* messageNode = [[XMPPMessage alloc] init]; [messageNode.attributes setObject:jid forKey:@"to"]; [messageNode.attributes setObject:kMessageChatType forKey:@"type"]; - [messageNode setXmppId:[[NSUUID UUID] UUIDString]]; // Send KeyTransportElement only to the one device (overrideDevices) [self encryptMessage:messageNode withMessage:nil toContact:jid]; @@ -441,7 +486,7 @@ -(void) addEncryptionKeyForAllDevices:(NSArray*) devices encryptForJid:(NSString for(NSNumber* device in devices) { // Do not encrypt for our own device - if(device.intValue == self.monalSignalStore.deviceid && [encryptForJid isEqualToString:self._senderJid]) { + if(device.intValue == self.monalSignalStore.deviceid && [encryptForJid isEqualToString:self.accountJid]) { continue; } SignalAddress* address = [[SignalAddress alloc] initWithName:encryptForJid deviceId:(uint32_t)device.intValue]; @@ -451,7 +496,7 @@ -(void) addEncryptionKeyForAllDevices:(NSArray*) devices encryptForJid:(NSString // Only add encryption key for devices that are trusted if([self.monalSignalStore isTrustedIdentity:address identityKey:identity]) { - SignalSessionCipher* cipher = [[SignalSessionCipher alloc] initWithAddress:address context:self._signalContext]; + SignalSessionCipher* cipher = [[SignalSessionCipher alloc] initWithAddress:address context:self.signalContext]; NSError* error; SignalCiphertext* deviceEncryptedKey = [cipher encryptData:encryptedPayload.key error:&error]; @@ -475,7 +520,7 @@ -(void) encryptMessage:(XMPPMessage *)messageNode withMessage:(NSString *)messag -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString*) message toContact:(NSString*) toContact overrideDevices:(NSArray* _Nullable) overrideDevices { - NSAssert(self._signalContext, @"_signalContext should be inited."); + NSAssert(self.signalContext, @"signalContext should be inited."); if(message) { @@ -485,7 +530,7 @@ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString*) messag } NSArray* devices = [self.monalSignalStore allDeviceIdsForAddressName:toContact]; - NSArray* myDevices = [self.monalSignalStore allDeviceIdsForAddressName:self._senderJid]; + NSArray* myDevices = [self.monalSignalStore allDeviceIdsForAddressName:self.accountJid]; // Check if we found omemo keys from the recipient if(devices.count > 0 || overrideDevices.count > 0) @@ -501,13 +546,30 @@ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString*) messag // Encrypt message encryptedPayload = [AESGcm encrypt:messageBytes keySize:KEY_SIZE]; + if(encryptedPayload == nil) + { + DDLogWarn(@"Could not encrypt message: AESGcm error"); + return; + } MLXMLNode* payload = [[MLXMLNode alloc] initWithElement:@"payload"]; [payload setData:[HelperTools encodeBase64WithData:encryptedPayload.body]]; [encrypted.children addObject:payload]; } else { // There is no message that can be encrypted -> create new session keys - encryptedPayload = [[MLEncryptedPayload alloc] initWithKey:[AESGcm genKey:KEY_SIZE] iv:[AESGcm genIV]]; + NSData* newKey = [AESGcm genKey:KEY_SIZE]; + NSData* newIv = [AESGcm genIV]; + if(newKey == nil || newIv == nil) + { + DDLogWarn(@"Could not create key or iv"); + return; + } + encryptedPayload = [[MLEncryptedPayload alloc] initWithKey:newKey iv:newIv]; + if(encryptedPayload == nil) + { + DDLogWarn(@"Could not encrypt message: AESGcm error"); + return; + } } // Get own device id @@ -525,7 +587,7 @@ -(void) encryptMessage:(XMPPMessage*) messageNode withMessage:(NSString*) messag // normal encryption -> add encryption for all of our own devices as well as to all of our contact's devices [self addEncryptionKeyForAllDevices:devices encryptForJid:toContact withEncryptedPayload:encryptedPayload withXMLHeader:header]; - [self addEncryptionKeyForAllDevices:myDevices encryptForJid:self._senderJid withEncryptedPayload:encryptedPayload withXMLHeader:header]; + [self addEncryptionKeyForAllDevices:myDevices encryptForJid:self.accountJid withEncryptedPayload:encryptedPayload withXMLHeader:header]; } else { @@ -552,6 +614,10 @@ -(void) needNewSessionForContact:(NSString*) contact andDevice:(NSNumber*) devic devicesWithInvalSession = [[NSMutableSet alloc] init]; } // add device to broken session contact set + if([devicesWithInvalSession containsObject:deviceId]) + { + return; + } [devicesWithInvalSession addObject:deviceId]; [self.devicesWithBrokenSession setObject:devicesWithInvalSession forKey:contact]; @@ -576,13 +642,13 @@ -(NSString *) decryptMessage:(XMPPMessage *) messageNode NSNumber* sid = [messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/header@sid|int"]; SignalAddress* address = [[SignalAddress alloc] initWithName:messageNode.fromUser deviceId:(uint32_t)sid.intValue]; - if(!self._signalContext) + if(!self.signalContext) { DDLogError(@"Missing signal context"); return NSLocalizedString(@"Error decrypting message", @""); } // check if we received our own bundle - if([messageNode.fromUser isEqualToString:self._senderJid] && sid.intValue == self.monalSignalStore.deviceid) + if([messageNode.fromUser isEqualToString:self.accountJid] && sid.intValue == self.monalSignalStore.deviceid) { // Nothing to do return nil; @@ -608,7 +674,7 @@ -(NSString *) decryptMessage:(XMPPMessage *) messageNode } else { - SignalSessionCipher* cipher = [[SignalSessionCipher alloc] initWithAddress:address context:self._signalContext]; + SignalSessionCipher* cipher = [[SignalSessionCipher alloc] initWithAddress:address context:self.signalContext]; SignalCiphertextType messagetype; // Check if message is encrypted with a prekey @@ -665,7 +731,7 @@ -(NSString *) decryptMessage:(XMPPMessage *) messageNode { // nothing to do DDLogInfo(@"KeyTransportElement received from device: %@", sid); -#ifdef DEBUG +#ifdef DEBUG_ALPHA return [NSString stringWithFormat:@"ALPHA_DEBUG_MESSAGE: KeyTransportElement received from device: %@", sid]; #else return nil; @@ -685,12 +751,21 @@ -(NSString *) decryptMessage:(XMPPMessage *) messageNode NSString* encryptedPayload = [messageNode findFirst:@"{eu.siacs.conversations.axolotl}encrypted/payload#"]; NSData* iv = [HelperTools dataWithBase64EncodedString:ivStr]; + if(iv.length != 12) + { + DDLogError(@"Could not decrypt message: iv length: %lu", (unsigned long)iv.length); + return NSLocalizedString(@"Error while decrypting: iv.length != 12", @""); + } NSData* decodedPayload = [HelperTools dataWithBase64EncodedString:encryptedPayload]; - + if(decodedPayload == nil || key == nil || iv == nil || auth == nil) + { + DDLogError(@"Could not decrypt message: GCM params missing"); + return NSLocalizedString(@"Error while decrypting", @""); + } NSData* decData = [AESGcm decrypt:decodedPayload withKey:key andIv:iv withAuth:auth]; if(!decData) { DDLogError(@"Could not decrypt message with key that was decrypted. (GCM error)"); - return NSLocalizedString(@"Encrypted message was sent in an older format Monal can't decrypt. Please ask them to update their client. (GCM error)", @""); + return NSLocalizedString(@"Encrypted message was sent in an older format Monal can't decrypt. Please ask them to update their client. (GCM error)", @""); } else { diff --git a/Monal/Classes/MLPasswordChangeTableViewController.m b/Monal/Classes/MLPasswordChangeTableViewController.m index cae970b9e2..3a0161f189 100644 --- a/Monal/Classes/MLPasswordChangeTableViewController.m +++ b/Monal/Classes/MLPasswordChangeTableViewController.m @@ -33,8 +33,8 @@ -(IBAction) changePress:(id)sender [self presentViewController:messageAlert animated:YES completion:nil]; } - else { - + else + { if(self.password.text.length>0) { [self.xmppAccount changePassword:self.password.text withCompletion:^(BOOL success, NSString *message) { @@ -96,11 +96,10 @@ - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { -(void) viewDidLoad { [super viewDidLoad]; - self.navigationItem.title=NSLocalizedString(@"Change Password",@ ""); + self.navigationItem.title=NSLocalizedString(@"Change Password", @""); [self.tableView registerNib:[UINib nibWithNibName:@"MLTextInputCell" bundle:[NSBundle mainBundle]] - forCellReuseIdentifier:@"TextCell"]; - + forCellReuseIdentifier:@"TextCell"]; } -(void) viewWillAppear:(BOOL)animated @@ -118,11 +117,10 @@ -(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if(section==0) - { + if(section == 0) return NSLocalizedString(@"Enter your new password. Passwords may not be empty. They may also be governed by server or company policies.",@ ""); - } - else return nil; + else + return nil; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section @@ -145,31 +143,20 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell ; - - switch (indexPath.section) { - case 0: { - MLTextInputCell *textCell =[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; - if(indexPath.row ==0){ - self.password =textCell.textInput; - self.password.placeholder = NSLocalizedString(@"New Password",@ ""); - self.password.delegate=self; - self.password.secureTextEntry=YES; - } - - cell= textCell; - break; - } - case 1: { - cell =[tableView dequeueReusableCellWithIdentifier:@"addButton"]; - break; + if(indexPath.section == 0) + { + MLTextInputCell *textCell =[tableView dequeueReusableCellWithIdentifier:@"TextCell"]; + if(indexPath.row == 0) + { + self.password =textCell.textInput; + self.password.placeholder = NSLocalizedString(@"New Password", @""); + self.password.delegate=self; + self.password.secureTextEntry=YES; } - default: - break; + return textCell; } - - return cell; - + else + return [tableView dequeueReusableCellWithIdentifier:@"addButton"]; } #pragma mark tableview delegate diff --git a/Monal/Classes/MLPipe.m b/Monal/Classes/MLPipe.m index 358540e7ea..026d7b9def 100755 --- a/Monal/Classes/MLPipe.m +++ b/Monal/Classes/MLPipe.m @@ -197,7 +197,7 @@ -(void) process { NSError* error=[_output streamError]; DDLogError(@"pipe sending failed with error %ld domain %@ message %@", (long)error.code, error.domain, error.userInfo); - return; + break; } else if(writtenLen < readLen) { diff --git a/Monal/Classes/MLPrivacySettingsViewController.m b/Monal/Classes/MLPrivacySettingsViewController.m index 838d3df3e4..147959683e 100644 --- a/Monal/Classes/MLPrivacySettingsViewController.m +++ b/Monal/Classes/MLPrivacySettingsViewController.m @@ -44,6 +44,7 @@ - (void)viewDidLoad -(void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + [[HelperTools defaultsDB] setObject:@YES forKey:@"HasSeenPrivacySettings"]; } - (void)viewWillDisappear:(BOOL)animated @@ -264,14 +265,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } break; } - default: - { - return nil; - break; - } } - cell.selectionStyle = UITableViewCellSelectionStyleNone; - cell.selectionStyle = UITableViewCellSelectionStyleNone; return cell; } diff --git a/Monal/Classes/MLPush.m b/Monal/Classes/MLPush.m index 4417a704e6..0ea25e64aa 100644 --- a/Monal/Classes/MLPush.m +++ b/Monal/Classes/MLPush.m @@ -7,14 +7,11 @@ // #import "MLPush.h" +#import "MLConstants.h" #import "MLXMPPManager.h" - - - @implementation MLPush - +(NSString*) stringFromToken:(NSData*) tokenIn { unsigned char* tokenBytes = (unsigned char*)[tokenIn bytes]; diff --git a/Monal/Classes/MLQRCodeScanner.swift b/Monal/Classes/MLQRCodeScanner.swift new file mode 100644 index 0000000000..1d30d140d1 --- /dev/null +++ b/Monal/Classes/MLQRCodeScanner.swift @@ -0,0 +1,253 @@ +// +// MLQRCodeScanner.swift +// Monal +// +// Created by Friedrich Altheide on 20.11.20. +// Copyright Β© 2020 Monal.im. All rights reserved. +// + +import CocoaLumberjack +import AVFoundation +import UIKit + +@objc protocol MLLQRCodeScannerAccountLoginDeleagte : AnyObject +{ + func MLQRCodeAccountLoginScanned(jid: String, password: String) +} + +@objc protocol MLLQRCodeScannerContactDeleagte : AnyObject +{ + func MLQRCodeContactScanned(jid: String, fingerprints: Dictionary) +} + +struct XMPPLoginQRCode : Codable +{ + let usedProtocol:String + let address:String + let password:String + + private enum CodingKeys: String, CodingKey + { + case usedProtocol = "protocol", address, password + } +} + +@objc class MLQRCodeScanner: UIViewController, AVCaptureMetadataOutputObjectsDelegate +{ + @objc weak var loginDelegate : MLLQRCodeScannerAccountLoginDeleagte? + @objc weak var contactDelegate : MLLQRCodeScannerContactDeleagte? + + var videoPreviewLayer: AVCaptureVideoPreviewLayer! + var captureSession: AVCaptureSession! + + override func viewDidLoad() + { + super.viewDidLoad() + self.title = NSLocalizedString("QR-Code Scanner", comment: "") + view.backgroundColor = UIColor.black + + // init capture session + captureSession = AVCaptureSession() + guard let captureDevice = AVCaptureDevice.default(for: .video) + else + { + errorMsg(title: NSLocalizedString("QR-Code video error", comment: "QR-Code-Scanner"), msg: NSLocalizedString("Could not get default capture device", comment: "QR-Code-Scanner")) + return; + } + let videoInput: AVCaptureDeviceInput + + do + { + videoInput = try AVCaptureDeviceInput(device: captureDevice) + } catch + { + errorMsg(title: NSLocalizedString("QR-Code video error", comment: "QR-Code-Scanner"), msg: NSLocalizedString("Could not init video session", comment: "QR-Code-Scanner")) + return + } + if(captureSession.canAddInput(videoInput)) + { + captureSession.addInput(videoInput) + } + else + { + errorMsgNoCameraFound() + return; + } + let metadataOutput = AVCaptureMetadataOutput() + + if (captureSession.canAddOutput(metadataOutput)) { + captureSession.addOutput(metadataOutput) + + metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + metadataOutput.metadataObjectTypes = [.qr] + } else { + errorMsgNoCameraFound() + return + } + + videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + videoPreviewLayer.frame = view.layer.bounds + videoPreviewLayer.videoGravity = .resizeAspectFill + view.layer.addSublayer(videoPreviewLayer) + + captureSession.startRunning() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if (captureSession?.isRunning == false) { + captureSession.startRunning() + } + } + + override func viewWillDisappear(_ animated: Bool) { + if (captureSession?.isRunning == true) { + captureSession.stopRunning() + } + super.viewWillDisappear(animated) + } + + func errorMsgNoCameraFound() + { + captureSession = nil + + errorMsg(title: NSLocalizedString("Could not access camera", comment: "QR-Code-Scanner: camera not found"), msg: NSLocalizedString("It does not seem as your device has a camera. Please use a device with a camera for scanning", comment: "QR-Code-Scanner: Camera not found")) + } + + override var prefersStatusBarHidden: Bool + { + return false + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask + { + return .portrait + } + + func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + captureSession.stopRunning() + + if let metadataObject = metadataObjects.first { + guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return } + + guard let qrCodeAsString = readableObject.stringValue + else + { + handleQRCodeError() + return + } + AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) + + if(qrCodeAsString.hasPrefix("xmpp:")) + { + handleNewContactRequest(contactString: qrCodeAsString) + return + } + else + { + // check if we have a json object + // https://github.com/iNPUTmice/Conversations/issues/3796 + guard let qrCodeData = qrCodeAsString.data(using: .utf8) + else + { + handleQRCodeError() + return + } + let jsonDecoder = JSONDecoder() + do + { + let loginData = try jsonDecoder.decode(XMPPLoginQRCode.self, from: qrCodeData) + print(loginData) + handleOmemoAccountLogin(loginData: loginData) + return + } catch + { + handleQRCodeError() + return + } + } + } + } + + func errorMsg(title: String, msg: String, startCaptureOnClose: Bool = false) + { + let ac = UIAlertController(title: title, message: msg, preferredStyle: .alert) + ac.addAction(UIAlertAction(title: NSLocalizedString("Close", comment: ""), style: .default) + { + action -> Void in + // start capture again after invalid qr code + if(startCaptureOnClose == true) + { + self.captureSession.startRunning() + } + } + ) + present(ac, animated: true) + } + + func handleOmemoAccountLogin(loginData: XMPPLoginQRCode) + { + if(loginData.usedProtocol == "xmpp") + { + self.loginDelegate?.MLQRCodeAccountLoginScanned(jid: loginData.address, password: loginData.password) + } + } + + func handleNewContactRequest(contactString: String) + { + let XMPP_PREFIX : String = "xmpp:" + let OMEMO_SID_PREFIX : String = "omemo-sid-" + + var omemoFingerprints = Dictionary() + var parsedJid : String + // parse contact string + if(contactString.hasPrefix(XMPP_PREFIX)) + { + let shortendContactString = contactString.suffix(contactString.count - XMPP_PREFIX.count) + let contactStringParts = shortendContactString.components(separatedBy: "?") + if(contactStringParts.count >= 1 && contactStringParts.count <= 2) + { + // check if contactStringParts[0] is a valid jid + let jidParts = contactStringParts[0].components(separatedBy: "@") + if(jidParts.count == 2 && jidParts[0].count > 0 && jidParts[1].count > 0) + { + parsedJid = contactStringParts[0] + // parse omemo fingerprints if present + if(contactStringParts.count == 2) + { + let omemoParts = contactStringParts[1].components(separatedBy: ";") + for omemoPart in omemoParts + { + let keyParts = omemoPart.components(separatedBy: "=") + if(keyParts.count == 2 && keyParts[0].hasPrefix(OMEMO_SID_PREFIX)) + { + let sidStr = keyParts[0].suffix(keyParts[0].count - OMEMO_SID_PREFIX.count) + // parse string sid to int + let sid = Int(sidStr) ?? -1 + if(sid > 0) + { + // valid sid + if(keyParts[1].count > 0) + { + // todo append + omemoFingerprints[sid] = keyParts[1] + } + } + } + } + } + // call handler + self.contactDelegate?.MLQRCodeContactScanned(jid: parsedJid, fingerprints: omemoFingerprints) + return + } + } + } + handleQRCodeError() + } + + func handleQRCodeError() + { + errorMsg(title: NSLocalizedString("Invalid format", comment: "QR-Code-Scanner: invalid format"), msg: NSLocalizedString("We could not find a xmpp related QR-Code", comment: "QR-Code-Scanner: invalid format"), startCaptureOnClose: true) + } +} diff --git a/Monal/Classes/MLRegisterViewController.m b/Monal/Classes/MLRegisterViewController.m index a11337d0f6..c02eaab4cf 100644 --- a/Monal/Classes/MLRegisterViewController.m +++ b/Monal/Classes/MLRegisterViewController.m @@ -10,7 +10,7 @@ #import "MBProgressHUD.h" #import "DataLayer.h" #import "MLXMPPManager.h" - +#import "xmpp.h" #import "MLRegSuccessViewController.h" @import QuartzCore; @@ -155,8 +155,8 @@ -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender -(IBAction) useWithoutAccount:(id)sender { - [self dismissViewControllerAnimated:YES completion:nil]; [[HelperTools defaultsDB] setBool:YES forKey:@"HasSeenLogin"]; + [self dismissViewControllerAnimated:YES completion:nil]; } -(IBAction) tapAction:(id)sender diff --git a/Monal/Classes/MLResourcesTableViewController.m b/Monal/Classes/MLResourcesTableViewController.m index 9c2d7acd37..f2e40a5c92 100644 --- a/Monal/Classes/MLResourcesTableViewController.m +++ b/Monal/Classes/MLResourcesTableViewController.m @@ -19,6 +19,7 @@ @implementation MLResourcesTableViewController - (void)viewDidLoad { [super viewDidLoad]; + if(self.contact.isGroup) { self.navigationItem.title=NSLocalizedString(@"Participants",@ ""); } else { @@ -34,6 +35,7 @@ - (void)viewDidLoad { -(void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + self.resources = [[DataLayer sharedInstance] resourcesForContact:self.contact.contactJid]; if (!self.contact.isGroup) { @@ -44,7 +46,8 @@ -(void) viewWillAppear:(BOOL)animated -(void)viewWillDisappear:(BOOL)animated { - [super viewWillAppear:animated]; + [super viewWillDisappear:animated]; + if(!self.contact.isGroup) { [[NSNotificationCenter defaultCenter] removeObserver:kMonalXmppUserSoftWareVersionRefresh]; diff --git a/Monal/Classes/MLSQLite.h b/Monal/Classes/MLSQLite.h index 52938681fa..5e2a8aee53 100644 --- a/Monal/Classes/MLSQLite.h +++ b/Monal/Classes/MLSQLite.h @@ -24,11 +24,14 @@ typedef BOOL (^monal_sqlite_bool_operations_t)(void); -(void) beginWriteTransaction; -(void) endWriteTransaction; --(id) executeScalar:(NSString*) query; --(id) executeScalar:(NSString*) query andArguments:(NSArray*) args; +-(id _Nullable) executeScalar:(NSString*) query; +-(id _Nullable) executeScalar:(NSString*) query andArguments:(NSArray*) args; --(NSMutableArray*) executeReader:(NSString*) query; --(NSMutableArray*) executeReader:(NSString*) query andArguments:(NSArray*) args; +-(NSArray* _Nullable) executeScalarReader:(NSString*) query; +-(NSArray* _Nullable) executeScalarReader:(NSString*) query andArguments:(NSArray*) args; + +-(NSMutableArray* _Nullable) executeReader:(NSString*) query; +-(NSMutableArray* _Nullable) executeReader:(NSString*) query andArguments:(NSArray*) args; -(BOOL) executeNonQuery:(NSString*) query; -(BOOL) executeNonQuery:(NSString*) query andArguments:(NSArray *) args; diff --git a/Monal/Classes/MLSQLite.m b/Monal/Classes/MLSQLite.m index 806a8e8391..13b3186dc6 100644 --- a/Monal/Classes/MLSQLite.m +++ b/Monal/Classes/MLSQLite.m @@ -242,10 +242,15 @@ -(void) testThreadInstanceForQuery:(NSString*) query andArguments:(NSArray*) arg } } +-(void) checkQuery:(NSString*) query +{ + if(!query || [query length] == 0) + @throw [NSException exceptionWithName:@"SQLite3Exception" reason:@"Empty sql query!" userInfo:nil]; +} + -(BOOL) executeNonQuery:(NSString*) query andArguments:(NSArray *) args withException:(BOOL) throwException { - if(!query) - return NO; + [self checkQuery:query]; //NOTE: we are not checking the thread instance here in this private api, but in the public api proxy methods @@ -349,8 +354,7 @@ -(id) executeScalar:(NSString*) query -(id) executeScalar:(NSString*) query andArguments:(NSArray*) args { - if(!query) - return nil; + [self checkQuery:query]; [self testThreadInstanceForQuery:query andArguments:args]; @@ -371,8 +375,41 @@ -(id) executeScalar:(NSString*) query andArguments:(NSArray*) args else { //if noting else - DDLogVerbose(@"returning nil with out OK %@", query); - toReturn = nil; + [self throwErrorForQuery:query andArguments:args]; + } + return toReturn; +} + +-(NSArray*) executeScalarReader:(NSString*) query +{ + return [self executeScalarReader:query andArguments:@[]]; +} + +-(NSArray*) executeScalarReader:(NSString*) query andArguments:(NSArray*) args +{ + [self checkQuery:query]; + + [self testThreadInstanceForQuery:query andArguments:args]; + + NSMutableArray* __block toReturn = [[NSMutableArray alloc] init]; + sqlite3_stmt* statement = [self prepareQuery:query withArgs:args]; + if(statement != NULL) + { + int step; + while((step=sqlite3_step(statement)) == SQLITE_ROW) + { + NSObject* returnData = [self getColumn:0 ofStatement:statement]; + //accessing an unset key in NSDictionary will return nil (nil can not be inserted directly into the dictionary) + if(returnData) + [toReturn addObject:returnData]; + } + sqlite3_finalize(statement); + if(step != SQLITE_DONE) + [self throwErrorForQuery:query andArguments:args]; + } + else + { + //if noting else [self throwErrorForQuery:query andArguments:args]; } return toReturn; @@ -385,8 +422,7 @@ -(NSMutableArray*) executeReader:(NSString*) query -(NSMutableArray*) executeReader:(NSString*) query andArguments:(NSArray*) args { - if(!query) - return nil; + [self checkQuery:query]; [self testThreadInstanceForQuery:query andArguments:args]; @@ -404,7 +440,7 @@ -(NSMutableArray*) executeReader:(NSString*) query andArguments:(NSArray*) args NSString* columnName = [NSString stringWithUTF8String:sqlite3_column_name(statement, counter)]; NSObject* returnData = [self getColumn:counter ofStatement:statement]; //accessing an unset key in NSDictionary will return nil (nil can not be inserted directly into the dictionary) - if(returnData != nil) + if(returnData) [row setObject:returnData forKey:columnName]; counter++; } @@ -418,7 +454,6 @@ -(NSMutableArray*) executeReader:(NSString*) query andArguments:(NSArray*) args { //if noting else DDLogVerbose(@"reader nil with sql not ok: %@", query); - toReturn = nil; [self throwErrorForQuery:query andArguments:args]; } return toReturn; diff --git a/Monal/Classes/MLSearchViewController.h b/Monal/Classes/MLSearchViewController.h index 77e960101a..d83a20cd61 100644 --- a/Monal/Classes/MLSearchViewController.h +++ b/Monal/Classes/MLSearchViewController.h @@ -10,13 +10,12 @@ #import "MLContact.h" #import "DataLayer.h" -@protocol SearchResultDeleagte - +@protocol SearchResultDelegate - (void) doGoSearchResultAction:(NSNumber*_Nullable) nextDBId; -- (void) doReloadActionForPathIndex:(NSIndexPath*_Nonnull) pathIdx; - (void) doReloadActionForAllTableView; - (void) doReloadHistoryForSearch; - (void) doGetMsgData; +- (void) doShowLoadingHistory:(NSString* _Nonnull) title; @end NS_ASSUME_NONNULL_BEGIN @@ -24,14 +23,14 @@ NS_ASSUME_NONNULL_BEGIN @interface MLSearchViewController : UISearchController @property (nonatomic, strong) MLContact *contact; @property (nonatomic, weak) NSString *jid; -@property (nonatomic, weak) id searchResultDelegate; +@property (nonatomic, weak) id searchResultDelegate; @property (nonatomic) BOOL isLoadingHistory; @property (nonatomic) BOOL isGoingUp; - (void) getSearchData:(NSString*) queryText; - (NSMutableAttributedString*) doSearchKeyword:(NSString*) keyword onText:(NSString*) allText andInbound:(BOOL) inDirection; -- (BOOL) isDBIdExited:(NSNumber*) dbId; +- (BOOL) isDBIdExistent:(NSNumber*) dbId; - (void) setResultToolBar; - (void) setMessageIndexPath:(NSNumber*)idxPath withDBId:(NSNumber*)dbId; - (NSNumber*) getMessageIndexPathForDBId:(NSNumber*)dbId; diff --git a/Monal/Classes/MLSearchViewController.m b/Monal/Classes/MLSearchViewController.m index bfe6f2c910..757e1b56ff 100644 --- a/Monal/Classes/MLSearchViewController.m +++ b/Monal/Classes/MLSearchViewController.m @@ -26,14 +26,16 @@ @implementation MLSearchViewController - (void)viewDidLoad { [super viewDidLoad]; - // Do any additional setup after loading the view. + // Do any additional setup after loading the view. self.searchBar.delegate = self; self.isLoadingHistory = NO; } - (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + CGFloat xAxis = self.searchBar.frame.origin.x; CGFloat yAxis = self.searchBar.frame.origin.y; CGFloat height = self.searchBar.frame.size.height; @@ -66,6 +68,8 @@ - (void)viewDidAppear:(BOOL)animated - (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + self.isLoadingHistory = NO; self.searchResultMessageList = nil; self.searchResultMessageDictionary = nil; @@ -108,10 +112,17 @@ - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText if ([self.searchResultMessageList count] >0) { self.toolbar.items = @[self.epmtyItem, self.prevItem, self.nextItem, self.searchResultIndicatorItem]; - self.searchBar.inputAccessoryView = self.toolbar; + #if TARGET_OS_MACCATALYST + CGFloat yAxis = self.view.frame.size.height - self.searchBar.frame.size.height; + [self.toolbar setFrame:CGRectMake(0, yAxis, self.searchBar.frame.size.width, self.searchBar.frame.size.height)]; + [self.view addSubview:self.toolbar]; + #else + self.searchBar.inputAccessoryView = self.toolbar; + #endif self.curIdxHistory = (int)[self.searchResultMessageList count] - 1; [self setResultIndicatorTitle:@"" onlyHint:NO]; + [self.searchBar reloadInputViews]; } else { @@ -132,7 +143,7 @@ - (void)doNextAction if (self.curIdxHistory > self.searchResultMessageList.count - 1) self.curIdxHistory = (int) self.searchResultMessageList.count - 1; - if ([self getMessageIndexPathForDBId:((MLMessage*)self.searchResultMessageList[self.curIdxHistory]).messageDBId]) + if([self getMessageIndexPathForDBId:((MLMessage*)self.searchResultMessageList[self.curIdxHistory]).messageDBId]) { [self setResultIndicatorTitle:@"" onlyHint:NO]; [self.searchResultDelegate doGoSearchResultAction:((MLMessage*)self.searchResultMessageList[self.curIdxHistory]).messageDBId]; @@ -142,6 +153,7 @@ - (void)doNextAction self.isLoadingHistory = YES; self.curIdxHistory -= 1; [self.searchResultDelegate doReloadHistoryForSearch]; + [self setResultIndicatorTitle:NSLocalizedString(@"Loading more Messages from Server", @"") onlyHint:YES]; } } else @@ -153,13 +165,13 @@ - (void)doNextAction - (void)doPreviousAction { self.isGoingUp = YES; - if (!self.isLoadingHistory) + if(!self.isLoadingHistory) { self.curIdxHistory -= 1; if (self.curIdxHistory <= 0) self.curIdxHistory = 0; - if ([self getMessageIndexPathForDBId:((MLMessage*)self.searchResultMessageList[self.curIdxHistory]).messageDBId]) + if([self getMessageIndexPathForDBId:((MLMessage*)self.searchResultMessageList[self.curIdxHistory]).messageDBId]) { [self setResultIndicatorTitle:@"" onlyHint:NO]; [self.searchResultDelegate doGoSearchResultAction:((MLMessage*)self.searchResultMessageList[self.curIdxHistory]).messageDBId]; @@ -169,6 +181,7 @@ - (void)doPreviousAction self.curIdxHistory += 1; self.isLoadingHistory = YES; [self.searchResultDelegate doReloadHistoryForSearch]; + [self setResultIndicatorTitle:NSLocalizedString(@"Loading more Messages from Server", @"") onlyHint:YES]; } } else @@ -188,10 +201,10 @@ - (void)setResultIndicatorTitle:(NSString*)title onlyHint:(BOOL)isOnlyHint else { finalTitle = title; + [self.searchResultDelegate doShowLoadingHistory:finalTitle]; } [self.searchResultIndicatorItem setTitle:finalTitle]; - [self.searchBar reloadInputViews]; [self.searchResultDelegate doReloadActionForAllTableView]; } @@ -204,7 +217,7 @@ - (void)updateMsgDictionary } } -- (BOOL)isDBIdExited:(NSNumber*) dbId +- (BOOL)isDBIdExistent:(NSNumber*) dbId { if ([self.searchResultMessageDictionary objectForKey:dbId]) { @@ -277,15 +290,9 @@ -(void)setMessageIndexPath:(NSNumber*)idxPath withDBId:(NSNumber*)dbId [self.messageDictionary setObject:idxPath forKey:dbId]; } --(NSNumber*)getMessageIndexPathForDBId:(NSNumber*)dbId +-(NSNumber*) getMessageIndexPathForDBId:(NSNumber*) dbId { - NSNumber *idxPath = [self.messageDictionary objectForKey:dbId]; - if(idxPath) - { - return idxPath; - } - - return nil; + return [self.messageDictionary objectForKey:dbId]; } -(void)escapeSearchPressed:(UIKeyCommand*)keyCommand diff --git a/Monal/Classes/MLSettingCell.h b/Monal/Classes/MLSettingCell.h index c0ddef9ec5..1c06daf96b 100644 --- a/Monal/Classes/MLSettingCell.h +++ b/Monal/Classes/MLSettingCell.h @@ -12,7 +12,7 @@ @property (nonatomic, assign) BOOL switchEnabled; @property (nonatomic, assign) BOOL textEnabled; -@property (nonatomic, weak) UIViewController *parent; +@property (nonatomic, weak) UIViewController* parent; /** NSuserdefault key to use diff --git a/Monal/Classes/MLSettingCell.m b/Monal/Classes/MLSettingCell.m index 32d90c135a..84aae373b1 100644 --- a/Monal/Classes/MLSettingCell.m +++ b/Monal/Classes/MLSettingCell.m @@ -8,11 +8,11 @@ #import "MLSettingCell.h" #import "MLXMPPManager.h" - +#import "xmpp.h" @implementation MLSettingCell -- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +-(id) initWithStyle:(UITableViewCellStyle) style reuseIdentifier:(NSString*) reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if(self) @@ -26,80 +26,53 @@ - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reus return self; } -- (void)layoutSubviews +-(void) layoutSubviews { - [super layoutSubviews]; //The default implementation of the layoutSubviews - CGRect textLabelFrame = self.textLabel.frame; + CGRect textLabelFrame = self.textLabel.frame; //this is to account for padding in the grouped tableview cell - int padding =30; - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) - { - padding=80; - } + int padding = 30; + if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) + padding = 80; if(self.switchEnabled) - { - _toggleSwitch.on= [[HelperTools defaultsDB] boolForKey: _defaultKey]; - [ _toggleSwitch addTarget:self action:@selector(switchChange) forControlEvents:UIControlEventValueChanged]; - CGRect frame=CGRectMake(self.frame.size.width-50-padding, - textLabelFrame.origin.y+9, - 0, - 0); - _toggleSwitch.frame=frame; - [self.contentView addSubview: _toggleSwitch ]; - - } + { + _toggleSwitch.on = [[HelperTools defaultsDB] boolForKey:_defaultKey]; + [ _toggleSwitch addTarget:self action:@selector(switchChange) forControlEvents:UIControlEventValueChanged]; + CGRect frame = CGRectMake(self.frame.size.width - 50 - padding, textLabelFrame.origin.y + 9, 0, 0); + _toggleSwitch.frame = frame; + [self.contentView addSubview:_toggleSwitch]; + } if(self.textEnabled) { CGRect frame=CGRectMake(self.frame.size.width-79-padding, textLabelFrame.origin.y+9,79, textLabelFrame.size.height*2/3); - if([_defaultKey isEqualToString:@"StatusMessage"]) - { - frame=CGRectMake( self.contentView.frame.origin.x+10, - self.contentView.frame.origin.y, - self.contentView.frame.size. width-10, - self.contentView.frame.size.height); - } - _textInputField.frame=frame; _textInputField.returnKeyType=UIReturnKeyDone; _textInputField.delegate=self; _textInputField.text= [[HelperTools defaultsDB] stringForKey: _defaultKey]; [self.contentView addSubview: _textInputField ]; } - - } -(void) switchChange { - [[HelperTools defaultsDB] setBool:_toggleSwitch.on forKey: _defaultKey]; - if([_defaultKey isEqualToString:@"Away"]) - { - [[MLXMPPManager sharedInstance] setAway:_toggleSwitch.on]; - } + [[HelperTools defaultsDB] setBool:_toggleSwitch.on forKey:_defaultKey]; } #pragma mark uitextfield delegate --(void)textFieldDidBeginEditing:(UITextField *)textField +-(void) textFieldDidBeginEditing:(UITextField*) textField { } -- (BOOL)textFieldShouldReturn:(UITextField *)textField +-(BOOL) textFieldShouldReturn:(UITextField*) textField { - [[HelperTools defaultsDB] setObject:_textInputField.text forKey: _defaultKey]; + [[HelperTools defaultsDB] setObject:_textInputField.text forKey: _defaultKey]; [textField resignFirstResponder]; - - if([_defaultKey isEqualToString:@"StatusMessage"]) - { - [[MLXMPPManager sharedInstance] setStatusMessage:textField.text]; - } - return YES; } diff --git a/Monal/Classes/MLSettingsTableViewController.m b/Monal/Classes/MLSettingsTableViewController.m index 281c82a5ac..86b299a264 100644 --- a/Monal/Classes/MLSettingsTableViewController.m +++ b/Monal/Classes/MLSettingsTableViewController.m @@ -43,11 +43,10 @@ - (void)viewDidLoad { self.appRows = @[ NSLocalizedString(@"Quick Setup", @""), NSLocalizedString(@"Accounts",@""), + NSLocalizedString(@"Privacy Settings",@""), NSLocalizedString(@"Notifications",@""), NSLocalizedString(@"Backgrounds",@""), NSLocalizedString(@"Sounds",@""), - NSLocalizedString(@"Display",@""), - NSLocalizedString(@"Privacy Settings",@""), NSLocalizedString(@"Chat Logs",@"") ]; self.supportRows = @[ @@ -158,33 +157,25 @@ -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath * break; case 2: - [self performSegueWithIdentifier:@"showNotification" sender:self]; + [self performSegueWithIdentifier:@"showPrivacySettings" sender:self]; break; case 3: - [self performSegueWithIdentifier:@"showBackgrounds" sender:self]; + [self performSegueWithIdentifier:@"showNotification" sender:self]; break; case 4: - [self performSegueWithIdentifier:@"showSounds" sender:self]; + [self performSegueWithIdentifier:@"showBackgrounds" sender:self]; break; case 5: - [self performSegueWithIdentifier:@"showDisplay" sender:self]; + [self performSegueWithIdentifier:@"showSounds" sender:self]; break; case 6: - [self performSegueWithIdentifier:@"showPrivacySettings" sender:self]; - break; - - case 7: [self performSegueWithIdentifier:@"showChatLog" sender:self]; break; - case 8: - [self performSegueWithIdentifier:@"showCloud" sender:self]; - break; - default: break; } diff --git a/Monal/Classes/MLSignalStore.h b/Monal/Classes/MLSignalStore.h index 0a5445a9ac..abd7f7d0a0 100644 --- a/Monal/Classes/MLSignalStore.h +++ b/Monal/Classes/MLSignalStore.h @@ -26,7 +26,6 @@ -(NSMutableArray *) readPreKeys; -(void) updateTrust:(BOOL) trust forAddress:(SignalAddress*)address; -(void) deleteDeviceforAddress:(SignalAddress*)address; --(void) deletePreKeyWithRid:(NSNumber*) rid; -(int) getHighestPreyKeyId; -(int) getPreKeyCount; diff --git a/Monal/Classes/MLSignalStore.m b/Monal/Classes/MLSignalStore.m index e72c4ec7e7..c1f7a0247a 100644 --- a/Monal/Classes/MLSignalStore.m +++ b/Monal/Classes/MLSignalStore.m @@ -23,7 +23,7 @@ @implementation MLSignalStore +(void) initialize { - //WE USE THE SAME DATABASE FILE AS THE DataLayer --> this should probably be migrated into the datalayer or use its own sqlite database + //TODO: WE USE THE SAME DATABASE FILE AS THE DataLayer --> this should probably be migrated into the datalayer or use its own sqlite database //make sure the datalayer has migrated the database file to the app group location first [DataLayer initialize]; @@ -100,9 +100,9 @@ -(NSMutableArray *) readPreKeys -(int) getHighestPreyKeyId { - NSNumber* highestId = (NSNumber*)[self.sqliteDatabase executeScalar:@"SELECT prekeyid FROM signalPreKey WHERE account_id=? ORDER BY prekeyid DESC LIMIT 1" andArguments:@[self.accountId]]; + NSNumber* highestId = [self.sqliteDatabase executeScalar:@"SELECT prekeyid FROM signalPreKey WHERE account_id=? ORDER BY prekeyid DESC LIMIT 1;" andArguments:@[self.accountId]]; - if(!highestId) { + if(highestId==nil) { return 0; // Default value -> first preKeyId will be 1 } else { return highestId.intValue; diff --git a/Monal/Classes/MLSoundsTableViewController.m b/Monal/Classes/MLSoundsTableViewController.m index c20f6e47cd..7ffee09661 100644 --- a/Monal/Classes/MLSoundsTableViewController.m +++ b/Monal/Classes/MLSoundsTableViewController.m @@ -74,33 +74,28 @@ -(NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteg } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - - UITableViewCell* toreturn; - switch (indexPath.section) { - case 0: { - MLSettingCell* cell=[[MLSettingCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"AccountCell"]; - cell.parent= self; - cell.switchEnabled=YES; - cell.defaultKey=@"Sound"; - cell.textLabel.text=NSLocalizedString(@"Play Sounds",@ ""); - toreturn=cell; - break; - } - case 1: { - UITableViewCell* cell=[tableView dequeueReusableCellWithIdentifier:@"soundCell"]; - cell.textLabel.text= self.soundList[indexPath.row]; - NSString *filename =[NSString stringWithFormat:@"alert%ld", (long)indexPath.row+1]; - if([filename isEqualToString:[[HelperTools defaultsDB] objectForKey:@"AlertSoundFile"]]) { - cell.accessoryType=UITableViewCellAccessoryCheckmark; - self.selectedIndex= indexPath.row; - } else { - cell.accessoryType=UITableViewCellAccessoryNone; - } - toreturn=cell; - + if(indexPath.section == 0) + { + MLSettingCell* cell=[[MLSettingCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"AccountCell"]; + cell.parent= self; + cell.switchEnabled=YES; + cell.defaultKey=@"Sound"; + cell.textLabel.text=NSLocalizedString(@"Play Sounds",@ ""); + return cell; + } + else + { + UITableViewCell* cell=[tableView dequeueReusableCellWithIdentifier:@"soundCell"]; + cell.textLabel.text= self.soundList[indexPath.row]; + NSString *filename =[NSString stringWithFormat:@"alert%ld", (long)indexPath.row+1]; + if([filename isEqualToString:[[HelperTools defaultsDB] objectForKey:@"AlertSoundFile"]]) { + cell.accessoryType=UITableViewCellAccessoryCheckmark; + self.selectedIndex= indexPath.row; + } else { + cell.accessoryType=UITableViewCellAccessoryNone; } + return cell; } - return toreturn; } diff --git a/Monal/Classes/MLSubscriptionTableViewController.m b/Monal/Classes/MLSubscriptionTableViewController.m index 041b932a6d..ff41b767a2 100644 --- a/Monal/Classes/MLSubscriptionTableViewController.m +++ b/Monal/Classes/MLSubscriptionTableViewController.m @@ -9,6 +9,7 @@ #import "MLSubscriptionTableViewController.h" #import "DataLayer.h" #import "MLXMPPManager.h" +#import "xmpp.h" @interface MLSubscriptionTableViewController () @property (nonatomic, strong) NSMutableArray *requests; diff --git a/Monal/Classes/MLUDPLogger.m b/Monal/Classes/MLUDPLogger.m index 6bc7bf343b..4dbfd6ea44 100644 --- a/Monal/Classes/MLUDPLogger.m +++ b/Monal/Classes/MLUDPLogger.m @@ -57,7 +57,7 @@ -(NSData*) gzipDeflate:(NSData*) data strm.opaque = Z_NULL; strm.total_out = 0; strm.next_in=(Bytef *)[data bytes]; - strm.avail_in = [data length]; + strm.avail_in = (unsigned int)[data length]; // Compresssion Levels: // Z_NO_COMPRESSION @@ -75,7 +75,7 @@ -(NSData*) gzipDeflate:(NSData*) data [compressed increaseLengthBy: 16384]; strm.next_out = [compressed mutableBytes] + strm.total_out; - strm.avail_out = [compressed length] - strm.total_out; + strm.avail_out = (unsigned int)([compressed length] - strm.total_out); deflate(&strm, Z_FINISH); diff --git a/Monal/Classes/MLWelcomeViewController.m b/Monal/Classes/MLWelcomeViewController.m index 6c6b37f64e..a73fca4ad9 100644 --- a/Monal/Classes/MLWelcomeViewController.m +++ b/Monal/Classes/MLWelcomeViewController.m @@ -93,7 +93,19 @@ - (void)showIntro { intro.skipButtonAlignment = EAViewAlignmentCenter; intro.skipButtonY = 100.f; intro.pageControlY = 120.0f; - intro.backgroundColor = [UIColor groupTableViewBackgroundColor]; + if (@available(iOS 13.0, *)) { +#if !TARGET_OS_MACCATALYST + intro.backgroundColor = [UIColor groupTableViewBackgroundColor]; +#else + intro.backgroundColor = [UIColor systemGroupedBackgroundColor]; +#endif + } else { +#if !TARGET_OS_MACCATALYST + intro.backgroundColor = [UIColor groupTableViewBackgroundColor]; +#else + intro.backgroundColor = [UIColor systemGroupedBackgroundColor]; +#endif + } [intro.skipButton setTitleColor:[UIColor monaldarkGreen] forState:UIControlStateNormal]; [intro setDelegate:self]; intro.pageControl.currentPageIndicatorTintColor = [UIColor monaldarkGreen]; diff --git a/Monal/Classes/MLXMLNode.m b/Monal/Classes/MLXMLNode.m index eda70cae9b..088000ff7f 100644 --- a/Monal/Classes/MLXMLNode.m +++ b/Monal/Classes/MLXMLNode.m @@ -78,19 +78,25 @@ +(void) nowIdle:(NSNotification*) notification #endif } --(id) init +-(void) internalInit { - self = [super init]; _attributes = [[NSMutableDictionary alloc] init]; _children = [[NSMutableArray alloc] init]; _data = nil; self.cache = [[NSMutableDictionary alloc] init]; +} + +-(id) init +{ + self = [super init]; + [self internalInit]; return self; } -(id) initWithElement:(NSString*) element { - self = [self init]; + self = [super init]; + [self internalInit]; _element = [element copy]; return self; } diff --git a/Monal/Classes/MLXMPPManager.h b/Monal/Classes/MLXMPPManager.h index 4cfde922c7..953ca4d997 100644 --- a/Monal/Classes/MLXMPPManager.h +++ b/Monal/Classes/MLXMPPManager.h @@ -7,7 +7,9 @@ // #import -#import "xmpp.h" + +@class xmpp; +@class MLContact; /** A singleton to control all of the active XMPP connections @@ -17,10 +19,9 @@ dispatch_source_t _pinger; } -+ (MLXMPPManager* )sharedInstance; ++(MLXMPPManager*) sharedInstance; -(BOOL) allAccountsIdle; --(void) configureBackgroundFetchingTask; #pragma mark connectivity /** @@ -112,7 +113,7 @@ /** hangup on a contact from an account */ --(void) hangupContact:(NSDictionary*) contact; +-(void) hangupContact:(MLContact*) contact; -(void) approveContact:(MLContact*) contact; @@ -132,28 +133,8 @@ Sends a message to a specified contact in account. Calls completion handler on s withCompletionHandler:(void (^)(BOOL success, NSString *messageId)) completion; -(void) sendChatState:(BOOL) isTyping fromAccount:(NSString*) accountNo toJid:(NSString*) jid; - -/** - uploads the selected png image Data as [uuid].jpg - */ --(void)httpUploadJpegData:(NSData*) fileData toContact:(NSString*)contact onAccount:(NSString*) accountNo withCompletionHandler:(void (^)(NSString *url, NSError *error)) completion; - -/** - opens file and attempts to upload it - */ --(void)httpUploadFileURL:(NSURL*) fileURL toContact:(NSString*)contact onAccount:(NSString*) accountNo withCompletionHandler:(void (^)(NSString *url, NSError *error)) completion; - -/** -Attempts to upload a file to the HTTP upload service - */ --(void)httpUploadData:(NSData*) data withFilename:(NSString*) filename andType:(NSString*) contentType toContact:(NSString*) contact onAccount:(NSString*) accountNo withCompletionHandler:(void (^)(NSString *url, NSError *error)) completion; - - #pragma mark XMPP settings --(void) setStatusMessage:(NSString*) message; --(void) setAway:(BOOL) isAway; - @property (nonatomic, strong, readonly) NSMutableArray* connectedXMPP; @property (nonatomic, readonly) BOOL hasConnectivity; @@ -162,36 +143,29 @@ Attempts to upload a file to the HTTP upload service @property (nonatomic, strong) NSString *pushNode; @property (nonatomic, strong) NSString *pushSecret; -/** - updates unread - */ --(void) handleNewMessage:(NSNotification*) notification; +@property (nonatomic, readonly) BOOL isBackgrounded; /** updates delivery status after message has been sent */ -(void) handleSentMessage:(NSNotification*) notification; --(void) scheduleBackgroundFetchingTask; - --(void) incomingPushWithCompletionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler; - /** updtes client state on server as inactive */ --(void) setClientsInactive; +-(void) nowBackgrounded; /** sets client state on server as active */ --(void) setClientsActive; +-(void) nowForegrounded; -(void) pingAllAccounts; /** fetch entity software version */ --(void) getEntitySoftWareVersionForContact:(MLContact *) contact andResource:(NSString*) resource; +-(void) getEntitySoftWareVersionForContact:(MLContact*) contact andResource:(NSString*) resource; /** Iterates through set and compares with connected accounts. Removes them. useful for active chat. */ diff --git a/Monal/Classes/MLXMPPManager.m b/Monal/Classes/MLXMPPManager.m index 922d75327f..ebfb1e9ef5 100644 --- a/Monal/Classes/MLXMPPManager.m +++ b/Monal/Classes/MLXMPPManager.m @@ -6,36 +6,24 @@ // // -#import #import +#import "MLConstants.h" #import "MLXMPPManager.h" #import "DataLayer.h" #import "HelperTools.h" -#import "MonalAppDelegate.h" -#import "MLConstants.h" +#import "xmpp.h" @import Network; @import MobileCoreServices; @import SAMKeychain; -typedef void (^pushCompletion)(UIBackgroundFetchResult result); - -static NSString* kBackgroundFetchingTask = @"im.monal.fetch"; - -//this is in seconds -#define SHORT_PING 4.0 -#define LONG_PING 16.0 - static const int pingFreqencyMinutes = 5; //about the same Conversations uses @interface MLXMPPManager() { nw_path_monitor_t _path_monitor; - UIBackgroundTaskIdentifier _bgTask; - API_AVAILABLE(ios(13.0)) BGTask* _bgFetch; BOOL _hasConnectivity; - NSMutableDictionary* _pushCompletions; NSMutableArray* _connectedXMPP; } @end @@ -47,16 +35,7 @@ -(void) defaultSettings BOOL setDefaults = [[HelperTools defaultsDB] boolForKey:@"SetDefaults"]; if(!setDefaults) { - // [[HelperTools defaultsDB] setObject:@"" forKey:@"StatusMessage"]; // we dont want anything set - [[HelperTools defaultsDB] setBool:NO forKey:@"Away"]; - [[HelperTools defaultsDB] setBool:YES forKey:@"MusicStatus"]; [[HelperTools defaultsDB] setBool:YES forKey:@"Sound"]; - [[HelperTools defaultsDB] setBool:YES forKey:@"MessagePreview"]; - [[HelperTools defaultsDB] setBool:YES forKey:@"Logging"]; - - [[HelperTools defaultsDB] setBool:YES forKey:@"OfflineContact"]; - [[HelperTools defaultsDB] setBool:NO forKey:@"SortContacts"]; - [[HelperTools defaultsDB] setBool:YES forKey:@"ChatBackgrounds"]; // Privacy Settings @@ -115,6 +94,14 @@ -(void) defaultSettings // upgrade Message Settings / Privacy [self upgradeIntegerUserSettingsIfUnset:@"NotificationPrivacySetting" toDefault:DisplayNameAndMessage]; + + // upgrade filetransfer settings + [self upgradeBoolUserSettingsIfUnset:@"AutodownloadFiletransfers" toDefault:YES]; +#ifdef IS_ALPHA + [self upgradeIntegerUserSettingsIfUnset:@"AutodownloadFiletransfersMaxSize" toDefault:16*1024*1024]; // 16 MiB +#else + [self upgradeIntegerUserSettingsIfUnset:@"AutodownloadFiletransfersMaxSize" toDefault:5*1024*1024]; // 5 MiB +#endif } -(void) upgradeBoolUserSettingsIfUnset:(NSString*) settingsName toDefault:(BOOL) defaultVal @@ -159,13 +146,14 @@ + (MLXMPPManager*) sharedInstance -(id) init { - self=[super init]; + self = [super init]; _connectedXMPP = [[NSMutableArray alloc] init]; - _bgTask = UIBackgroundTaskInvalid; _hasConnectivity = NO; + _isBackgrounded = NO; [self defaultSettings]; + [self setPushNode:nil andSecret:nil]; //load push settings from defaultsDB (can be overwritten later on in mainapp, but *not* in appex) //set up regular ping dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); @@ -196,12 +184,12 @@ -(id) init dispatch_resume(_pinger); - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNewMessage:) name:kMonalNewMessageNotice object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSentMessage:) name:kMonalSentMessageNotice object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(autoJoinRoom:) name:kMLHasConnectedNotice object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sendOutbox:) name:kMonalFinishedCatchup object:nil]; + //this processes the sharesheet outbox only, the handler in the NotificationServiceExtension will do more interesting things + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(catchupFinished:) name:kMonalFinishedCatchup object:nil]; _path_monitor = nw_path_monitor_create(); nw_path_monitor_set_queue(_path_monitor, q_background); @@ -236,19 +224,14 @@ -(id) init if(!wasIdle) { DDLogVerbose(@"scheduling background fetching task to start app in background once our connectivity gets restored"); - [self scheduleBackgroundFetchingTask]; //this will automatically start the app if connectivity gets restored + //this will automatically start the app if connectivity gets restored + [[NSNotificationCenter defaultCenter] postNotificationName:kScheduleBackgroundFetchingTask object:nil]; } } } }); nw_path_monitor_start(_path_monitor); - //this is only for debugging purposes, the real handler has to be added to the NotificationServiceExtension - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(catchupFinished:) name:kMonalFinishedCatchup object:nil]; - - //process idle state changes - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowIdle:) name:kMonalIdle object:nil]; - return self; } @@ -269,13 +252,11 @@ -(NSArray*) connectedXMPP -(void) catchupFinished:(NSNotification*) notification { - DDLogVerbose(@"### MAM/SMACKS CATCHUP FINISHED ###"); -} - --(void) nowIdle:(NSNotification*) notification -{ - DDLogVerbose(@"### SOME ACCOUNT CHANGED TO IDLE STATE ###"); - [self checkIfBackgroundTaskIsStillNeeded]; + xmpp* account = notification.object; + DDLogInfo(@"### MAM/SMACKS CATCHUP FINISHED FOR ACCOUNT NO %@ ###", account.accountNo); + + //send sharesheet outbox + [self sendOutboxForAccount:account]; } -(BOOL) allAccountsIdle @@ -286,225 +267,19 @@ -(BOOL) allAccountsIdle return YES; } --(void) checkIfBackgroundTaskIsStillNeeded -{ - if([self allAccountsIdle]) - { - //remove syncError notification because all accounts are idle and fully synced now - [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[@"syncError"]]; - - if(![HelperTools isAppExtension]) - { - //use a synchronized block to disconnect only once - @synchronized(self) { - DDLogVerbose(@"### NOT EXTENSION --> checking if background is still needed ###"); - BOOL background = [HelperTools isInBackground]; - if(background) - { - DDLogInfo(@"### All accounts idle, disconnecting and stopping all background tasks ###"); - [DDLog flushLog]; - [self disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking - [HelperTools dispatchSyncReentrant:^{ - BOOL stopped = NO; - if(_bgTask != UIBackgroundTaskInvalid) - { - DDLogVerbose(@"stopping UIKit _bgTask"); - [DDLog flushLog]; - [[UIApplication sharedApplication] endBackgroundTask:_bgTask]; - _bgTask = UIBackgroundTaskInvalid; - stopped = YES; - } - if(_bgFetch) - { - DDLogVerbose(@"stopping backgroundFetchingTask"); - [DDLog flushLog]; - [_bgFetch setTaskCompletedWithSuccess:YES]; - _bgFetch = nil; - stopped = YES; - } - if(!stopped) - DDLogVerbose(@"no background tasks running, nothing to stop"); - [DDLog flushLog]; - } onQueue:dispatch_get_main_queue()]; - } - if([_pushCompletions count]) - { - //we don't need to call disconnectAll if we are in background here, because we already did this in the if above (don't reorder these 2 ifs!) - DDLogInfo(@"### All accounts idle, calling push completion handlers ###"); - [DDLog flushLog]; - for(NSString* completionId in _pushCompletions) - { - //cancel running timer and push completion handler - ((monal_void_block_t)_pushCompletions[completionId][@"timer"])(); - ((pushCompletion)_pushCompletions[completionId][@"handler"])(UIBackgroundFetchResultNewData); - [_pushCompletions removeObjectForKey:completionId]; - } - } - } - } - else - DDLogVerbose(@"### IN EXTENSION --> ignoring in MLXMPPManager ###"); - } -} - --(void) addBackgroundTask -{ - if(![HelperTools isAppExtension]) - { - dispatch_async(dispatch_get_main_queue(), ^{ - //start indicating we want to do work even when the app is put into background - if(_bgTask == UIBackgroundTaskInvalid) - { - _bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) { - DDLogWarn(@"BG WAKE EXPIRING"); - [DDLog flushLog]; - - [self disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking - - [HelperTools postSendingErrorNotification]; - - //schedule a BGProcessingTaskRequest to process this further as soon as possible - if(@available(iOS 13.0, *)) - { - DDLogInfo(@"calling scheduleBackgroundFetchingTask"); - [self scheduleBackgroundFetchingTask]; - } - - [DDLog flushLog]; - [[UIApplication sharedApplication] endBackgroundTask:_bgTask]; - _bgTask = UIBackgroundTaskInvalid; - }]; - } - }); - } -} - --(void) handleBackgroundFetchingTask:(BGTask*) task API_AVAILABLE(ios(13.0)) -{ - DDLogVerbose(@"RUNNING BGTASK"); - _bgFetch = task; - __weak BGTask* weakTask = task; - task.expirationHandler = ^{ - DDLogWarn(@"*** BGTASK EXPIRED ***"); - _bgFetch = nil; - [self disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking - [HelperTools postSendingErrorNotification]; - [weakTask setTaskCompletedWithSuccess:NO]; - [self scheduleBackgroundFetchingTask]; //schedule new one if neccessary - [DDLog flushLog]; - }; - - if(_hasConnectivity) - { - for(xmpp* xmppAccount in [self connectedXMPP]) - { - //try to send a ping. if it fails, it will reconnect - DDLogVerbose(@"manager pinging"); - [xmppAccount sendPing:SHORT_PING]; //short ping timeout to quickly check if connectivity is still okay - } - } - else - DDLogWarn(@"BGTASK has *no* connectivity? That's strange!"); - - //log bgtask ticks - unsigned long tick = 0; - while(1) - { - DDLogVerbose(@"BGTASK TICK: %lu", tick++); - [DDLog flushLog]; - [NSThread sleepForTimeInterval:1.000]; - } -} - --(void) configureBackgroundFetchingTask -{ - if(![HelperTools isAppExtension]) - { - if(@available(iOS 13.0, *)) - { - [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:kBackgroundFetchingTask usingQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) launchHandler:^(BGTask *task) { - DDLogVerbose(@"RUNNING BGTASK LAUNCH HANDLER"); - [self handleBackgroundFetchingTask:task]; - }]; - } else { - // No fallback unfortunately - } - } -} - --(void) scheduleBackgroundFetchingTask -{ - if(![HelperTools isAppExtension]) - { - if(@available(iOS 13.0, *)) - { - [HelperTools dispatchSyncReentrant:^{ - NSError *error = NULL; - // cancel existing task (if any) - [BGTaskScheduler.sharedScheduler cancelTaskRequestWithIdentifier:kBackgroundFetchingTask]; - // new task - //BGAppRefreshTaskRequest* request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:kBackgroundFetchingTask]; - BGProcessingTaskRequest* request = [[BGProcessingTaskRequest alloc] initWithIdentifier:kBackgroundFetchingTask]; - //do the same like the corona warn app from germany which leads to this hint: https://developer.apple.com/forums/thread/134031 - request.requiresNetworkConnectivity = YES; - request.requiresExternalPower = NO; - request.earliestBeginDate = nil; - //request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:40]; //begin nearly immediately (if we have network connectivity) - BOOL success = [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error]; - if(!success) { - // Errorcodes https://stackoverflow.com/a/58224050/872051 - DDLogError(@"Failed to submit BGTask request: %@", error); - } else { - DDLogVerbose(@"Success submitting BGTask request %@", request); - } - } onQueue:dispatch_get_main_queue()]; - } - else - { - // No fallback unfortunately - DDLogError(@"BGTask needed but NOT supported!"); - } - } -} - --(void) incomingPushWithCompletionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler -{ - DDLogInfo(@"got incomingPushWithCompletionHandler"); - if(![HelperTools isInBackground]) - { - DDLogError(@"Ignoring incomingPushWithCompletionHandler: because app is in FG!"); - completionHandler(UIBackgroundFetchResultNoData); - return; - } - // should any accounts reconnect? - [self pingAllAccounts]; - - //register push completion handler and associated timer - NSString* completionId = [[NSUUID UUID] UUIDString]; - _pushCompletions[completionId] = @{ - @"handler": completionHandler, - @"timer": [HelperTools startTimer:28.0 withHandler:^{ - DDLogWarn(@"### Push timer triggered!! ###"); - [_pushCompletions removeObjectForKey:completionId]; - completionHandler(UIBackgroundFetchResultFailed); - }] - }; -} +#pragma mark - app state -#pragma mark - client state - --(void) setClientsInactive +-(void) nowBackgrounded { - [self addBackgroundTask]; + _isBackgrounded = YES; for(xmpp* xmppAccount in [self connectedXMPP]) [xmppAccount setClientInactive]; - [self checkIfBackgroundTaskIsStillNeeded]; } --(void) setClientsActive +-(void) nowForegrounded { - [self addBackgroundTask]; + _isBackgrounded = NO; //*** we don't need to check for a running service extension here because the appdelegate does this already for us *** @@ -517,6 +292,8 @@ -(void) setClientsActive } } +#pragma mark - Connection related + -(void) pingAllAccounts { for(xmpp* xmppAccount in [self connectedXMPP]) @@ -562,8 +339,6 @@ -(xmpp*) getConnectedAccountForID:(NSString*) accountNo return nil; } -#pragma mark - Connection related - -(void) connectAccount:(NSString*) accountNo { NSDictionary* account = [[DataLayer sharedInstance] detailsForAccount:accountNo]; @@ -605,6 +380,7 @@ -(void) connectAccountWithDictionary:(NSDictionary*)account xmpp* xmppAccount = [[xmpp alloc] initWithServer:server andIdentity:identity andAccountNo:[NSString stringWithFormat:@"%@",[account objectForKey:kAccountID]]]; xmppAccount.pushNode = self.pushNode; xmppAccount.pushSecret = self.pushSecret; + xmppAccount.statusMessage = [account objectForKey:@"statusMessage"]; if(xmppAccount) { @@ -666,21 +442,24 @@ -(void) logoutAll -(void) disconnectAll { + DDLogVerbose(@"manager disconnecAll"); for(xmpp* xmppAccount in [self connectedXMPP]) { //disconnect to prevent endless loops trying to connect DDLogVerbose(@"manager disconnecting"); [xmppAccount disconnect]; } + DDLogVerbose(@"manager disconnecAll done"); } -(void) connectIfNecessary { - [self addBackgroundTask]; + DDLogVerbose(@"manager connectIfNecessary"); NSArray* enabledAccountList = [[DataLayer sharedInstance] enabledAccountList]; for(NSDictionary* account in enabledAccountList) { [self connectAccountWithDictionary:account]; } + DDLogVerbose(@"manager connectIfNecessary done"); } -(void) updatePassword:(NSString *) password forAccount:(NSString *) accountNo @@ -702,28 +481,36 @@ -(void) sendMessageAndAddToHistory:(NSString*) message toContact:(NSString*) rec NSAssert(account, @"Account should not be nil"); NSAssert(contact, @"Contact should not be nil"); + NSString* messageType = kMessageTypeText; + if(isUpload) + messageType = kMessageTypeFiletransfer; + // Save message to history - [[DataLayer sharedInstance] addMessageHistoryFrom:account.connectionProperties.identity.jid - to:recipient - forAccount:accountID - withMessage:message - actuallyFrom:(isMUC ? contact.accountNickInGroup : account.connectionProperties.identity.jid) - withId:msgid - encrypted:encrypted - withCompletion:^(BOOL successHist, NSString* messageTypeHist, NSNumber* messageDBId) { - // Send message - if(successHist) { - DDLogInfo(@"Message added to history with id %ld, now sending...", (long)[messageDBId intValue]); - [self sendMessage:message toContact:recipient fromAccount:accountID isEncrypted:encrypted isMUC:isMUC isUpload:NO messageId:msgid withCompletionHandler:^(BOOL successSend, NSString *messageIdSend) { - if(successSend) - completion(successSend, messageIdSend); - }]; - DDLogVerbose(@"Notifying active chats of change for contact %@", contact); - [[NSNotificationCenter defaultCenter] postNotificationName:kMLMessageSentToContact object:self userInfo:@{@"contact":contact}]; - } - else - DDLogError(@"Could not add message to history!"); - }]; + NSNumber* messageDBId = [[DataLayer sharedInstance] + addMessageHistoryFrom:account.connectionProperties.identity.jid + to:recipient + forAccount:accountID + withMessage:message + actuallyFrom:(isMUC ? contact.accountNickInGroup : account.connectionProperties.identity.jid) + withId:msgid + encrypted:encrypted + messageType:messageType + mimeType:nil + size:nil + ]; + // Send message + if(messageDBId != nil) + { + DDLogInfo(@"Message added to history with id %ld, now sending...", (long)[messageDBId intValue]); + [self sendMessage:message toContact:recipient fromAccount:accountID isEncrypted:encrypted isMUC:isMUC isUpload:NO messageId:msgid withCompletionHandler:^(BOOL successSend, NSString *messageIdSend) { + if(successSend) + completion(successSend, messageIdSend); + }]; + DDLogVerbose(@"Notifying active chats of change for contact %@", contact); + [[NSNotificationCenter defaultCenter] postNotificationName:kMLMessageSentToContact object:self userInfo:@{@"contact":contact}]; + } + else + DDLogError(@"Could not add message to history!"); } -(void)sendMessage:(NSString*) message toContact:(NSString*)contact fromAccount:(NSString*) accountNo isEncrypted:(BOOL) encrypted isMUC:(BOOL) isMUC isUpload:(BOOL) isUpload messageId:(NSString *) messageId withCompletionHandler:(void (^)(BOOL success, NSString *messageId)) completion @@ -747,54 +534,6 @@ -(void) sendChatState:(BOOL) isTyping fromAccount:(NSString*) accountNo toJid:(N [account sendChatState:isTyping toJid:jid]; } - -#pragma mark - HTTP upload - --(void) httpUploadJpegData:(NSData*) fileData toContact:(NSString*)contact onAccount:(NSString*) accountNo withCompletionHandler:(void (^)(NSString *url, NSError *error)) completion{ - - NSString *fileName = [NSString stringWithFormat:@"%@.jpg",[NSUUID UUID].UUIDString]; - - //get file type - CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)@"jpg", NULL); - NSString *mimeType = (__bridge_transfer NSString *)(UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType)); - CFRelease(UTI); - [self httpUploadData:fileData withFilename:fileName andType:mimeType toContact:contact onAccount:accountNo withCompletionHandler:completion]; -} - --(void) httpUploadFileURL:(NSURL*) fileURL toContact:(NSString*)contact onAccount:(NSString*) accountNo withCompletionHandler:(void (^)(NSString *url, NSError *error)) completion{ - - //get file name - NSString *fileName = fileURL.pathComponents.lastObject; - - //get file type - CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileURL.pathExtension, NULL); - NSString *mimeType = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType)); - CFRelease(UTI); - //get data - NSData *fileData = [[NSData alloc] initWithContentsOfURL:fileURL]; - - [self httpUploadData:fileData withFilename:fileName andType:mimeType toContact:contact onAccount:accountNo withCompletionHandler:completion]; -} - - --(void)httpUploadData:(NSData *)data withFilename:(NSString*) filename andType:(NSString*)contentType toContact:(NSString*)contact onAccount:(NSString*) accountNo withCompletionHandler:(void (^)(NSString *url, NSError *error)) completion -{ - if(!data || !filename || !contentType || !contact || !accountNo) - { - NSError *error = [NSError errorWithDomain:@"Empty" code:0 userInfo:@{}]; - if(completion) completion(nil, error); - return; - } - - xmpp* account=[self getConnectedAccountForID:accountNo]; - if(account) - { - NSDictionary *params =@{kData:data,kFileName:filename, kContentType:contentType, kContact:contact}; - [account requestHTTPSlotWithParams:params andCompletion:completion]; - } -} - - #pragma mark - getting details -(NSString*) getAccountNameForConnectedRow:(NSInteger) row @@ -904,7 +643,7 @@ -(void) callContact:(MLContact*) contact -(void) hangupContact:(MLContact*) contact { - xmpp* account =[self getConnectedAccountForID:contact.accountId]; + xmpp* account = [self getConnectedAccountForID:contact.accountId]; [account hangup:contact]; } @@ -922,33 +661,7 @@ -(void) handleCall:(NSDictionary *) userDic withResponse:(BOOL) accept } - -#pragma mark - XMPP settings - --(void) setStatusMessage:(NSString*) message -{ - for(xmpp* xmppAccount in [self connectedXMPP]) - [xmppAccount setStatusMessageText:message]; -} - --(void) setAway:(BOOL) isAway -{ - for(xmpp* xmppAccount in [self connectedXMPP]) - [xmppAccount setAway:isAway]; -} - #pragma mark message signals --(void) handleNewMessage:(NSNotification *)notification -{ - if(![HelperTools isAppExtension]) - { - dispatch_async(dispatch_get_main_queue(), ^{ - MonalAppDelegate* appDelegate = (MonalAppDelegate*) [UIApplication sharedApplication].delegate; - [appDelegate updateUnread]; - }); - } -} - -(void) handleSentMessage:(NSNotification*) notification { @@ -1006,13 +719,13 @@ -(void) setPushNode:(NSString*) node andSecret:(NSString*) secret self.pushSecret.length ) { - DDLogInfo(@"push token valid, current push settings: node=%@, secret=%@", [MLXMPPManager sharedInstance].pushNode, [MLXMPPManager sharedInstance].pushSecret); + DDLogInfo(@"push token valid, current push settings: node=%@, secret=%@", self.pushNode, self.pushSecret); self.hasAPNSToken = YES; } else { self.hasAPNSToken = NO; - DDLogWarn(@"push token invalid, current push settings: node=%@, secret=%@", [MLXMPPManager sharedInstance].pushNode, [MLXMPPManager sharedInstance].pushSecret); + DDLogWarn(@"push token invalid, current push settings: node=%@, secret=%@", self.pushNode, self.pushSecret); } //only try to enable push if we have a node and secret value @@ -1027,12 +740,7 @@ -(void) setPushNode:(NSString*) node andSecret:(NSString*) secret #pragma mark - share sheet added --(void) sendOutbox: (NSNotification *) notification { - xmpp* account = notification.object; - [self sendOutboxForAccount:account]; -} - -- (void) sendOutboxForAccount:(xmpp*) account +-(void) sendOutboxForAccount:(xmpp*) account { NSMutableArray* outbox = [[[HelperTools defaultsDB] objectForKey:@"outbox"] mutableCopy]; NSMutableArray* outboxClean = [[[HelperTools defaultsDB] objectForKey:@"outbox"] mutableCopy]; diff --git a/Monal/Classes/Monal-Bridging-Header.h b/Monal/Classes/Monal-Bridging-Header.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Monal/Classes/MonalAppDelegate.h b/Monal/Classes/MonalAppDelegate.h index accc74f34c..2944406918 100644 --- a/Monal/Classes/MonalAppDelegate.h +++ b/Monal/Classes/MonalAppDelegate.h @@ -15,7 +15,11 @@ @import UserNotifications; +#if !TARGET_OS_MACCATALYST @interface MonalAppDelegate : UIResponder +#else +@interface MonalAppDelegate : UIResponder +#endif @property (nonatomic, strong) UIWindow* window; @property (nonatomic, strong) DDFileLogger* fileLogger; diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index 8b57833d75..b7b6e79069 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -6,8 +6,9 @@ // Copyright __MyCompanyName__ 2008. All rights reserved. // -#import "MonalAppDelegate.h" +#import +#import "MonalAppDelegate.h" #import "CallViewController.h" #import "MLConstants.h" #import "HelperTools.h" @@ -18,18 +19,35 @@ #import "ActiveChatsViewController.h" #import "IPC.h" #import "MLProcessLock.h" +#import "MLFiletransfer.h" +#import "xmpp.h" @import NotificationBannerSwift; #import "MLXMPPManager.h" #import "UIColor+Theme.h" +typedef void (^pushCompletion)(UIBackgroundFetchResult result); +static NSString* kBackgroundFetchingTask = @"im.monal.fetch"; + @interface MonalAppDelegate() +{ + NSMutableDictionary* _pushCompletions; + UIBackgroundTaskIdentifier _bgTask; + API_AVAILABLE(ios(13.0)) BGTask* _bgFetch; +} @property (nonatomic, weak) ActiveChatsViewController* activeChats; @end @implementation MonalAppDelegate +-(id) init +{ + self = [super init]; + _bgTask = UIBackgroundTaskInvalid; + return self; +} + #pragma mark - APNS notificaion -(void) application:(UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*) deviceToken @@ -77,7 +95,7 @@ -(void) pushRegistry:(PKPushRegistry*) registry didReceiveIncomingPushWithPayloa if(@available(iOS 13.0, *)) DDLogError(@"Voip push shouldnt arrive on ios13."); else - [[MLXMPPManager sharedInstance] incomingPushWithCompletionHandler:^(UIBackgroundFetchResult result) { + [self incomingPushWithCompletionHandler:^(UIBackgroundFetchResult result) { completion(); }]; } @@ -102,13 +120,13 @@ -(void) updateUnread { //make sure unread badge matches application badge NSNumber* unreadMsgCnt = [[DataLayer sharedInstance] countUnreadMessages]; - [HelperTools dispatchSyncReentrant:^{ + dispatch_async(dispatch_get_main_queue(), ^{ NSInteger unread = 0; if(unreadMsgCnt) unread = [unreadMsgCnt integerValue]; DDLogInfo(@"Updating unread badge to: %ld", (long)unread); [UIApplication sharedApplication].applicationIconBadgeNumber = unread; - } onQueue:dispatch_get_main_queue()]; + }); } #pragma mark - app life cycle @@ -126,21 +144,14 @@ -(BOOL) application:(UIApplication*) application willFinishLaunchingWithOptions: if(![[HelperTools defaultsDB] boolForKey:@"DefaulsMigratedToAppGroup"]) { DDLogInfo(@"Migrating [NSUserDefaults standardUserDefaults] to app group container..."); - [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"MessagePreview"] forKey:@"MessagePreview"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"ChatBackgrounds"] forKey:@"ChatBackgrounds"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"ShowGeoLocation"] forKey:@"ShowGeoLocation"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"Sound"] forKey:@"Sound"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"SetDefaults"] forKey:@"SetDefaults"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"HasSeenIntro"] forKey:@"HasSeenIntro"]; - [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"HasSeeniOS13Message"] forKey:@"HasSeeniOS13Message"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"HasSeenLogin"] forKey:@"HasSeenLogin"]; - [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"SortContacts"] forKey:@"SortContacts"]; - [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"OfflineContact"] forKey:@"OfflineContact"]; - [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"Logging"] forKey:@"Logging"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"ShowImages"] forKey:@"ShowImages"]; - [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"Away"] forKey:@"Away"]; [[HelperTools defaultsDB] setBool:[[NSUserDefaults standardUserDefaults] boolForKey:@"HasUpgradedPushiOS13"] forKey:@"HasUpgradedPushiOS13"]; - [[HelperTools defaultsDB] setObject:[[NSUserDefaults standardUserDefaults] objectForKey:@"StatusMessage"] forKey:@"StatusMessage"]; [[HelperTools defaultsDB] setObject:[[NSUserDefaults standardUserDefaults] objectForKey:@"BackgroundImage"] forKey:@"BackgroundImage"]; [[HelperTools defaultsDB] setObject:[[NSUserDefaults standardUserDefaults] objectForKey:@"AlertSoundFile"] forKey:@"AlertSoundFile"]; [[HelperTools defaultsDB] setObject:[[NSUserDefaults standardUserDefaults] objectForKey:@"pushSecret"] forKey:@"pushSecret"]; @@ -159,6 +170,12 @@ -(BOOL) application:(UIApplication*) application willFinishLaunchingWithOptions: [MLProcessLock lock]; [[IPC sharedInstance] sendMessage:@"Monal.disconnectAll" withData:nil to:@"NotificationServiceExtension"]; + //do MLFiletransfer cleanup tasks (do this in a new thread to parallelize it with our ping to the appex and don't slow down app startup) + //this will also migrate our old image cache to new MLFiletransfer cache + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [MLFiletransfer doStartupCleanup]; + }); + //only proceed with launching if the NotificationServiceExtension is *not* running if([MLProcessLock checkRemoteRunning:@"NotificationServiceExtension"]) { @@ -195,7 +212,13 @@ - (BOOL)application:(UIApplication*) application didFinishLaunchingWithOptions:( #endif } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scheduleBackgroundFetchingTask) name:kScheduleBackgroundFetchingTask object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowIdle:) name:kMonalIdle object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showConnectionStatus:) name:kXMPPError object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateUnread) name:kMonalNewMessageNotice object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateUnread) name:kMonalUpdateUnread object:nil]; + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; @@ -252,24 +275,20 @@ - (BOOL)application:(UIApplication*) application didFinishLaunchingWithOptions:( [[UINavigationBar appearance] setScrollEdgeAppearance:appearance]; [[UINavigationBar appearance] setStandardAppearance:appearance]; #if TARGET_OS_MACCATALYST - self.window.windowScene.titlebar.titleVisibility=UITitlebarTitleVisibilityHidden; + self.window.windowScene.titlebar.titleVisibility = UITitlebarTitleVisibilityHidden; #endif } [[UINavigationBar appearance] setPrefersLargeTitles:YES]; [[UITabBar appearance] setTintColor:monaldarkGreen]; - //update logs if needed - if(![[HelperTools defaultsDB] boolForKey:@"Logging"]) - [[DataLayer sharedInstance] messageHistoryCleanAll]; - //handle message notifications by initializing the MLNotificationManager [MLNotificationManager sharedInstance]; //register BGTask if(@available(iOS 13.0, *)) { - DDLogInfo(@"calling MLXMPPManager configureBackgroundFetchingTask"); - [[MLXMPPManager sharedInstance] configureBackgroundFetchingTask]; + DDLogInfo(@"calling MonalAppDelegate configureBackgroundFetchingTask"); + [self configureBackgroundFetchingTask]; } NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary]; @@ -279,14 +298,46 @@ - (BOOL)application:(UIApplication*) application didFinishLaunchingWithOptions:( DDLogInfo(@"App started: %@", [NSString stringWithFormat:NSLocalizedString(@"Version %@ (%@ %@ UTC)", @ ""), version, buildDate, buildTime]); //should any accounts connect? - [[MLXMPPManager sharedInstance] connectIfNecessary]; + [self connectIfNecessary]; //handle IPC messages (this should be done *after* calling connectIfNecessary to make sure any disconnectAll messages are handled properly [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(incomingIPC:) name:kMonalIncomingIPC object:nil]; +#if TARGET_OS_MACCATALYST + //handle catalyst foregrounding/backgrounding of window + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowHandling:) name:@"NSWindowDidResignKeyNotification" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowHandling:) name:@"NSWindowDidBecomeKeyNotification" object:nil]; +#endif + + //init background/foreground status + if([UIApplication sharedApplication].applicationState==UIApplicationStateBackground) + [[MLXMPPManager sharedInstance] nowBackgrounded]; + else + [[MLXMPPManager sharedInstance] nowForegrounded]; + return YES; } +#if TARGET_OS_MACCATALYST +-(void) windowHandling:(NSNotification*) notification +{ + if([notification.name isEqualToString:@"NSWindowDidResignKeyNotification"]) + { + DDLogInfo(@"Window lost focus (key window)..."); + [self updateUnread]; + [self addBackgroundTask]; + [[MLXMPPManager sharedInstance] nowBackgrounded]; + [self checkIfBackgroundTaskIsStillNeeded]; + } + else if([notification.name isEqualToString:@"NSWindowDidBecomeKeyNotification"]) + { + DDLogInfo(@"Window got focus (key window)..."); + [self addBackgroundTask]; + [[MLXMPPManager sharedInstance] nowForegrounded]; + } +} +#endif + -(void) incomingIPC:(NSNotification*) notification { NSDictionary* message = notification.userInfo; @@ -304,7 +355,7 @@ -(void) incomingIPC:(NSNotification*) notification { DDLogInfo(@"Got connectIfNecessary IPC message"); //(re)connect all accounts - [[MLXMPPManager sharedInstance] connectIfNecessary]; + [self connectIfNecessary]; } } @@ -352,6 +403,7 @@ -(void) handleURL:(NSURL *) url { } [[DataLayer sharedInstance] addActiveBuddies:contact.contactJid forAccount:contact.accountId]; + //no success may mean its already there dispatch_async(dispatch_get_main_queue(), ^{ [(ActiveChatsViewController *) self.activeChats presentChatWithRow:contact]; @@ -378,7 +430,7 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDiction -(void) application:(UIApplication*) application didReceiveRemoteNotification:(NSDictionary*) userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler { DDLogVerbose(@"got didReceiveRemoteNotification: %@", userInfo); - [[MLXMPPManager sharedInstance] incomingPushWithCompletionHandler:completionHandler]; + [self incomingPushWithCompletionHandler:completionHandler]; } - (void)userNotificationCenter:(UNUserNotificationCenter*) center willPresentNotification:(UNNotification*) notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options)) completionHandler; @@ -397,7 +449,7 @@ -(void) userNotificationCenter:(UNUserNotificationCenter*) center didReceiveNoti if([response.notification.request.content.categoryIdentifier isEqualToString:@"message"]) { DDLogVerbose(@"notification action triggered for %@", response.notification.request.content.userInfo); - [[MLXMPPManager sharedInstance] connectIfNecessary]; + [self connectIfNecessary]; NSString* from = response.notification.request.content.userInfo[@"from"]; NSString* accountId = response.notification.request.content.userInfo[@"accountId"]; @@ -466,7 +518,7 @@ -(void) applicationDidReceiveMemoryWarning:(UIApplication *)application - (void) applicationWillEnterForeground:(UIApplication *)application { - DDLogVerbose(@"Entering FG"); + DDLogInfo(@"Entering FG"); [[IPC sharedInstance] sendMessage:@"Monal.disconnectAll" withData:nil to:@"NotificationServiceExtension"]; //only proceed with foregrounding if the NotificationServiceExtension is not running @@ -479,7 +531,8 @@ - (void) applicationWillEnterForeground:(UIApplication *)application //trigger view updates (this has to be done because the NotificationServiceExtension could have updated the database some time ago) [[NSNotificationCenter defaultCenter] postNotificationName:kMonalRefresh object:self userInfo:nil]; - [[MLXMPPManager sharedInstance] setClientsActive]; + [self addBackgroundTask]; + [[MLXMPPManager sharedInstance] nowForegrounded]; } -(void) applicationWillResignActive:(UIApplication *)application @@ -502,25 +555,27 @@ -(void) applicationDidEnterBackground:(UIApplication*) application { UIApplicationState state = [application applicationState]; if(state == UIApplicationStateInactive) - DDLogVerbose(@"Screen lock / incoming call"); + DDLogInfo(@"Screen lock / incoming call"); else if(state == UIApplicationStateBackground) - DDLogVerbose(@"Entering BG"); + DDLogInfo(@"Entering BG"); [self updateUnread]; - [[MLXMPPManager sharedInstance] setClientsInactive]; + [self addBackgroundTask]; + [[MLXMPPManager sharedInstance] nowBackgrounded]; + [self checkIfBackgroundTaskIsStillNeeded]; } -(void) applicationWillTerminate:(UIApplication *)application { DDLogWarn(@"|~~| T E R M I N A T I N G |~~|"); [self updateUnread]; - DDLogVerbose(@"|~~| 25%% |~~|"); + DDLogInfo(@"|~~| 25%% |~~|"); [[HelperTools defaultsDB] synchronize]; - DDLogVerbose(@"|~~| 50%% |~~|"); - [[MLXMPPManager sharedInstance] setClientsInactive]; - DDLogVerbose(@"|~~| 75%% |~~|"); - [[MLXMPPManager sharedInstance] scheduleBackgroundFetchingTask]; //make sure delivery will be attempted, if needed - DDLogVerbose(@"|~~| T E R M I N A T E D |~~|"); + DDLogInfo(@"|~~| 50%% |~~|"); + [[MLXMPPManager sharedInstance] nowBackgrounded]; + DDLogInfo(@"|~~| 75%% |~~|"); + [self scheduleBackgroundFetchingTask]; //make sure delivery will be attempted, if needed + DDLogInfo(@"|~~| T E R M I N A T E D |~~|"); [DDLog flushLog]; //give the server some more time to send smacks acks (it doesn't matter if we get killed because of this, we're terminating anyways) usleep(1000000); @@ -530,15 +585,14 @@ -(void) applicationWillTerminate:(UIApplication *)application -(void) showConnectionStatus:(NSNotification*) notification { - if([HelperTools isInBackground]) - DDLogDebug(@"not surfacing errors in the background because they are super common"); - else + //this will show an error banner but only if our app is foregrounded + if(![HelperTools isInBackground]) { dispatch_async(dispatch_get_main_queue(), ^{ - NSArray* payload = [notification.object copy]; - NSString* message = payload[1]; // this is just the way i set it up a dic might better - xmpp *xmppAccount = payload.firstObject; - NotificationBanner* banner = [[NotificationBanner alloc] initWithTitle:xmppAccount.connectionProperties.identity.jid subtitle:message leftView:nil rightView:nil style:BannerStyleInfo colors:nil]; + xmpp* xmppAccount = notification.object; + if(![notification.userInfo[@"isSevere"] boolValue]) + DDLogError(@"Minor XMPP Error(%@): %@", xmppAccount.connectionProperties.identity.jid, notification.userInfo[@"message"]); + NotificationBanner* banner = [[NotificationBanner alloc] initWithTitle:xmppAccount.connectionProperties.identity.jid subtitle:notification.userInfo[@"message"] leftView:nil rightView:nil style:BannerStyleInfo colors:nil]; NotificationBannerQueue* queue = [[NotificationBannerQueue alloc] initWithMaxBannersOnScreenSimultaneously:2]; [banner showWithQueuePosition:QueuePositionFront bannerPosition:BannerPositionTop queue:queue on:nil]; }); @@ -578,7 +632,6 @@ - (void)buildMenuWithBuilder:(id)builder } } - -(void) showNew { [self.activeChats showNew]; } @@ -595,5 +648,224 @@ -(void) showDetails { [self.activeChats showDetails]; } +#pragma mark - background tasks + +-(void) nowIdle:(NSNotification*) notification +{ + DDLogInfo(@"### SOME ACCOUNT CHANGED TO IDLE STATE ###"); + [self checkIfBackgroundTaskIsStillNeeded]; +} + +-(void) checkIfBackgroundTaskIsStillNeeded +{ + if([[MLXMPPManager sharedInstance] allAccountsIdle]) + { + DDLogInfo(@"### ALL ACCOUNTS IDLE NOW ###"); + + //remove syncError notification because all accounts are idle and fully synced now + [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[@"syncError"]]; + +#if !TARGET_OS_MACCATALYST + //use a synchronized block to disconnect only once + @synchronized(self) { + DDLogInfo(@"### checking if background is still needed ###"); + BOOL background = [HelperTools isInBackground]; + if(background) + { + DDLogInfo(@"### All accounts idle, disconnecting and stopping all background tasks ###"); + [DDLog flushLog]; + [[MLXMPPManager sharedInstance] disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking + [HelperTools dispatchSyncReentrant:^{ + BOOL stopped = NO; + if(_bgTask != UIBackgroundTaskInvalid) + { + DDLogDebug(@"stopping UIKit _bgTask"); + [DDLog flushLog]; + [[UIApplication sharedApplication] endBackgroundTask:_bgTask]; + _bgTask = UIBackgroundTaskInvalid; + stopped = YES; + } + if(_bgFetch) + { + DDLogDebug(@"stopping backgroundFetchingTask"); + [DDLog flushLog]; + [_bgFetch setTaskCompletedWithSuccess:YES]; + _bgFetch = nil; + stopped = YES; + } + if(!stopped) + DDLogDebug(@"no background tasks running, nothing to stop"); + [DDLog flushLog]; + } onQueue:dispatch_get_main_queue()]; + } + if([_pushCompletions count]) + { + //we don't need to call disconnectAll if we are in background here, because we already did this in the if above (don't reorder these 2 ifs!) + DDLogInfo(@"### All accounts idle, calling push completion handlers ###"); + [DDLog flushLog]; + for(NSString* completionId in _pushCompletions) + { + //cancel running timer and push completion handler + ((monal_void_block_t)_pushCompletions[completionId][@"timer"])(); + ((pushCompletion)_pushCompletions[completionId][@"handler"])(UIBackgroundFetchResultNewData); + [_pushCompletions removeObjectForKey:completionId]; + } + } + } +#else + DDLogInfo(@"### CATALYST BUILD --> ignoring in MonalAppDelegate ###"); +#endif + } +} + +-(void) addBackgroundTask +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if(_bgTask == UIBackgroundTaskInvalid) + { + //indicate we want to do work even if the app is put into background + _bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) { + DDLogWarn(@"BG WAKE EXPIRING"); + [DDLog flushLog]; + + [[MLXMPPManager sharedInstance] disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking + + [HelperTools postSendingErrorNotification]; + + //schedule a BGProcessingTaskRequest to process this further as soon as possible + if(@available(iOS 13.0, *)) + { + DDLogInfo(@"calling scheduleBackgroundFetchingTask"); + [self scheduleBackgroundFetchingTask]; + } + + [DDLog flushLog]; + [[UIApplication sharedApplication] endBackgroundTask:_bgTask]; + _bgTask = UIBackgroundTaskInvalid; + }]; + } + }); +} + +-(void) handleBackgroundFetchingTask:(BGTask*) task API_AVAILABLE(ios(13.0)) +{ + DDLogVerbose(@"RUNNING BGTASK"); + _bgFetch = task; + __weak BGTask* weakTask = task; + task.expirationHandler = ^{ + DDLogWarn(@"*** BGTASK EXPIRED ***"); + _bgFetch = nil; + [[MLXMPPManager sharedInstance] disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking + [HelperTools postSendingErrorNotification]; + [weakTask setTaskCompletedWithSuccess:NO]; + [self scheduleBackgroundFetchingTask]; //schedule new one if neccessary + [DDLog flushLog]; + }; + + if([[MLXMPPManager sharedInstance] hasConnectivity]) + { + for(xmpp* xmppAccount in [[MLXMPPManager sharedInstance] connectedXMPP]) + { + //try to send a ping. if it fails, it will reconnect + DDLogVerbose(@"app delegate pinging"); + [xmppAccount sendPing:SHORT_PING]; //short ping timeout to quickly check if connectivity is still okay + } + } + else + DDLogWarn(@"BGTASK has *no* connectivity? That's strange!"); + + //log bgtask ticks (and stop when the task expires) + unsigned long tick = 0; + while(_bgFetch != nil) + { + DDLogVerbose(@"BGTASK TICK: %lu", tick++); + [DDLog flushLog]; + [NSThread sleepForTimeInterval:1.000]; + } +} + +-(void) configureBackgroundFetchingTask +{ + if(@available(iOS 13.0, *)) + { + [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:kBackgroundFetchingTask usingQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) launchHandler:^(BGTask *task) { + DDLogVerbose(@"RUNNING BGTASK LAUNCH HANDLER"); + [self handleBackgroundFetchingTask:task]; + }]; + } else { + // No fallback unfortunately + } +} + +-(void) scheduleBackgroundFetchingTask +{ + if(@available(iOS 13.0, *)) + { + [HelperTools dispatchSyncReentrant:^{ + NSError *error = NULL; + // cancel existing task (if any) + [BGTaskScheduler.sharedScheduler cancelTaskRequestWithIdentifier:kBackgroundFetchingTask]; + // new task + //BGAppRefreshTaskRequest* request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:kBackgroundFetchingTask]; + BGProcessingTaskRequest* request = [[BGProcessingTaskRequest alloc] initWithIdentifier:kBackgroundFetchingTask]; + //do the same like the corona warn app from germany which leads to this hint: https://developer.apple.com/forums/thread/134031 + request.requiresNetworkConnectivity = YES; + request.requiresExternalPower = NO; + request.earliestBeginDate = nil; + //request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:40]; //begin nearly immediately (if we have network connectivity) + BOOL success = [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error]; + if(!success) { + // Errorcodes https://stackoverflow.com/a/58224050/872051 + DDLogError(@"Failed to submit BGTask request: %@", error); + } else { + DDLogVerbose(@"Success submitting BGTask request %@", request); + } + } onQueue:dispatch_get_main_queue()]; + } + else + { + // No fallback unfortunately + DDLogError(@"BGTask needed but NOT supported!"); + } +} + +-(void) connectIfNecessary +{ + [self addBackgroundTask]; + [[MLXMPPManager sharedInstance] connectIfNecessary]; +} + +-(void) incomingPushWithCompletionHandler:(void (^)(UIBackgroundFetchResult result)) completionHandler +{ +#if TARGET_OS_MACCATALYST + DDLogError(@"Ignoring incomingPushWithCompletionHandler: we are a catalyst app!"); + completionHandler(UIBackgroundFetchResultNoData); + return; +#else + if(![HelperTools isInBackground]) + { + DDLogError(@"Ignoring incomingPushWithCompletionHandler: because app is in FG!"); + completionHandler(UIBackgroundFetchResultNoData); + return; + } + + DDLogInfo(@"got incomingPushWithCompletionHandler"); + + // should any accounts reconnect? + [[MLXMPPManager sharedInstance] pingAllAccounts]; + + //register push completion handler and associated timer + NSString* completionId = [[NSUUID UUID] UUIDString]; + _pushCompletions[completionId] = @{ + @"handler": completionHandler, + @"timer": [HelperTools startTimer:28.0 withHandler:^{ + DDLogWarn(@"### Push timer triggered!! ###"); + [_pushCompletions removeObjectForKey:completionId]; + completionHandler(UIBackgroundFetchResultFailed); + }] + }; +#endif +} + @end diff --git a/Monal/Classes/RTP.mm b/Monal/Classes/RTP.mm index 11936effd5..c9bc8c35fe 100644 --- a/Monal/Classes/RTP.mm +++ b/Monal/Classes/RTP.mm @@ -109,7 +109,7 @@ -(void) listenThread // debug_NSLog(@"Got packet"); - TPCircularBufferProduceBytes(&packetInCircularBuffer,pack->GetPayloadData(), pack->GetPayloadLength()); + TPCircularBufferProduceBytes(&packetInCircularBuffer,pack->GetPayloadData(), (unsigned int)pack->GetPayloadLength()); sess.DeletePacket(pack); @@ -194,11 +194,13 @@ void AudioOutputCallback( void* pbuffer=outBuffer->mAudioData; memcpy(pbuffer, buffer, bytesToCopy); + /* OSStatus status = AudioQueueEnqueueBuffer( playState->queue, outBuffer, 0, packetDescs); + */ playState->currentPacket += numPackets; @@ -245,6 +247,7 @@ -(void) sendThread int timestampIncrement=800; int rtpstatus = sess.SendPacket(targetBuffer,bytesToCopy,8,self.marker, timestampIncrement); + free(targetBuffer); if(self.marker) self.marker=NO; @@ -254,7 +257,6 @@ -(void) sendThread //clean up TPCircularBufferConsume(&packetOutCircularBuffer, bytesToCopy); - free(targetBuffer); } } diff --git a/Monal/Classes/XMPPDataForm.h b/Monal/Classes/XMPPDataForm.h index a12c888c1b..4ce499d48c 100644 --- a/Monal/Classes/XMPPDataForm.h +++ b/Monal/Classes/XMPPDataForm.h @@ -12,25 +12,27 @@ #import #import "MLXMLNode.h" +NS_ASSUME_NONNULL_BEGIN + @interface XMPPDataForm : MLXMLNode --(id _Nonnull) initWithType:(NSString* _Nonnull) type andFormType:(NSString* _Nonnull) formType; --(id _Nonnull) initWithType:(NSString* _Nonnull) type formType:(NSString* _Nonnull) formType andDictionary:(NSDictionary* _Nonnull) vars; +-(id) initWithType:(NSString*) type andFormType:(NSString*) formType; +-(id) initWithType:(NSString*) type formType:(NSString*) formType andDictionary:(NSDictionary*) vars; -@property (atomic, strong) NSString* _Nonnull type; -@property (atomic, strong) NSString* _Nonnull formType; --(void) setFieldWithDictionary:(NSDictionary* _Nonnull) field; --(void) setField:(NSString* _Nonnull) name withValue:(NSString* _Nonnull) value; --(void) setField:(NSString* _Nonnull) name withType:(NSString* _Nonnull) type andValue:(NSString* _Nonnull) value; --(NSDictionary* _Nullable) getField:(NSString* _Nonnull) name; --(void) removeField:(NSString* _Nonnull) name; +@property (atomic, strong) NSString* type; +@property (atomic, strong) NSString* formType; +-(void) setFieldWithDictionary:(NSDictionary*) field; +-(void) setField:(NSString*) name withValue:(NSString*) value; +-(void) setField:(NSString*) name withType:(NSString* _Nullable) type andValue:(NSString*) value; +-(NSDictionary* _Nullable) getField:(NSString*) name; +-(void) removeField:(NSString*) name; //NSMutableDictionary interface (not complete, but nearly complete) @property(readonly) NSUInteger count; @property(readonly, copy) NSArray* allKeys; @property(readonly, copy) NSArray* allValues; --(id _Nullable) objectForKeyedSubscript:(NSString* _Nonnull) key; --(void) setObject:(id _Nullable) obj forKeyedSubscript:(NSString* _Nonnull) key; +-(id _Nullable) objectForKeyedSubscript:(NSString*) key; +-(void) setObject:(id _Nullable) obj forKeyedSubscript:(NSString*) key; -(NSArray*) allKeys; -(NSArray*) allValues; -(NSArray*) allKeysForObject:(id) anObject; @@ -40,10 +42,12 @@ -(void) removeAllObjects; -(void) removeObjectsForKeys:(NSArray*) keyArray; -(void) setObject:(NSString*) value forKey:(NSString*) key; --(void) setValue:(NSString*) value forKey:(NSString*) key; +-(void) setValue:(NSString* _Nullable) value forKey:(NSString*) key; -(void) addEntriesFromDictionary:(NSDictionary*) vars; -(void) setDictionary:(NSDictionary*) vars; @end +NS_ASSUME_NONNULL_END + #endif /* XMPPDataForm_h */ diff --git a/Monal/Classes/XMPPDataForm.m b/Monal/Classes/XMPPDataForm.m index ca12760e7c..f9ce62c227 100644 --- a/Monal/Classes/XMPPDataForm.m +++ b/Monal/Classes/XMPPDataForm.m @@ -45,7 +45,7 @@ -(void) setField:(NSString* _Nonnull) name withValue:(NSString* _Nonnull) value [self setField:name withType:nil andValue:value]; } --(void) setField:(NSString* _Nonnull) name withType:(NSString*) type andValue:(NSString* _Nonnull) value +-(void) setField:(NSString* _Nonnull) name withType:(NSString* _Nullable) type andValue:(NSString* _Nonnull) value { NSDictionary* attrs = type ? @{@"type": type, @"var": name} : @{@"var": name}; [self removeChild:[self findFirst:[NSString stringWithFormat:@"field", name]]]; @@ -173,7 +173,7 @@ -(void) setObject:(NSString*) value forKey:(NSString*) key [self setField:key withValue:value]; } --(void) setValue:(NSString*) value forKey:(NSString*) key +-(void) setValue:(NSString* _Nullable) value forKey:(NSString*) key { if(!value) [self removeObjectForKey:key]; diff --git a/Monal/Classes/XMPPEdit.h b/Monal/Classes/XMPPEdit.h index 99b0433a3d..12fceac490 100644 --- a/Monal/Classes/XMPPEdit.h +++ b/Monal/Classes/XMPPEdit.h @@ -20,11 +20,14 @@ @property (nonatomic, strong ) NSArray *sectionArray; @property (nonatomic, assign) BOOL editMode; -@property (nonatomic, strong) NSString *accountno; -@property (nonatomic, strong) NSIndexPath *originIndex; -@property (nonatomic, strong) NSString *accountType; +@property (nonatomic, strong) NSString* accountno; +// Used for QR-Code scanning +@property (nonatomic, strong) NSString* jid; +@property (nonatomic, strong) NSString* password; + +@property (nonatomic, strong) NSIndexPath* originIndex; +@property (nonatomic, strong) NSString* accountType; --(IBAction) delClicked: (id) sender; -(IBAction) save:(id) sender; diff --git a/Monal/Classes/XMPPEdit.m b/Monal/Classes/XMPPEdit.m index cffd3173e3..3cbcb0c620 100644 --- a/Monal/Classes/XMPPEdit.m +++ b/Monal/Classes/XMPPEdit.m @@ -21,9 +21,8 @@ @interface XMPPEdit() -@property (nonatomic, strong) NSString *jid; @property (nonatomic, strong) NSString *rosterName; -@property (nonatomic, strong) NSString *password; +@property (nonatomic, strong) NSString *statusMessage; @property (nonatomic, strong) NSString *resource; @property (nonatomic, strong) NSString *server; @property (nonatomic, strong) NSString *port; @@ -40,13 +39,12 @@ @interface XMPPEdit() @property (nonatomic, strong) UIImage *selectedAvatarImage; @property (nonatomic) BOOL avatarChanged; @property (nonatomic) BOOL rosterNameChanged; +@property (nonatomic) BOOL statusMessageChanged; @property (nonatomic) BOOL usedCamera; @end - @implementation XMPPEdit - -(void) hideKeyboard { [self.currentTextField resignFirstResponder]; @@ -71,7 +69,7 @@ - (void)viewDidLoad if(![_accountno isEqualToString:@"-1"]) { - self.editMode = true; + self.editMode = YES; } DDLogVerbose(@"got account number %@", _accountno); @@ -82,8 +80,9 @@ - (void)viewDidLoad self.avatarChanged = NO; self.rosterNameChanged = NO; + self.statusMessageChanged = NO; - if(_originIndex.section == 0) + if(self.originIndex && self.originIndex.section == 0) { //edit DDLogVerbose(@"reading account number %@", _accountno); @@ -113,16 +112,33 @@ - (void)viewDidLoad self.selfSignedSSL = [[settings objectForKey:@"selfsigned"] boolValue]; self.rosterName = [settings objectForKey:kRosterName]; + self.statusMessage = [settings objectForKey:@"statusMessage"]; + self.sectionArray = @[ + @"", + [NSString stringWithFormat:NSLocalizedString(@"Account (%@)", @""), self.jid], + NSLocalizedString(@"General", @""), + NSLocalizedString(@"Advanced Settings", @""), + @"" + ]; } else { + self.title = NSLocalizedString(@"New Account", @""); self.port = @"5222"; self.resource = [HelperTools encodeRandomResource]; self.directTLS = NO; self.selfSignedSSL = NO; self.rosterName = @""; + self.statusMessage = @""; + self.enabled = YES; + self.sectionArray = @[ + @"", + NSLocalizedString(@"Account (new)", @""), + NSLocalizedString(@"General", @""), + NSLocalizedString(@"Advanced Settings", @""), + @"" + ]; } - self.sectionArray = @[@"", NSLocalizedString(@"Account", @""), NSLocalizedString(@"General", @""), NSLocalizedString(@"Advanced Settings", @""), @""]; #if TARGET_OS_MACCATALYST //UTI @"public.data" for everything @@ -223,6 +239,7 @@ -(IBAction) save:(id) sender [dic setObject:[NSNumber numberWithBool:self.directTLS] forKey:kDirectTLS]; [dic setObject:self.accountno forKey:kAccountID]; [dic setObject:self.rosterName forKey:kRosterName]; + [dic setObject:self.statusMessage forKey:@"statusMessage"]; if(!self.editMode) { @@ -244,15 +261,18 @@ -(IBAction) save:(id) sender [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlock]; [SAMKeychain setPassword:self.password forService:@"Monal" account:self.accountno]; if(self.enabled) + { [[MLXMPPManager sharedInstance] connectAccount:self.accountno]; + xmpp* account = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; + [account publishStatusMessage:self.statusMessage]; + [account publishRosterName:self.rosterName]; + [account publishAvatar:self.selectedAvatarImage]; + } else [[MLXMPPManager sharedInstance] disconnectAccount:self.accountno]; - xmpp* account = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; - [account publishRosterName:self.rosterName]; - [account publishAvatar:self.selectedAvatarImage]; [self showSuccessHUD]; } - } else { + } else { [self alertWithTitle:NSLocalizedString(@"Account Exists", @"") andMsg:NSLocalizedString(@"This account already exists in Monal.", @"")]; } } @@ -263,14 +283,18 @@ -(IBAction) save:(id) sender if(updatedAccount) { [[MLXMPPManager sharedInstance] updatePassword:self.password forAccount:self.accountno]; if(self.enabled) + { [[MLXMPPManager sharedInstance] connectAccount:self.accountno]; + xmpp* account = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; + if(self.statusMessageChanged) + [account publishStatusMessage:self.statusMessage]; + if(self.rosterNameChanged) + [account publishRosterName:self.rosterName]; + if(self.avatarChanged) + [account publishAvatar:self.selectedAvatarImage]; + } else [[MLXMPPManager sharedInstance] disconnectAccount:self.accountno]; - xmpp* account = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; - if(self.rosterNameChanged) - [account publishRosterName:self.rosterName]; - if(self.avatarChanged) - [account publishAvatar:self.selectedAvatarImage]; [self showSuccessHUD]; } } @@ -293,7 +317,7 @@ -(void) showSuccessHUD }); } -- (IBAction) delClicked: (id) sender +- (IBAction) deleteAccountClicked: (id) sender { DDLogVerbose(@"Deleting"); @@ -330,6 +354,37 @@ - (IBAction) delClicked: (id) sender } +- (IBAction) clearHistoryClicked: (id) sender +{ + DDLogVerbose(@"Deleting History"); + + UIAlertController *questionAlert =[UIAlertController alertControllerWithTitle:NSLocalizedString(@"Clear Chat History", @"") message:NSLocalizedString(@"This will clear the whole chat history of this account from this device.", @"") preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertAction *noAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"No", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + //do nothing when "no" was pressed + }]; + UIAlertAction *yesAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + + [self.db clearMessages:self.accountno]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalRefresh object:nil userInfo:nil]; + + MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + hud.mode = MBProgressHUDModeCustomView; + hud.removeFromSuperViewOnHide=YES; + hud.label.text =NSLocalizedString(@"Success", @""); + hud.detailsLabel.text =NSLocalizedString(@"The chat history has been cleared", @""); + UIImage *image = [[UIImage imageNamed:@"success"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + hud.customView = [[UIImageView alloc] initWithImage:image]; + [hud hideAnimated:YES afterDelay:1.0f]; + }]; + + [questionAlert addAction:noAction]; + [questionAlert addAction:yesAction]; + questionAlert.popoverPresentationController.sourceView = sender; + + [self presentViewController:questionAlert animated:YES completion:nil]; + +} + #pragma mark table view datasource methods - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section @@ -362,34 +417,26 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N switch (indexPath.row) { case 0: { - thecell.cellLabel.text = NSLocalizedString(@"Display Name", @""); - thecell.toggleSwitch.hidden = YES; - thecell.textInputField.tag = 1; - thecell.textInputField.keyboardType = UIKeyboardTypeDefault; - thecell.textInputField.text = self.rosterName; + thecell.cellLabel.text = NSLocalizedString(@"Enabled", @""); + thecell.textInputField.hidden = YES; + thecell.toggleSwitch.tag = 1; + thecell.toggleSwitch.on = self.enabled; break; } case 1: { - thecell.cellLabel.text = NSLocalizedString(@"XMPP ID", @""); + thecell.cellLabel.text = NSLocalizedString(@"Display Name", @""); thecell.toggleSwitch.hidden = YES; - thecell.textInputField.tag = 2; - thecell.textInputField.keyboardType = UIKeyboardTypeEmailAddress; - thecell.textInputField.text = self.jid; + thecell.textInputField.tag = 1; + thecell.textInputField.keyboardType = UIKeyboardTypeAlphabet; + thecell.textInputField.text = self.rosterName; break; } case 2: { - thecell.cellLabel.text = NSLocalizedString(@"Password", @""); + thecell.cellLabel.text = NSLocalizedString(@"Status Message", @""); thecell.toggleSwitch.hidden = YES; - thecell.textInputField.secureTextEntry = YES; - thecell.textInputField.tag = 3; - thecell.textInputField.text = self.password; - break; - } - case 3: { - thecell.cellLabel.text = NSLocalizedString(@"Enabled", @""); - thecell.textInputField.hidden = YES; - thecell.toggleSwitch.tag = 1; - thecell.toggleSwitch.on = self.enabled; + thecell.textInputField.tag = 6; + thecell.textInputField.keyboardType = UIKeyboardTypeAlphabet; + thecell.textInputField.text = self.statusMessage; break; } } @@ -400,9 +447,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { // general case 0: { - thecell.cellLabel.text = NSLocalizedString(@"Message Archive Pref", @""); + thecell.cellLabel.text = NSLocalizedString(@"Change Password", @""); thecell.toggleSwitch.hidden = YES; - thecell.textInputField.hidden = YES; thecell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; @@ -410,7 +456,13 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N case 1: { thecell.cellLabel.text = NSLocalizedString(@"My Keys", @""); thecell.toggleSwitch.hidden = YES; - + thecell.textInputField.hidden = YES; + thecell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + break; + } + case 2: { + thecell.cellLabel.text = NSLocalizedString(@"Message Archive Pref", @""); + thecell.toggleSwitch.hidden = YES; thecell.textInputField.hidden = YES; thecell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; @@ -422,7 +474,23 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N switch (indexPath.row) { //advanced - case 0: { + case 0: { + thecell.cellLabel.text = NSLocalizedString(@"XMPP ID", @""); + thecell.toggleSwitch.hidden = YES; + thecell.textInputField.tag = 2; + thecell.textInputField.keyboardType = UIKeyboardTypeEmailAddress; + thecell.textInputField.text = self.jid; + break; + } + case 1: { + thecell.cellLabel.text = NSLocalizedString(@"Password", @""); + thecell.toggleSwitch.hidden = YES; + thecell.textInputField.secureTextEntry = YES; + thecell.textInputField.tag = 3; + thecell.textInputField.text = self.password; + break; + } + case 2: { thecell.cellLabel.text = NSLocalizedString(@"Server", @""); thecell.toggleSwitch.hidden = YES; thecell.textInputField.tag = 4; @@ -431,36 +499,28 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N thecell.accessoryType = UITableViewCellAccessoryDetailButton; break; } - case 1: { + case 3: { thecell.cellLabel.text = NSLocalizedString(@"Port", @""); thecell.toggleSwitch.hidden = YES; thecell.textInputField.tag = 5; thecell.textInputField.text = self.port; break; } - case 2: { + case 4: { thecell.cellLabel.text = NSLocalizedString(@"Direct TLS", @""); thecell.textInputField.hidden = YES; thecell.toggleSwitch.tag = 2; thecell.toggleSwitch.on = self.directTLS; break; } - case 3: { + case 5: { thecell.cellLabel.text = NSLocalizedString(@"Validate certificate", @""); thecell.textInputField.hidden = YES; thecell.toggleSwitch.tag = 3; thecell.toggleSwitch.on = !self.selfSignedSSL; break; } - case 4: { - thecell.cellLabel.text = NSLocalizedString(@"Change Password", @""); - thecell.toggleSwitch.hidden = YES; - - thecell.textInputField.hidden = YES; - thecell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - break; - } - case 5: { + case 6: { thecell.cellLabel.text = NSLocalizedString(@"Resource", @""); thecell.labelRight.text = self.resource; thecell.labelRight.hidden = NO; @@ -470,21 +530,26 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } } } - else if (indexPath.section == 4) + else if (indexPath.section == 4 && self.editMode == YES) { switch (indexPath.row) { case 0: { - if(self.editMode == true) - { - - MLButtonCell* buttonCell = (MLButtonCell*)[tableView dequeueReusableCellWithIdentifier:@"ButtonCell"]; - buttonCell.buttonText.text = NSLocalizedString(@"Delete", @""); - buttonCell.buttonText.textColor = [UIColor redColor]; - buttonCell.selectionStyle = UITableViewCellSelectionStyleNone; - return buttonCell; - } - break; + MLButtonCell* buttonCell = (MLButtonCell*)[tableView dequeueReusableCellWithIdentifier:@"ButtonCell"]; + buttonCell.buttonText.text = NSLocalizedString(@"Clear Chat History", @""); + buttonCell.buttonText.textColor = [UIColor redColor]; + buttonCell.selectionStyle = UITableViewCellSelectionStyleNone; + buttonCell.tag = 0; + return buttonCell; + } + case 1: + { + MLButtonCell* buttonCell = (MLButtonCell*)[tableView dequeueReusableCellWithIdentifier:@"ButtonCell"]; + buttonCell.buttonText.text = NSLocalizedString(@"Delete Account", @""); + buttonCell.buttonText.textColor = [UIColor redColor]; + buttonCell.selectionStyle = UITableViewCellSelectionStyleNone; + buttonCell.tag = 1; + return buttonCell; } } } @@ -541,30 +606,20 @@ - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInte - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - - if(section == 0){ + //avatar image + if(section == 0) return 0; - } // account - else if(section == 1){ - return 4; - } + else if(section == 1) + return 3; // General settings - else if(section == 2) { - return 2; - } + else if(section == 2) + return 3; // Advanced settings - else if(section == 3) { - return 6; - } - else if(section == 4 && !self.editMode) - { - return 0; - } + else if(section == 3) + return 7; else - { - return 1; - } + return self.editMode ? 2 : 0; } #pragma mark - table view delegate @@ -577,26 +632,28 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath switch(newIndexPath.row) { case 0: - [self performSegueWithIdentifier:@"showMAMPref" sender:self]; + [self performSegueWithIdentifier:@"showPassChange" sender:self]; break; case 1: [self performSegueWithIdentifier:@"showKeyTrust" sender:self]; break; + case 2: + [self performSegueWithIdentifier:@"showMAMPref" sender:self]; + break; } } - else if(newIndexPath.section == 3) + else if(newIndexPath.section == 4) { switch(newIndexPath.row) { - case 4: - [self performSegueWithIdentifier:@"showPassChange" sender:self]; + case 0: + [self clearHistoryClicked:[tableView cellForRowAtIndexPath:newIndexPath]]; + break; + case 1: + [self deleteAccountClicked:[tableView cellForRowAtIndexPath:newIndexPath]]; break; } } - else if(newIndexPath.section == 4) - { - [self delClicked:[tableView cellForRowAtIndexPath:newIndexPath]]; - } } @@ -606,7 +663,7 @@ -(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPa { switch(indexPath.row) { - case 0: + case 2: [self performSegueWithIdentifier:@"showServerDetails" sender:self]; break; } @@ -618,20 +675,21 @@ -(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPa - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - if ([segue.identifier isEqualToString:@"showServerDetails"]) + if([segue.identifier isEqualToString:@"showServerDetails"]) { MLServerDetails* server= (MLServerDetails*)segue.destinationViewController; server.xmppAccount = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; } - else if ([segue.identifier isEqualToString:@"showMAMPref"]) + else if([segue.identifier isEqualToString:@"showMAMPref"]) { MLMAMPrefTableViewController* mam = (MLMAMPrefTableViewController*)segue.destinationViewController; mam.xmppAccount = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; } - else if ([segue.identifier isEqualToString:@"showKeyTrust"]) + else if([segue.identifier isEqualToString:@"showKeyTrust"]) { - if(self.jid && self.accountno) { + if(self.jid && self.accountno) + { MLKeysTableViewController* keys = (MLKeysTableViewController*)segue.destinationViewController; keys.ownKeys = YES; MLContact *contact = [[MLContact alloc] init]; @@ -640,11 +698,12 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender keys.contact=contact; } } - else if ([segue.identifier isEqualToString:@"showPassChange"]) + else if([segue.identifier isEqualToString:@"showPassChange"]) { - if(self.jid && self.accountno) { + if(self.jid && self.accountno) + { MLPasswordChangeTableViewController* pwchange = (MLPasswordChangeTableViewController*)segue.destinationViewController; - pwchange.xmppAccount = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; + pwchange.xmppAccount = [[MLXMPPManager sharedInstance] getConnectedAccountForID:self.accountno]; } } } @@ -690,6 +749,11 @@ - (void)textFieldDidEndEditing:(UITextField *)textField self.port = textField.text; break; } + case 6: { + self.statusMessage = textField.text; + self.statusMessageChanged = YES; + break; + } default: break; } diff --git a/Monal/Classes/XMPPIQ.h b/Monal/Classes/XMPPIQ.h index 5cbefd9ee4..af4330293a 100644 --- a/Monal/Classes/XMPPIQ.h +++ b/Monal/Classes/XMPPIQ.h @@ -8,6 +8,8 @@ #import "XMPPStanza.h" +NS_ASSUME_NONNULL_BEGIN + FOUNDATION_EXPORT NSString* const kiqGetType; FOUNDATION_EXPORT NSString* const kiqSetType; FOUNDATION_EXPORT NSString* const kiqResultType; @@ -52,7 +54,7 @@ FOUNDATION_EXPORT NSString* const kiqErrorType; */ -(void) updateMamArchivePrefDefault:(NSString *) pref; --(void) setMAMQueryLatestMessagesForJid:(NSString*) jid before:(NSString*) uid; +-(void) setMAMQueryLatestMessagesForJid:(NSString*) jid before:(NSString* _Nullable) uid; -(void) setMAMQueryAfter:(NSString*) uid; -(void) setMAMQueryForLatestId; @@ -90,7 +92,7 @@ removes a contact from the roster /** Requests a full roster from the server. A null version will not set the ver attribute */ --(void) setRosterRequest:(NSString *) version; +-(void) setRosterRequest:(NSString* _Nullable) version; /** makes iq with version element @@ -127,7 +129,7 @@ removes a contact from the roster /** Dictionary info has initiator, responder, sid, ownip */ --(void) setJingleTerminateTo:(NSString*) jid andResource:(NSString*) resource withValues:(NSDictionary*) info; +-(void) setJingleTerminateTo:(NSString* _Nullable) jid andResource:(NSString* _Nullable) resource withValues:(NSDictionary* _Nullable) info; -(void) setBlocked:(BOOL) blocked forJid:(NSString* _Nonnull) blockedJid; @@ -138,3 +140,5 @@ removes a contact from the roster -(void) registerUser:(NSString* _Nonnull) user withPassword:(NSString* _Nonnull) newPass captcha:(NSString* _Nonnull) captcha andHiddenFields:(NSDictionary* _Nonnull) hiddenFields; @end + +NS_ASSUME_NONNULL_END diff --git a/Monal/Classes/XMPPMessage.h b/Monal/Classes/XMPPMessage.h index c2e95cca05..d6c82a9f90 100644 --- a/Monal/Classes/XMPPMessage.h +++ b/Monal/Classes/XMPPMessage.h @@ -39,6 +39,7 @@ FOUNDATION_EXPORT NSString* const kMessageHeadlineType; */ -(void) setOobUrl:(NSString*) link; +-(void) setLMCFor:(NSString*) id withNewBody:(NSString*) newBody; /** sets the receipt child element diff --git a/Monal/Classes/XMPPMessage.m b/Monal/Classes/XMPPMessage.m index 7d55541eb7..7e72b6f512 100644 --- a/Monal/Classes/XMPPMessage.m +++ b/Monal/Classes/XMPPMessage.m @@ -8,14 +8,13 @@ #import "XMPPMessage.h" - @implementation XMPPMessage -NSString* const kMessageChatType=@"chat"; -NSString* const kMessageGroupChatType=@"groupchat"; -NSString* const kMessageErrorType=@"error"; -NSString* const kMessageNormalType =@"normal"; -NSString* const kMessageHeadlineType=@"headline"; +NSString* const kMessageChatType = @"chat"; +NSString* const kMessageGroupChatType = @"groupchat"; +NSString* const kMessageErrorType = @"error"; +NSString* const kMessageNormalType = @"normal"; +NSString* const kMessageHeadlineType = @"headline"; -(id) init { @@ -34,7 +33,7 @@ -(id) initWithXMPPMessage:(XMPPMessage*) msg -(void) setXmppId:(NSString*) idval { - [self.attributes setObject:idval forKey:@"id"]; + self.attributes[@"id"] = idval; //add origin id to indicate we are using uuids for our stanza ids if([self check:@"{urn:xmpp:sid:0}origin-id"]) //modify existing origin id ((MLXMLNode*)[self findFirst:@"{urn:xmpp:sid:0}origin-id"]).attributes[@"id"] = idval; @@ -44,7 +43,7 @@ -(void) setXmppId:(NSString*) idval -(NSString*) xmppId { - return [self.attributes objectForKey:@"id"]; + return self.attributes[@"id"]; } -(void) setBody:(NSString*) messageBody @@ -60,6 +59,12 @@ -(void) setOobUrl:(NSString*) link [self setBody:link]; //http filetransfers must have a message body equal to the oob link to be recognized as filetransfer } +-(void) setLMCFor:(NSString*) id withNewBody:(NSString*) newBody +{ + [self addChild:[[MLXMLNode alloc] initWithElement:@"replace" andNamespace:@"urn:xmpp:message-correct:0" withAttributes:@{@"id": id} andChildren:@[] andData:nil]]; + [self setBody:newBody]; +} + /** @see https://xmpp.org/extensions/xep-0184.html */ diff --git a/Monal/Classes/XMPPStanza.h b/Monal/Classes/XMPPStanza.h index 031c344218..f24f816533 100644 --- a/Monal/Classes/XMPPStanza.h +++ b/Monal/Classes/XMPPStanza.h @@ -15,17 +15,17 @@ NS_ASSUME_NONNULL_BEGIN -(void) addDelayTagFrom:(NSString*) from; -@property (atomic, strong) NSString* from; -@property (atomic, strong) NSString* fromUser; -@property (atomic, strong) NSString* fromNode; -@property (atomic, strong) NSString* fromHost; -@property (atomic, strong) NSString* fromResource; - -@property (atomic, strong) NSString* to; -@property (atomic, strong) NSString* toUser; -@property (atomic, strong) NSString* toNode; -@property (atomic, strong) NSString* toHost; -@property (atomic, strong) NSString* toResource; +@property (atomic, strong) NSString* _Nullable from; +@property (atomic, strong) NSString* _Nullable fromUser; +@property (atomic, strong) NSString* _Nullable fromNode; +@property (atomic, strong) NSString* _Nullable fromHost; +@property (atomic, strong) NSString* _Nullable fromResource; + +@property (atomic, strong) NSString* _Nullable to; +@property (atomic, strong) NSString* _Nullable toUser; +@property (atomic, strong) NSString* _Nullable toNode; +@property (atomic, strong) NSString* _Nullable toHost; +@property (atomic, strong) NSString* _Nullable toResource; @end diff --git a/Monal/Classes/addContact.h b/Monal/Classes/addContact.h index ebef64da60..df56c864c1 100644 --- a/Monal/Classes/addContact.h +++ b/Monal/Classes/addContact.h @@ -8,8 +8,9 @@ #import #import "MLConstants.h" +#import -@interface addContact : UITableViewController +@interface addContact : UITableViewController { UITextField* _currentTextField; NSInteger _selectedRow; diff --git a/Monal/Classes/addContact.m b/Monal/Classes/addContact.m index 3d84aeca59..e9d33ea0ee 100644 --- a/Monal/Classes/addContact.m +++ b/Monal/Classes/addContact.m @@ -13,6 +13,9 @@ #import "MLTextInputCell.h" #import "MLAccountPickerViewController.h" #import "DataLayer.h" +#import "xmpp.h" + +@class MLQRCodeScanner; @implementation addContact @@ -69,7 +72,7 @@ -(IBAction) addPress:(id)sender else { UIAlertController* messageAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error",@"") message:NSLocalizedString(@"Name can't be empty", @"") preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *closeAction =[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + UIAlertAction* closeAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { }]; [messageAlert addAction:closeAction]; @@ -91,7 +94,7 @@ - (BOOL)textFieldShouldReturn:(UITextField *)textField - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { - _currentTextField=textField; + _currentTextField = textField; return YES; } @@ -101,9 +104,7 @@ - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { -(void) viewDidLoad { [super viewDidLoad]; - self.navigationItem.title=NSLocalizedString(@"Add Contact", @""); - _closeButton =[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Close", @"") style:UIBarButtonItemStylePlain target:self action:@selector(closeView)]; - self.navigationItem.rightBarButtonItem = _closeButton; + self.navigationItem.title = NSLocalizedString(@"Add Contact", @""); [self.tableView registerNib:[UINib nibWithNibName:@"MLTextInputCell" bundle:[NSBundle mainBundle]] @@ -129,17 +130,17 @@ -(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if(section==0) - { - return NSLocalizedString(@"Contacts are usually in the format: username@domain.something", @""); - } - else return nil; + if(section == 0) + return NSLocalizedString(@"Contacts are usually in the format: username@dom.ain", @""); + else + return nil; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - NSInteger toreturn =0; - switch (section) { + NSInteger toreturn = 0; + switch (section) + { case 0: toreturn = 2; break; @@ -156,34 +157,25 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell* cell ; - - switch (indexPath.section) { - case 0: { - if(indexPath.row == 0){ - UITableViewCell* accountCell = [tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; - accountCell.textLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Using Account: %@", @""), [[MLXMPPManager sharedInstance] getAccountNameForConnectedRow:_selectedRow]]; - accountCell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; - - cell = accountCell; - } else if(indexPath.row == 1){ - MLTextInputCell* textCell = [tableView dequeueReusableCellWithIdentifier:@"TextCell"]; - self.contactName = textCell.textInput; - self.contactName.placeholder = NSLocalizedString(@"Contact Name", @""); - self.contactName.delegate=self; - - cell = textCell; - } - break; - } - case 1: { - cell = [tableView dequeueReusableCellWithIdentifier:@"addButton"]; - break; + if(indexPath.section == 0) + { + if(indexPath.row == 0){ + UITableViewCell* accountCell = [tableView dequeueReusableCellWithIdentifier:@"AccountCell"]; + accountCell.textLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Using Account: %@", @""), [[MLXMPPManager sharedInstance] getAccountNameForConnectedRow:_selectedRow]]; + accountCell.accessoryType=UITableViewCellAccessoryDisclosureIndicator; + + return accountCell; } - default: - break; + + MLTextInputCell* textCell = [tableView dequeueReusableCellWithIdentifier:@"TextCell"]; + self.contactName = textCell.textInput; + self.contactName.placeholder = NSLocalizedString(@"Contact Jid", @""); + self.contactName.delegate=self; + + return textCell; } - return cell; + else + return [tableView dequeueReusableCellWithIdentifier:@"addButton"]; } #pragma mark tableview delegate @@ -191,7 +183,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath { switch (indexPath.section) { case 0: { - if(indexPath.row ==0){ + if(indexPath.row == 0){ [self performSegueWithIdentifier:@"showAccountPicker" sender:self]; } } @@ -201,14 +193,25 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if([segue.identifier isEqualToString:@"showAccountPicker"]) { - MLAccountPickerViewController *accountPicker = (MLAccountPickerViewController *) segue.destinationViewController; + MLAccountPickerViewController* accountPicker = (MLAccountPickerViewController *) segue.destinationViewController; accountPicker.completion = ^(NSInteger accountRow) { - self->_selectedRow=accountRow; - NSIndexPath *indexpath = [NSIndexPath indexPathForRow:0 inSection:0]; + self->_selectedRow = accountRow; + NSIndexPath* indexpath = [NSIndexPath indexPathForRow:0 inSection:0]; [self.tableView reloadRowsAtIndexPaths:@[indexpath] withRowAnimation:UITableViewRowAnimationNone]; }; } - + else if([segue.identifier isEqualToString:@"qrContactScan"]) + { + MLQRCodeScanner* qrCodeScanner = (MLQRCodeScanner *) segue.destinationViewController; + qrCodeScanner.contactDelegate = self; + } +} + +-(void) MLQRCodeContactScannedWithJid:(NSString *)jid fingerprints:(NSDictionary *)fingerprints +{ + self.contactName.text = jid; + // Close QR-Code scanner + [self.navigationController popViewControllerAnimated:YES]; } @end diff --git a/Monal/Classes/chatViewController.h b/Monal/Classes/chatViewController.h index 6c97694257..63acc801ab 100644 --- a/Monal/Classes/chatViewController.h +++ b/Monal/Classes/chatViewController.h @@ -15,9 +15,10 @@ #import "MLXMPPManager.h" #import "MLNotificationManager.h" #import "MLResizingTextView.h" +#import "MLSearchViewController.h" -@interface chatViewController : UIViewController +@interface chatViewController : UIViewController { UIView* containerView; BOOL _firstmsg; @@ -68,10 +69,9 @@ Receives the new message notice and will update if it is this user. */ -(void) handleNewMessage:(NSNotification *)notification; --(void) addMessageto:(NSString*)to withMessage:(NSString*) message andId:(NSString *) messageId withCompletion:(void (^)(BOOL success))completion; -(void) retry:(id) sender; --(void) reloadTable; +-(void) reloadTable; @end diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index 1a2bd35fa9..99fefb6a00 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -16,6 +16,7 @@ #import "MLConstants.h" #import "MonalAppDelegate.h" #import "MBProgressHUD.h" +#import "xmpp.h" #import "MLOMEMO.h" #import "IDMPhotoBrowser.h" @@ -29,12 +30,13 @@ #import "MLChatInputContainer.h" #import "MLXEPSlashMeHandler.h" #import "MLSearchViewController.h" +#import "MLFiletransfer.h" @import QuartzCore; @import MobileCoreServices; @import AVFoundation; -@interface chatViewController() +@interface chatViewController() { BOOL _isTyping; monal_void_block_t _cancelTypingNotification; @@ -80,6 +82,7 @@ @interface chatViewController())coordinator { + [self stopEditing]; [self.chatInput resignFirstResponder]; [coordinator animateAlongsideTransition:^(id context) { } completion:^(id context) { - [self lastMsgButtonPositionConfigWithSize:[UIApplication sharedApplication].keyWindow.bounds.size]; + //[self lastMsgButtonPositionConfigWithSize:self.inputContainerView.bounds.size]; + [self lastMsgButtonPositionConfigWithSize:size]; }]; } @@ -697,6 +714,7 @@ -(void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id 0) { - // Reset chatInput -> remove draft from db so that macOS will show the new send message + // Reset chatInput -> remove draft from db so that macOS will show the newly sens message [self.chatInput setText:@""]; [self saveMessageDraft]; - // Send trimmed message - [self sendMessage:cleanString]; + + if(self.editingCallback) + self.editingCallback(cleanString); + else + { + // Send trimmed message + [self sendMessage:cleanString withType:kMessageTypeText]; + } } [self sendChatState:NO]; } @@ -897,6 +921,7 @@ -(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender #pragma mark - doc picker -(IBAction)attachfile:(id)sender { + [self stopEditing]; [self.chatInput resignFirstResponder]; [self presentViewController:self.imagePicker animated:YES completion:nil]; @@ -904,13 +929,36 @@ -(IBAction)attachfile:(id)sender return; } -- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls +- (void) documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { - NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init]; - [coordinator coordinateReadingItemAtURL:urls.firstObject options:NSFileCoordinatorReadingForUploading error:nil byAccessor:^(NSURL * _Nonnull newURL) { - NSData *data =[NSData dataWithContentsOfURL:newURL]; - [self uploadData:data]; - }]; + if(urls.count == 0) + return; + + [self showUploadHUD]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + __block int filesToUpload = (int)urls.count; + NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] init]; + for(NSURL* url in urls) + { + [coordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingForUploading error:nil byAccessor:^(NSURL * _Nonnull newURL) { + [MLFiletransfer uploadFile:newURL onAccount:self.xmppAccount withEncryption:self.encryptChat andCompletion:^(NSString* url, NSString* mimeType, NSNumber* size, NSError* error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self showPotentialError:error]; + if(!error) + { + filesToUpload--; + if(filesToUpload == 0) + [self hideUploadHUD]; + + NSString* newMessageID = [[NSUUID UUID] UUIDString]; + [self addMessageto:self.contact.contactJid withMessage:url andId:newMessageID messageType:kMessageTypeFiletransfer mimeType:mimeType size:size]; + [[MLXMPPManager sharedInstance] sendMessage:url toContact:self.contact.contactJid fromAccount:self.contact.accountId isEncrypted:self.encryptChat isMUC:self.contact.isGroup isUpload:YES messageId:newMessageID withCompletionHandler:nil]; + } + }); + }]; + }]; + } + }); } #pragma mark - location delegate @@ -939,7 +987,7 @@ -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray< } self.gpsHUD.hidden=YES; // Send location - [self sendMessage:[NSString stringWithFormat:@"geo:%f,%f", gpsLoc.coordinate.latitude, gpsLoc.coordinate.longitude]]; + [self sendMessage:[NSString stringWithFormat:@"geo:%f,%f", gpsLoc.coordinate.latitude, gpsLoc.coordinate.longitude] withType:kMessageTypeGeo]; } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { @@ -987,6 +1035,7 @@ -(void) displayGPSHUD { -(IBAction)attach:(id)sender { + [self stopEditing]; [self.chatInput resignFirstResponder]; xmpp* account=[[MLXMPPManager sharedInstance] getConnectedAccountForID:self.contact.accountId]; @@ -1009,21 +1058,22 @@ -(IBAction)attach:(id)sender #if TARGET_OS_MACCATALYST [self attachfile:sender]; #else - UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; - imagePicker.delegate =self; + UIImagePickerController* mediaPicker = [[UIImagePickerController alloc] init]; + mediaPicker.delegate = self; - UIAlertAction* cameraAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Camera",@ "") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; - [self presentViewController:imagePicker animated:YES completion:nil]; + UIAlertAction* cameraAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Camera", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + mediaPicker.sourceType = UIImagePickerControllerSourceTypeCamera; + [self presentViewController:mediaPicker animated:YES completion:nil]; }]; - UIAlertAction* photosAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Photos",@ "") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + UIAlertAction* photosAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Photos", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + mediaPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + mediaPicker.allowsEditing = NO; [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if(granted) { dispatch_async(dispatch_get_main_queue(), ^{ - [self presentViewController:imagePicker animated:YES completion:nil]; + [self presentViewController:mediaPicker animated:YES completion:nil]; }); } }]; @@ -1074,89 +1124,28 @@ -(IBAction)attach:(id)sender [self presentViewController:actionControll animated:YES completion:nil]; } --(void) uploadData:(NSData *) data -{ - if(!self.uploadHUD) { - self.uploadHUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; - self.uploadHUD.removeFromSuperViewOnHide = YES; - self.uploadHUD.label.text = NSLocalizedString(@"Uploading", @""); - self.uploadHUD.detailsLabel.text = NSLocalizedString(@"Uploading file to server", @""); - } else { - self.uploadHUD.hidden = NO; - } - NSData* decryptedData = data; - NSData* dataToPass = data; - MLEncryptedPayload* encrypted; - - int keySize = 32; - if(self.encryptChat) { - encrypted = [AESGcm encrypt:decryptedData keySize:keySize]; - if(encrypted) { - NSMutableData* mutableBody = [encrypted.body mutableCopy]; - [mutableBody appendData:encrypted.authTag]; - dataToPass = [mutableBody copy]; - } else { - DDLogError(@"Could not encrypt attachment"); - } - } - - [[MLXMPPManager sharedInstance] httpUploadJpegData:dataToPass toContact:self.contact.contactJid onAccount:self.contact.accountId withCompletionHandler:^(NSString *url, NSError *error) { - dispatch_async(dispatch_get_main_queue(), ^{ - self.uploadHUD.hidden = YES; - - if(url) { - NSString* newMessageID = [[NSUUID UUID] UUIDString]; - - NSString* urlToPass = url; - - if(encrypted) { - NSURLComponents* urlComponents = [NSURLComponents componentsWithURL:[NSURL URLWithString:urlToPass] resolvingAgainstBaseURL:NO]; - if(urlComponents) { - urlComponents.scheme = @"aesgcm"; - urlComponents.fragment = [NSString stringWithFormat:@"%@%@", - [HelperTools hexadecimalString:encrypted.iv], - [HelperTools hexadecimalString:[encrypted.key subdataWithRange:NSMakeRange(0, keySize)]]]; - urlToPass = urlComponents.string; - } else { - DDLogError(@"Could not parse URL for conversion to aesgcm:"); - } - } - [[MLImageManager sharedInstance] saveImageData:decryptedData forLink:urlToPass]; - - weakify(self); - [self addMessageto:self.contact.contactJid withMessage:urlToPass andId:newMessageID withCompletion:^(BOOL success) { - strongify(self); - [[MLXMPPManager sharedInstance] sendMessage:urlToPass toContact:self.contact.contactJid fromAccount:self.contact.accountId isEncrypted:self.encryptChat isMUC:self.contact.isGroup isUpload:YES messageId:newMessageID - withCompletionHandler:nil]; - }]; - } - else { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"There was an error uploading the file to the server", @"") message:[NSString stringWithFormat:@"%@", error.localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - [alert dismissViewControllerAnimated:YES completion:nil]; - }]]; - [self presentViewController:alert animated:YES completion:nil]; - } - }); - }]; -} - -- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info +-(void) imagePickerController:(UIImagePickerController*) picker didFinishPickingMediaWithInfo:(NSDictionary*) info { [self dismissViewControllerAnimated:YES completion:nil]; - - NSString *mediaType = info[UIImagePickerControllerMediaType]; - if([mediaType isEqualToString:(NSString *)kUTTypeImage]) { - UIImage *selectedImage = info[UIImagePickerControllerEditedImage]; - if(!selectedImage) selectedImage = info[UIImagePickerControllerOriginalImage]; - NSData *jpgData= UIImageJPEGRepresentation(selectedImage, 0.5f); - if(jpgData) - { - [self uploadData:jpgData]; - } - - } - + + UIImage* selectedImage = info[UIImagePickerControllerEditedImage]; + if(!selectedImage) + selectedImage = info[UIImagePickerControllerOriginalImage]; + + [self showUploadHUD]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [MLFiletransfer uploadUIImage:selectedImage onAccount:self.xmppAccount withEncryption:self.encryptChat andCompletion:^(NSString* url, NSString* mimeType, NSNumber* size, NSError* error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self showPotentialError:error]; + if(!error) + [self hideUploadHUD]; + + NSString* newMessageID = [[NSUUID UUID] UUIDString]; + [self addMessageto:self.contact.contactJid withMessage:url andId:newMessageID messageType:kMessageTypeFiletransfer mimeType:mimeType size:size]; + [[MLXMPPManager sharedInstance] sendMessage:url toContact:self.contact.contactJid fromAccount:self.contact.accountId isEncrypted:self.encryptChat isMUC:self.contact.isGroup isUpload:YES messageId:newMessageID withCompletionHandler:nil]; + }); + }]; + }); } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker @@ -1168,64 +1157,62 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker -(void) reloadTable { - if(self.messageTable.hasUncommittedUpdates) return; - + if(self.messageTable.hasUncommittedUpdates) + return; [self.messageTable reloadData]; } -//always messages going out --(void) addMessageto:(NSString*)to withMessage:(NSString*) message andId:(NSString *) messageId withCompletion:(void (^)(BOOL success))completion +//only for messages going out +-(NSNumber*) addMessageto:(NSString*)to withMessage:(NSString*) message andId:(NSString *) messageId messageType:(NSString*) messageType mimeType:(NSString*) mimeType size:(NSNumber*) size { - if(!self.jid || !message) { - DDLogError(@" not ready to send messages"); - return; + if(!self.jid || !message) + { + DDLogError(@"not ready to send messages"); + return nil; } - [[DataLayer sharedInstance] addMessageHistoryFrom:self.jid to:to forAccount:self.contact.accountId withMessage:message actuallyFrom:self.jid withId:messageId encrypted:self.encryptChat withCompletion:^(BOOL result, NSString* messageType, NSNumber* messageDBId) { + + NSNumber* messageDBId = [[DataLayer sharedInstance] addMessageHistoryFrom:self.jid to:to forAccount:self.contact.accountId withMessage:message actuallyFrom:self.jid withId:messageId encrypted:self.encryptChat messageType:messageType mimeType:mimeType size:size]; + if(messageDBId) + { DDLogVerbose(@"added message"); + NSArray* msgList = [[DataLayer sharedInstance] messagesForHistoryIDs:@[messageDBId]]; + if(![msgList count]) + { + DDLogError(@"Could not find msg for history ID %@!", messageDBId); + return nil; + } + MLMessage* messageObj = msgList[0]; + + [self tempfreezeAutoloading]; - if(result) + //update message list in ui + dispatch_async(dispatch_get_main_queue(), ^{ + [self.messageTable performBatchUpdates:^{ + if(!self.messageList) + self.messageList = [[NSMutableArray alloc] init]; + [self.messageList addObject:messageObj]; + NSInteger bottom = [self.messageList count]-1; + if(bottom>=0) + { + NSIndexPath* path1 = [NSIndexPath indexPathForRow:bottom inSection:messagesSection]; + [self->_messageTable insertRowsAtIndexPaths:@[path1] + withRowAnimation:UITableViewRowAnimationNone]; + } + } completion:^(BOOL finished) { + [self scrollToBottom]; + }]; + }); + + // make sure its in active chats list + if(_firstmsg == YES) { - if(completion) - completion(result); - - dispatch_async(dispatch_get_main_queue(), ^{ - MLMessage* messageObj = [[MLMessage alloc] init]; - messageObj.actualFrom = self.jid; - messageObj.from = self.jid; - messageObj.timestamp = [NSDate date]; - messageObj.hasBeenDisplayed = NO; - messageObj.hasBeenReceived = NO; - messageObj.hasBeenSent = NO; - messageObj.messageId = messageId; - messageObj.encrypted = self.encryptChat; - messageObj.messageType = messageType; - messageObj.messageText = message; - messageObj.messageDBId = messageDBId; - - [self.messageTable performBatchUpdates:^{ - if(!self.messageList) self.messageList = [[NSMutableArray alloc] init]; - [self.messageList addObject:messageObj]; - NSInteger bottom = [self.messageList count]-1; - if(bottom>=0) { - NSIndexPath* path1 = [NSIndexPath indexPathForRow:bottom inSection:messagesSection]; - [self->_messageTable insertRowsAtIndexPaths:@[path1] - withRowAnimation:UITableViewRowAnimationNone]; - } - } completion:^(BOOL finished) { - [self scrollToBottom]; - }]; - }); + [[DataLayer sharedInstance] addActiveBuddies:to forAccount:self.contact.accountId]; + _firstmsg = NO; } - else - DDLogVerbose(@"failed to add message"); - }]; - - // make sure its in active - if(_firstmsg == YES) - { - [[DataLayer sharedInstance] addActiveBuddies:to forAccount:self.contact.accountId]; - _firstmsg = NO; } + else + DDLogError(@"failed to add message to history db"); + return messageDBId; } -(void) presentMucInvite:(NSNotification *)notification @@ -1248,43 +1235,43 @@ -(void) presentMucInvite:(NSNotification *)notification }); } - -(void) handleNewMessage:(NSNotification *)notification { DDLogVerbose(@"chat view got new message notice %@", notification.userInfo); - MLMessage *message = [notification.userInfo objectForKey:@"message"]; - if(!message) { + MLMessage* message = [notification.userInfo objectForKey:@"message"]; + if(!message) DDLogError(@"Notification without message"); - } if([message.accountId isEqualToString:self.contact.accountId] && ([message.from isEqualToString:self.contact.contactJid] || [message.to isEqualToString:self.contact.contactJid] )) { - if([self.contact.subscription isEqualToString:kSubBoth]) { - //getting encrypted chat turns it on. not the other way around - // if(message.encrypted && !self.encryptChat) { - // NSArray *devices= [self.xmppAccount.monalSignalStore knownDevicesForAddressName:self.contact.contactJid]; - // if(devices.count>0) { - // dispatch_async(dispatch_get_main_queue(), ^{ - // [[DataLayer sharedInstance] encryptForJid:self.contact.contactJid andAccountNo:self.contact.accountId]; - // self.encryptChat=YES; - // }); - // } - // } - } - - NSString* messageType = [[DataLayer sharedInstance] messageTypeForMessage: message.messageText withKeepThread:YES]; - dispatch_async(dispatch_get_main_queue(), ^{ - NSString* finalMessageType = messageType; - if([message.messageType isEqualToString:kMessageTypeStatus]) - finalMessageType = kMessageTypeStatus; - message.messageType = finalMessageType; - if(!self.messageList) self.messageList = [[NSMutableArray alloc] init]; + + //update already existent message + for(size_t msgIdx = [self.messageList count]; msgIdx > 0; msgIdx--) + { + // find msg that should be updated + MLMessage* msgInList = [self.messageList objectAtIndex:(msgIdx - 1)]; + if([msgInList.messageDBId intValue] == [message.messageDBId intValue]) + { + //update message in our list + [msgInList updateWithMessage:message]; + + // Update table entry + NSIndexPath* indexPath = [NSIndexPath indexPathForRow:(msgIdx - 1) inSection:messagesSection]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_messageTable beginUpdates]; + [self->_messageTable reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + [self->_messageTable endUpdates]; + }); + return; + } + } + [self.messageList addObject:message]; //do not insert based on delay timestamp because that would make it possible to fake history entries [self->_messageTable beginUpdates]; @@ -1311,6 +1298,33 @@ -(void) handleNewMessage:(NSNotification *)notification } } +-(void) handleDeletedMessage:(NSNotification*) notification +{ + NSDictionary* dic = notification.userInfo; + MLMessage* msg = dic[@"message"]; + + DDLogDebug(@"Got deleted message notice for history id %ld and message id %@", (long)[msg.messageDBId intValue], msg.messageId); + + NSIndexPath* indexPath; + for(size_t msgIdx = [self.messageList count]; msgIdx > 0; msgIdx--) + { + // find msg that should be deleted + MLMessage* msgInList = [self.messageList objectAtIndex:(msgIdx - 1)]; + if([msgInList.messageDBId intValue] == [msg.messageDBId intValue]) + { + // Remove table entry + indexPath = [NSIndexPath indexPathForRow:(msgIdx - 1) inSection:messagesSection]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_messageTable beginUpdates]; + [self.messageList removeObjectAtIndex:indexPath.row]; + [self->_messageTable deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; + [self->_messageTable endUpdates]; + }); + break; + } + } +} + -(void) updateMsgState:(NSString *) messageId withEvent:(size_t) event withOptDic:(NSDictionary*) dic { NSIndexPath* indexPath; @@ -1382,6 +1396,36 @@ -(void) handleDisplayedMessage:(NSNotification*) notification [self updateMsgState:[dic objectForKey:kMessageId] withEvent:msgDisplayed withOptDic:nil]; } +-(void) handleFiletransferMessageUpdate:(NSNotification*) notification +{ + NSDictionary* dic = notification.userInfo; + MLMessage* msg = dic[@"message"]; + + DDLogDebug(@"Got filetransfer message update for history id %ld: %@ (%@)", (long)[msg.messageDBId intValue], msg.filetransferMimeType, msg.filetransferSize); + + NSIndexPath* indexPath; + for(size_t msgIdx = [self.messageList count]; msgIdx > 0; msgIdx--) + { + // find msg that should be updated + MLMessage* msgInList = [self.messageList objectAtIndex:(msgIdx - 1)]; + if([msgInList.messageDBId intValue] == [msg.messageDBId intValue]) + { + //update message in our list (this will copy filetransferMimeType and filetransferSize fields) + [msgInList updateWithMessage:msg]; + + //TODO JIM: do something on update (maybe this is not needed because handling will be done in filetransferChatCell) + + // Update table entry + indexPath = [NSIndexPath indexPathForRow:(msgIdx - 1) inSection:messagesSection]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_messageTable beginUpdates]; + [self->_messageTable reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + [self->_messageTable endUpdates]; + }); + break; + } + } +} -(void) scrollToBottom { @@ -1466,12 +1510,18 @@ -(NSString*) formattedTimeStampWithSource:(NSDate *) sourceDate -(void) retry:(id) sender { NSInteger msgHistoryID = ((UIButton*) sender).tag; - MLMessage* msg = [[DataLayer sharedInstance] messageForHistoryID:msgHistoryID]; + NSArray* msgArray = [[DataLayer sharedInstance] messagesForHistoryIDs:@[[NSNumber numberWithInteger:msgHistoryID]]]; + if(![msgArray count]) + { + DDLogError(@"Called retry for non existing message with history id %ld", (long)msgHistoryID); + return; + } + MLMessage* msg = msgArray[0]; DDLogDebug(@"Called retry for message with history id %ld: %@", (long)msgHistoryID, msg); UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Retry sending message?", @"") message:[NSString stringWithFormat:NSLocalizedString(@"This message failed to send (%@): %@", @""), msg.errorType, msg.errorReason] preferredStyle:UIAlertControllerStyleActionSheet]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Retry", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - [self sendMessage:msg.messageText andMessageID:msg.messageId]; + [self sendMessage:msg.messageText andMessageID:msg.messageId withType:nil]; //type not needed for messages already in history db //[self setMessageId:msg.messageId sent:YES]; // for the UI, db will be set in the notification }]]; [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { @@ -1533,25 +1583,25 @@ -(void) tableView:(UITableView*) tableView willDisplayCell:(nonnull UITableViewC -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath { - if(indexPath.section == reloadBoxSection) { + if(indexPath.section == reloadBoxSection) + { MLReloadCell* cell = (MLReloadCell*)[tableView dequeueReusableCellWithIdentifier:@"reloadBox" forIndexPath:indexPath]; - #if TARGET_OS_MACCATALYST +#if TARGET_OS_MACCATALYST // "Pull" could be a bit misleading on a mac cell.reloadLabel.text = NSLocalizedString(@"Scroll down to load more messages", @"mac only string"); - #endif +#endif // Remove selection style (if cell is pressed) cell.selectionStyle = UITableViewCellSelectionStyleNone; return cell; - } else if(indexPath.section != messagesSection) - return nil; + } MLBaseCell* cell; MLMessage* row; if(indexPath.row < self.messageList.count) { row = [self.messageList objectAtIndex:indexPath.row]; - } else { + } else { DDLogError(@"Attempt to access beyond bounds"); } @@ -1566,19 +1616,71 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS cell = [tableView dequeueReusableCellWithIdentifier:@"StatusCell"]; cell.messageBody.text = messageText; cell.link = nil; + cell.parent = self; return cell; } - else if([row.messageType isEqualToString:kMessageTypeImage]) + else if([row.messageType isEqualToString:kMessageTypeFiletransfer]) { - MLChatImageCell* imageCell = (MLChatImageCell *) [self messageTableCellWithIdentifier:@"image" andInbound:inDirection fromTable: tableView]; + DDLogVerbose(@"got filetransfer chat cell: %@ (%@)", row.filetransferMimeType, row.filetransferSize); + NSDictionary* info = [MLFiletransfer getFileInfoForMessage:row]; + + //TODO JIM: here we need the download and check-file buttons + + //TODO JIM: explanation: this was already downloaded and it is an image --> show this image inline + if(info && ![info[@"needsDownloading"] boolValue] && [info[@"mimeType"] hasPrefix:@"image/"]) + { + MLChatImageCell* imageCell = (MLChatImageCell *) [self messageTableCellWithIdentifier:@"image" andInbound:inDirection fromTable:tableView]; - if(![imageCell.link isEqualToString:messageText]){ - imageCell.link = messageText; - imageCell.thumbnailImage.image = nil; - imageCell.loading = NO; - [imageCell loadImageWithCompletion:^{}]; + if(imageCell.msg != row) + { + imageCell.msg = row; + imageCell.thumbnailImage.image = nil; + imageCell.loading = NO; + [imageCell loadImage]; + } + cell = imageCell; + } + //TODO JIM: explanation: this was already checked (mime ype and size are known) but not yet downloaded --> download it + //TODO JIM: explanation: this should not be automatically but only triggered by a button press + //TODO JIM: explanation: I'm doing this automatically here because we still lack those buttons + //TODO JIM: explanation: this only handles images, because we don't want to autodownload everything + else if(info && [info[@"needsDownloading"] boolValue] && [info[@"mimeType"] hasPrefix:@"image/"] && [[HelperTools defaultsDB] boolForKey:@"ShowImages"]) + [MLFiletransfer downloadFileForHistoryID:row.messageDBId]; + //TODO JIM: explanation: this was not yet checked, do an http head request to get mime type and size + //TODO JIM: explanation: this should not be automatically but only triggered by a button press + //TODO JIM: explanation: I'm doing this automatically here because we still lack those buttons + else if(info && [info[@"needsDownloading"] boolValue] && info[@"mimeType"] == nil) + [MLFiletransfer checkMimeTypeAndSizeForHistoryID:row.messageDBId]; + else + { + //TODO JIM: add handling for some other mime types and default handling for general files (e.g. "open this file" button) here + //TODO JIM: for now we just show the link as normal chat cell + } + + if(cell == nil) + { + //this is just a dummy to display something usable (the filetransfer url as link cell) + MLLinkCell* toreturn = (MLLinkCell *)[self messageTableCellWithIdentifier:@"link" andInbound:inDirection fromTable: tableView];; + toreturn.link = row.messageText; + toreturn.messageBody.text = toreturn.link; + + if(row.previewText || row.previewImage) + { + toreturn.imageUrl = row.previewImage; + toreturn.messageTitle.text = row.previewText; + [toreturn loadImageWithCompletion:^{}]; + } + else + { + [toreturn loadPreviewWithCompletion:^{ + // prevent repeated calls + if(toreturn.messageTitle.text.length == 0) + toreturn.messageTitle.text = @" "; + [[DataLayer sharedInstance] setMessageId:row.messageId previewText:toreturn.messageTitle.text andPreviewImage:toreturn.imageUrl.absoluteString]; + }]; + } + cell = toreturn; } - cell = imageCell; } else if([row.messageType isEqualToString:kMessageTypeUrl]) { @@ -1593,8 +1695,7 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS { toreturn.imageUrl = row.previewImage; toreturn.messageTitle.text = row.previewText; - [toreturn loadImageWithCompletion:^{ - }]; + [toreturn loadImageWithCompletion:^{}]; } else { @@ -1764,16 +1865,16 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS if(cell.outBound && ([row.errorType length] > 0 || [row.errorReason length] > 0) && !row.hasBeenReceived && row.hasBeenSent) { - cell.messageStatus.text = @"Error"; + cell.messageStatus.text = NSLocalizedString(@"Error", @""); cell.deliveryFailed = YES; } [cell updateCellWithNewSender:newSender]; [self resetHistoryAttributeForCell:cell]; - if (self.searchController.isActive) + if(self.searchController.isActive && row.messageDBId) { - if ([self.searchController isDBIdExited:row.messageDBId]) + if([self.searchController isDBIdExistent:row.messageDBId]) { NSMutableAttributedString *attributedMsgString = [self.searchController doSearchKeyword:self.searchController.searchBar.text onText:messageText @@ -1788,6 +1889,7 @@ -(UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NS #pragma mark - tableview delegate -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [self stopEditing]; [self.chatInput resignFirstResponder]; if(indexPath.section == reloadBoxSection) { [self loadOldMsgHistory]; @@ -1854,37 +1956,127 @@ - (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPat } } - -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { - if(indexPath.section == reloadBoxSection) { - return; +-(UISwipeActionsConfiguration*) tableView:(UITableView*) tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath*) indexPath +{ + self.editingCallback = nil; //stop editing (if there is some) on new swipe + + //don't allow swipe actions for our reload box + if(indexPath.section == reloadBoxSection) + return [UISwipeActionsConfiguration configurationWithActions:@[]]; + + //do some sanity checks + MLMessage* message; + if(indexPath.row < self.messageList.count) + message = [self.messageList objectAtIndex:indexPath.row]; + else + { + DDLogError(@"Attempt to access beyond bounds"); + return [UISwipeActionsConfiguration configurationWithActions:@[]]; + } + if(!message.messageDBId) + return [UISwipeActionsConfiguration configurationWithActions:@[]]; + + //configure swipe actions + + UIContextualAction* LMCEditAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:NSLocalizedString(@"Edit", @"") handler:^(UIContextualAction* action, UIView* sourceView, void (^completionHandler)(BOOL actionPerformed)) { + [self.chatInput setText:message.messageText]; //we want to begin editing using the old message + weakify(self); + self.editingCallback = ^(NSString* newBody) { + strongify(self); + if(newBody != nil) + { + message.messageText = newBody; + + [self.xmppAccount sendLMCForId:message.messageId withNewBody:newBody to:message.to]; + [[DataLayer sharedInstance] updateMessageHistory:message.messageDBId withText:newBody]; + + [self->_messageTable beginUpdates]; + [self->_messageTable reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; + [self->_messageTable endUpdates]; + + //update active chats if necessary + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalContactRefresh object:self.xmppAccount userInfo:@{@"contact": self.contact}]; + return completionHandler(YES); + } + else + [self.chatInput setText:@""]; + return completionHandler(NO); + }; + }]; + LMCEditAction.backgroundColor = UIColor.systemYellowColor; + if(@available(iOS 13.0, *)) + { + LMCEditAction.image = [[UIImage systemImageNamed:@"pencil.circle.fill"] imageWithTintColor:UIColor.whiteColor renderingMode:UIImageRenderingModeAutomatic]; } - if (editingStyle == UITableViewCellEditingStyleDelete) { - MLMessage* message = [self.messageList objectAtIndex:indexPath.row]; + + UIContextualAction* LMCDeleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:NSLocalizedString(@"Delete", @"") handler:^(UIContextualAction* action, UIView* sourceView, void (^completionHandler)(BOOL actionPerformed)) { + [self.xmppAccount sendLMCForId:message.messageId withNewBody:kMessageDeletedBody to:message.to]; + [[DataLayer sharedInstance] deleteMessageHistory:message.messageDBId]; - DDLogVerbose(@"%@", message); + [self->_messageTable beginUpdates]; + [self.messageList removeObjectAtIndex:indexPath.row]; + [self->_messageTable deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; + [self->_messageTable endUpdates]; - if(message.messageId) - { - [[DataLayer sharedInstance] deleteMessageHistory:message.messageDBId]; - } - else - { - return; - } + //update active chats if necessary + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalContactRefresh object:self.xmppAccount userInfo:@{@"contact": self.contact}]; + + return completionHandler(YES); + }]; + LMCDeleteAction.backgroundColor = UIColor.systemRedColor; + if(@available(iOS 13.0, *)) + { + LMCDeleteAction.image = [[UIImage systemImageNamed:@"trash.circle.fill"] imageWithTintColor:UIColor.whiteColor renderingMode:UIImageRenderingModeAutomatic]; + } + + UIContextualAction* localDeleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:NSLocalizedString(@"Delete Locally", @"") handler:^(UIContextualAction* action, UIView* sourceView, void (^completionHandler)(BOOL actionPerformed)) { + [[DataLayer sharedInstance] deleteMessageHistory:message.messageDBId]; + + [self->_messageTable beginUpdates]; [self.messageList removeObjectAtIndex:indexPath.row]; - [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; + [self->_messageTable deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; + [self->_messageTable endUpdates]; + + //update active chats if necessary + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalContactRefresh object:self.xmppAccount userInfo:@{@"contact": self.contact}]; + + return completionHandler(YES); + }]; + localDeleteAction.backgroundColor = UIColor.systemRedColor; + if(@available(iOS 13.0, *)) + { + localDeleteAction.image = [[UIImage systemImageNamed:@"trash.circle.fill"] imageWithTintColor:UIColor.whiteColor renderingMode:UIImageRenderingModeAutomatic]; } -} - -- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return YES; -} - -- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender -{ - return YES; + + UIContextualAction* copyAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:NSLocalizedString(@"Copy", @"") handler:^(UIContextualAction* action, UIView* sourceView, void (^completionHandler)(BOOL actionPerformed)) { + UIPasteboard* pasteboard = [UIPasteboard generalPasteboard]; + MLBaseCell* selectedCell = [self.messageTable cellForRowAtIndexPath:indexPath]; + if([selectedCell isKindOfClass:[MLChatImageCell class]]) + pasteboard.image = ((MLChatImageCell*)selectedCell).thumbnailImage.image; + else if([selectedCell isKindOfClass:[MLLinkCell class]]) + pasteboard.URL = [NSURL URLWithString:((MLLinkCell*)selectedCell).link]; + else + pasteboard.string = message.messageText; + return completionHandler(YES); + }]; + copyAction.backgroundColor = UIColor.systemGreenColor; + if(@available(iOS 13.0, *)) + { + copyAction.image = [[UIImage systemImageNamed:@"doc.on.doc.fill"] imageWithTintColor:UIColor.whiteColor renderingMode:UIImageRenderingModeAutomatic]; + } + + //only allow editing for the 2 newest outgoing message that were sent in the last 2 minutes + if([[DataLayer sharedInstance] checkLMCEligible:message.messageDBId from:self.xmppAccount.connectionProperties.identity.jid]) + return [UISwipeActionsConfiguration configurationWithActions:@[ + LMCEditAction, + LMCDeleteAction, + copyAction, + ]]; + else + return [UISwipeActionsConfiguration configurationWithActions:@[ + localDeleteAction, + copyAction, + ]]; } //dummy function needed to remove warnign @@ -1892,11 +2084,6 @@ -(void) openlink: (id) sender { } -- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender -{ - -} - -(void) scrollViewDidScroll:(UIScrollView *)scrollView { if(self.contact.isGroup) @@ -2184,4 +2371,50 @@ - (void)keyboardWillShow:(NSNotification*)aNotification // self.messageTable.scrollIndicatorInsets = contentInsets; } +-(void) tempfreezeAutoloading +{ + // Allow autoloading of more messages after a few seconds + self.viewIsScrolling = YES; + [HelperTools startTimer:1.5 withHandler:^{ + self.viewIsScrolling = NO; + }]; +} + +-(void) stopEditing +{ + if(self.editingCallback != nil) + self.editingCallback(nil); //dismiss swipe action +} + +-(void) showUploadHUD +{ + if(!self.uploadHUD) + { + self.uploadHUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; + self.uploadHUD.removeFromSuperViewOnHide = YES; + self.uploadHUD.label.text = NSLocalizedString(@"Uploading", @""); + self.uploadHUD.detailsLabel.text = NSLocalizedString(@"Uploading file to server", @""); + } + else + self.uploadHUD.hidden = NO; +} + +-(void) hideUploadHUD +{ + self.uploadHUD.hidden = YES; +} + +-(void) showPotentialError:(NSError*) error +{ + if(error) + { + DDLogError(@"Could not send attachment: %@", error); + UIAlertController* alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Could not upload file", @"") message:[NSString stringWithFormat:@"%@", error.localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [alert dismissViewControllerAnimated:YES completion:nil]; + }]]; + [self presentViewController:alert animated:YES completion:nil]; + } +} + @end diff --git a/Monal/Classes/xmpp.h b/Monal/Classes/xmpp.h index cdda76e8be..4088c2e207 100644 --- a/Monal/Classes/xmpp.h +++ b/Monal/Classes/xmpp.h @@ -38,8 +38,6 @@ typedef NS_ENUM (NSInteger, xmppRegistrationState) { FOUNDATION_EXPORT NSString* const kFileName; FOUNDATION_EXPORT NSString* const kContentType; FOUNDATION_EXPORT NSString* const kData; -FOUNDATION_EXPORT NSString* const kContact; -FOUNDATION_EXPORT NSString* const kCompletion; @class jingleCall; @class MLPubSub; @@ -75,9 +73,8 @@ typedef void (^monal_iq_handler_t)(XMPPIQ* _Nullable); // state attributes @property (nonatomic, strong) NSString* statusMessage; -@property (nonatomic, assign) BOOL awayState; -@property (nonatomic, strong) jingleCall *jingle; +@property (nonatomic, strong) jingleCall* _Nullable jingle; // DB info @property (nonatomic, strong) NSString* accountNo; @@ -101,16 +98,6 @@ typedef void (^monal_iq_handler_t)(XMPPIQ* _Nullable); @property (nonatomic, strong, readonly) NSSet* capsFeatures; @property (nonatomic, strong, readonly) NSString* capsHash; -extern NSString *const kMessageId; -extern NSString *const kSendTimer; - -extern NSString *const kXMPPError; -extern NSString *const kXMPPSuccess; -extern NSString *const kXMPPPresence; - -extern NSString* const kAccountState; -extern NSString* const kAccountHibernate; - -(id) initWithServer:(nonnull MLXMPPServer*) server andIdentity:(nonnull MLXMPPIdentity*) identity andAccountNo:(NSString*) accountNo; -(void) unfreezed; @@ -168,15 +155,6 @@ extern NSString* const kAccountHibernate; -(void) updateRosterItem:(NSString*) jid withName:(NSString*) name; #pragma mark set connection attributes -/** -sets the status message. makes xmpp call - */ --(void) setStatusMessageText:(NSString*) message; - -/** -sets away xmpp call. - */ --(void) setAway:(BOOL) away; /** join a room on the conference server @@ -227,7 +205,7 @@ Decline a call request -(void) requestHTTPSlotWithParams:(NSDictionary *)params andCompletion:(void(^)(NSString *url, NSError *error)) completion; --(void) setMAMQueryMostRecentForJid:(NSString*) jid before:(NSString*) uid withCompletion:(void (^)(NSArray* _Nullable)) completion; +-(void) setMAMQueryMostRecentForJid:(NSString*) jid before:(NSString* _Nullable) uid withCompletion:(void (^)(NSArray* _Nullable)) completion; -(void) setMAMPrefs:(NSString*) preference; -(void) getMAMPrefs; @@ -260,13 +238,15 @@ Decline a call request #pragma mark - internal stuff for processors --(void) addMessageToMamPageArray:(XMPPMessage*) messageNode forOuterMessageNode:(XMPPMessage*) outerMessageNode withBody:(NSString*) body andEncrypted:(BOOL) encrypted andShowAlert:(BOOL) showAlert andMessageType:(NSString*) messageType; +-(void) addMessageToMamPageArray:(XMPPMessage*) messageNode forOuterMessageNode:(XMPPMessage*) outerMessageNode withBody:(NSString* _Nullable) body andEncrypted:(BOOL) encrypted andMessageType:(NSString*) messageType; -(NSArray* _Nullable) getOrderedMamPageFor:(NSString*) mamQueryId; -(void) bindResource:(NSString*) resource; -(void) initSession; --(MLMessage*) parseMessageToMLMessage:(XMPPMessage*) messageNode withBody:(NSString*) body andEncrypted:(BOOL) encrypted andShowAlert:(BOOL) showAlert andMessageType:(NSString*) messageType andActualFrom:(NSString* _Nullable) actualFrom; +-(MLMessage*) parseMessageToMLMessage:(XMPPMessage*) messageNode withBody:(NSString*) body andEncrypted:(BOOL) encrypted andMessageType:(NSString*) messageType andActualFrom:(NSString* _Nullable) actualFrom; -(void) sendDisplayMarkerForId:(NSString*) messageid to:(NSString*) to; -(void) publishAvatar:(UIImage*) image; +-(void) publishStatusMessage:(NSString*) message; +-(void) sendLMCForId:(NSString*) messageid withNewBody:(NSString*) newBody to:(NSString*) to; +(NSDictionary*) invalidateState:(NSDictionary*) dic; diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 1c60400b19..3f9144f230 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -46,25 +46,14 @@ #define STATE_VERSION 1 -NSString *const kMessageId=@"MessageID"; -NSString *const kSendTimer=@"SendTimer"; - NSString *const kQueueID=@"queueID"; NSString *const kStanza=@"stanza"; - NSString *const kFileName=@"fileName"; NSString *const kContentType=@"contentType"; NSString *const kData=@"data"; -NSString *const kContact=@"contact"; - -NSString *const kCompletion=@"completion"; -NSString *const kXMPPError =@"error"; -NSString *const kXMPPSuccess =@"success"; -NSString *const kXMPPPresence = @"presence"; - @interface MLPubSub () -(id) initWithAccount:(xmpp*) account; -(NSDictionary*) getInternalData; @@ -105,9 +94,11 @@ @interface xmpp() BOOL _catchupDone; double _exponentialBackoff; BOOL _reconnectInProgress; + BOOL _disconnectInProgres; NSObject* _stateLockObject; //only used for @synchronized() blocks BOOL _lastIdleState; NSMutableDictionary* _mamPageArrays; + BOOL _firstLoginForThisInstance; //registration related stuff BOOL _registration; @@ -209,7 +200,9 @@ -(void) setupObjects _startTLSComplete = NO; _catchupDone = NO; _reconnectInProgress = NO; + _disconnectInProgres = NO; _lastIdleState = NO; + _firstLoginForThisInstance = YES; _outputQueue = [[NSMutableArray alloc] init]; _iqHandlers = [[NSMutableDictionary alloc] init]; _mamPageArrays = [[NSMutableDictionary alloc] init]; @@ -254,11 +247,9 @@ -(void) setupObjects _isCSIActive = NO; } _lastInteractionDate = [NSDate date]; //better default than 1970 - - self.statusMessage = [[HelperTools defaultsDB] stringForKey:@"StatusMessage"]; - self.awayState = [[HelperTools defaultsDB] boolForKey:@"Away"]; - - self.sendIdleNotifications = [[HelperTools defaultsDB] boolForKey: @"SendLastUserInteraction"]; + self.sendIdleNotifications = [[HelperTools defaultsDB] boolForKey:@"SendLastUserInteraction"]; + + self.statusMessage = @""; } -(void) dealloc @@ -308,7 +299,7 @@ -(void) setPubSubNotificationsForNodes:(NSArray* _Nonnull) nodes persistState:(B -(void) invalidXMLError { DDLogError(@"Server returned invalid xml!"); - [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:@[self, @"Server returned invalid xml!"]]; + [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:self userInfo:@{@"message": NSLocalizedString(@"Server returned invalid xml!", @""), @"isSevere": @NO}]; [self reconnect]; return; } @@ -352,19 +343,26 @@ -(void) observeValueForKeyPath:(NSString*) keyPath ofObject:(id) object change:( //only do the (more heavy but complete) idle check if we reache zero operations in the observed queue //we dispatch the idle check and subsequent notification on the receive queue to account for races //between the idle check and calls to disconnect issued in response to this idle notification - //NOTE: yes, doing the check for [_sendQueue operationCount] (inside [self idle]) from the receive queue is not race free + //NOTE: yes, doing the check for [_sendQueue operationCount] (inside [self idle]) from the receive queue is not race free. //with such disconnects, but: we only want to track the send queue on a best effort basis (because network sends are best effort, too) - //to some extent we want to make sure every stanza was physically sent out to the network before our app gets frozen by ios + //to some extent we want to make sure every stanza was physically sent out to the network before our app gets frozen by ios, //but we don't need to make this completely race free (network "races" can occur far more often than send queue races). //in a race the smacks unacked stanzas array will contain the not yet sent stanzas --> we won't loose stanzas when racing the send queue //with [self disconnect] through an idle check - if(![object operationCount]) + //NOTE: we only want to do an idle check if we are not in the middle of a disconnect call because this can race when the _bgTask is expiring + //and cancel the new _bgFetch because we are now idle (the dispatchAsyncOnReceiveQueue: will add a new task to the receive queue when + //the send queue gets cleaned up and this task will run as soon as the disconnect is done and interfere with the configuration of the + //_bgFetch and the syncError push notification both created on the main thread + if(![object operationCount] && !_disconnectInProgres) + { + DDLogVerbose(@"Adding idle state check to receive queue..."); [self dispatchAsyncOnReceiveQueue:^{ BOOL lastState = _lastIdleState; //only send out idle notifications if we changed from non-idle to idle state if(self.idle && !lastState) [[NSNotificationCenter defaultCenter] postNotificationName:kMonalIdle object:self]; }]; + } } } @@ -451,12 +449,15 @@ -(void) initTLS //see this for creating the proper protocols array: https://github.com/LLNL/FRS/blob/master/Pods/AWSIoT/AWSIoT/Internal/AWSIoTMQTTClient.m //WARNING: this will only have an effect if the TLS handshake was not already started (e.g. the TCP socket is not connected) and // will be ignored otherwise +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" SSLContextRef sslContext = (__bridge SSLContextRef) [_oStream propertyForKey: (__bridge NSString *) kCFStreamPropertySSLContext ]; CFStringRef strs[1]; strs[0] = CFSTR("xmpp-client"); CFArrayRef protocols = CFArrayCreate(NULL, (void *)strs, 1, &kCFTypeArrayCallBacks); SSLSetALPNProtocols(sslContext, protocols); CFRelease(protocols); +#pragma clang diagnostic pop } -(void) createStreams @@ -474,8 +475,8 @@ -(void) createStreams if((localIStream==nil) || (localOStream==nil)) { DDLogError(@"Connection failed"); - NSString *message=NSLocalizedString(@"Unable to connect to server",@ ""); - [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:@[self, message]]; + [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:self userInfo:@{@"message": NSLocalizedString(@"Unable to connect to server!", @""), @"isSevere": @NO}]; + [self reconnect]; return; } else @@ -532,9 +533,11 @@ -(BOOL) connectionTask // Check if entry "." == srv target if(![[row objectForKey:@"isEnabled"] boolValue]) { - NSString *message = NSLocalizedString(@"SRV entry prohibits XMPP connection",@ ""); - DDLogInfo(@"%@ for domain %@", message, self.connectionProperties.identity.domain); - [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:@[self, message]]; + DDLogInfo(@"SRV entry prohibits XMPP connection for server %@", self.connectionProperties.identity.domain); + [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:self userInfo:@{ + @"message": [NSString stringWithFormat:NSLocalizedString(@"SRV entry prohibits XMPP connection for server %@", @""), self.connectionProperties.identity.domain], + @"isSevere": @YES + }]; return YES; } } @@ -542,7 +545,8 @@ -(BOOL) connectionTask // if all servers have been tried start over with the first one again if([_discoveredServersList count]>0 && [_usableServersList count]==0) { - DDLogWarn(@"All %lu SRV dns records tried, starting over again", (unsigned long)[_discoveredServersList count]); + if(!_firstLoginForThisInstance) + DDLogWarn(@"All %lu SRV dns records tried, starting over again", (unsigned long)[_discoveredServersList count]); _usableServersList = [_discoveredServersList mutableCopy]; for(NSDictionary *row in _usableServersList) { @@ -682,6 +686,7 @@ -(void) disconnect:(BOOL) explicitLogout return; } DDLogInfo(@"disconnecting"); + _disconnectInProgres = YES; [_parseQueue cancelAllOperations]; //throw away all parsed but not processed stanzas (we should be logged out then!) [_receiveQueue cancelAllOperations]; //stop everything coming after this (we should be logged out then!) @@ -768,6 +773,7 @@ -(void) disconnect:(BOOL) explicitLogout [self closeSocket]; [self accountStatusChanged]; + _disconnectInProgres = NO; }]; } @@ -778,10 +784,11 @@ -(void) closeSocket [_receiveQueue cancelAllOperations]; //stop everything coming after this (we should have closed sockets then!) //prevent any new read or write - if(_xmlParser!=nil) + if(_xmlParser != nil) { [_xmlParser setDelegate:nil]; [_xmlParser abortParsing]; + _xmlParser = nil; } [self->_iPipe close]; self->_iPipe = nil; @@ -1229,7 +1236,7 @@ -(void) processInput:(MLXMLNode*) parsedStanza else if([parsedStanza check:@"/{urn:xmpp:sm:3}a"] && self.connectionProperties.supportsSM3 && self.accountState>=kStateBound) { NSNumber* h = [parsedStanza findFirst:@"/@h|int"]; - if(!h) + if(h==nil) return [self invalidXMLError]; @synchronized(_stateLockObject) { @@ -1252,8 +1259,15 @@ -(void) processInput:(MLXMLNode*) parsedStanza if([presenceNode.fromUser isEqualToString:self.connectionProperties.identity.jid]) { - //ignore self presences for now - DDLogInfo(@"ignoring presence from self"); + DDLogInfo(@"got self presence"); + + //ignore special presences for status updates (they don't have one) + if(![presenceNode check:@"/@type"]) + { + NSMutableDictionary* accountDetails = [[DataLayer sharedInstance] detailsForAccount:self.accountNo]; + accountDetails[@"statusMessage"] = [presenceNode check:@"status#"] ? [presenceNode findFirst:@"status#"] : @""; + [[DataLayer sharedInstance] updateAccounWithDictionary:accountDetails]; + } } else { @@ -1318,17 +1332,34 @@ -(void) processInput:(MLXMLNode*) parsedStanza //handle last interaction time (dispatch database update in own background thread) if([presenceNode check:@"{urn:xmpp:idle:1}idle@since"]) + { + NSDate* lastInteraction = [[DataLayer sharedInstance] lastInteractionOfJid:presenceNode.fromUser forAccountNo:self.accountNo]; + + if([[presenceNode findFirst:@"{urn:xmpp:idle:1}idle@since|datetime"] compare:lastInteraction] == NSOrderedDescending) + { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[DataLayer sharedInstance] setLastInteraction:[presenceNode findFirst:@"{urn:xmpp:idle:1}idle@since|datetime"] forJid:presenceNode.fromUser andAccountNo:self.accountNo]; + + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalLastInteractionUpdatedNotice object:self userInfo:@{ + @"jid": presenceNode.fromUser, + @"accountNo": self.accountNo, + @"lastInteraction": [presenceNode findFirst:@"{urn:xmpp:idle:1}idle@since|datetime"], + @"isTyping": @NO + }]; + }); + } + } + else + { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[DataLayer sharedInstance] setLastInteraction:[presenceNode findFirst:@"{urn:xmpp:idle:1}idle@since|datetime"] forJid:presenceNode.fromUser andAccountNo:self.accountNo]; + [[NSNotificationCenter defaultCenter] postNotificationName:kMonalLastInteractionUpdatedNotice object:self userInfo:@{ + @"jid": presenceNode.fromUser, + @"accountNo": self.accountNo, + @"lastInteraction": [[NSDate date] initWithTimeIntervalSince1970:0], //nil cannot directly be saved in NSDictionary + @"isTyping": @NO + }]; }); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kMonalLastInteractionUpdatedNotice object:self userInfo:@{ - @"jid": presenceNode.fromUser, - @"accountNo": self.accountNo, - @"lastInteraction": [presenceNode check:@"{urn:xmpp:idle:1}idle@since"] ? [presenceNode findFirst:@"{urn:xmpp:idle:1}idle@since|datetime"] : [[NSDate date] initWithTimeIntervalSince1970:0], //nil cannot directly be saved in NSDictionary - @"isTyping": @NO - }]; - }); + } } else { @@ -1441,11 +1472,6 @@ -(void) processInput:(MLXMLNode*) parsedStanza if(!messageNode.to) messageNode.to = self.connectionProperties.identity.fullJid; - DDLogVerbose(@"Incoming outer message from='%@' to='%@' -- inner message from='%@' to='%@'", outerMessageNode.from, outerMessageNode.to, messageNode.from, messageNode.to); - DDLogVerbose(@"Incoming outer message fromUser='%@' toUser='%@' -- inner message fromUser='%@' toUser='%@'", outerMessageNode.fromUser, outerMessageNode.toUser, messageNode.fromUser, messageNode.toUser); - DDLogVerbose(@"Raw outer from value: '%@', raw inner from value: '%@'", outerMessageNode.attributes[@"from"], messageNode.attributes[@"from"]); - DDLogVerbose(@"Raw outer to value: '%@', raw inner to value: '%@'", outerMessageNode.attributes[@"to"], messageNode.attributes[@"to"]); - NSAssert(![messageNode.fromUser containsString:@"/"], @"messageNode.fromUser contains resource!"); NSAssert(![messageNode.toUser containsString:@"/"], @"messageNode.toUser contains resource!"); NSAssert(![outerMessageNode.fromUser containsString:@"/"], @"outerMessageNode.fromUser contains resource!"); @@ -1546,7 +1572,7 @@ -(void) processInput:(MLXMLNode*) parsedStanza else if([parsedStanza check:@"/{urn:xmpp:sm:3}resumed"] && self.connectionProperties.supportsSM3 && self.accountState 0) - self.statusMessage = message; - else - message = nil; - [self sendPresence]; -} - --(void) setAway:(BOOL) away -{ - self.awayState = away; - [self sendPresence]; -} - -(void) setBlocked:(BOOL) blocked forJid:(NSString* _Nonnull) blockedJid { XMPPIQ* iqBlocked= [[XMPPIQ alloc] initWithType:kiqSetType]; @@ -2392,7 +2402,7 @@ -(void) requestHTTPSlotWithParams:(NSDictionary*) params andCompletion:(void(^)( headers:headers withArguments:nil data:[params objectForKey:kData] - andCompletionHandler:^(NSError *error, id result) { + andCompletionHandler:^(NSError* error, id result) { if(!error) { DDLogInfo(@"Upload succeded, get url: %@", [response findFirst:@"{urn:xmpp:http:upload:0}slot/get@url"]); @@ -2411,7 +2421,7 @@ -(void) requestHTTPSlotWithParams:(NSDictionary*) params andCompletion:(void(^)( }); } andErrorHandler:^(XMPPIQ* error) { if(completion) - completion(nil, [error findFirst:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}text#"]); + completion(nil, [NSError errorWithDomain:@"MonalError" code:0 userInfo:@{NSLocalizedDescriptionKey: [HelperTools extractXMPPError:error withDescription:@"Upload Error"]}]); }]; } @@ -2495,29 +2505,20 @@ -(void) sendCurrentCSIState #pragma mark - Message archive --(void) setMAMPrefs:(NSString *) preference +-(void) setMAMPrefs:(NSString*) preference { if(!self.connectionProperties.supportsMam2) return; XMPPIQ* query = [[XMPPIQ alloc] initWithId:[[NSUUID UUID] UUIDString] andType:kiqSetType]; [query updateMamArchivePrefDefault:preference]; - [self send:query]; + [self sendIq:query withHandler:$newHandler(MLIQProcessor, handleSetMamPrefs)]; } -(void) getMAMPrefs { XMPPIQ* query = [[XMPPIQ alloc] initWithId:[[NSUUID UUID] UUIDString] andType:kiqGetType]; [query mamArchivePref]; - //this can be a non persistent iq handler because getMAMPrefs is only called from the ui - //THIS HAS TO BE CHANGED IF THIS METHOD IS CALLED FROM OTHER MORE PERSISTENT PLACES - [self sendIq:query withResponseHandler:^(XMPPIQ* response) { - if([response check:@"{urn:xmpp:mam:2}prefs@default"]) - [[NSNotificationCenter defaultCenter] postNotificationName:kMLMAMPref object:@{@"mamPref": [response findFirst:@"{urn:xmpp:mam:2}prefs@default"]}]; - else - DDLogError(@"MAM prefs query returned unexpected result: %@", response); - } andErrorHandler:^(XMPPIQ* error) { - DDLogError(@"MAM prefs query returned an error: %@", error); - }]; + [self sendIq:query withHandler:$newHandler(MLIQProcessor, handleMamPrefs)]; } -(void) setMAMQueryMostRecentForJid:(NSString*) jid before:(NSString*) uid withCompletion:(void (^)(NSArray* _Nullable)) completion @@ -2529,7 +2530,8 @@ -(void) setMAMQueryMostRecentForJid:(NSString*) jid before:(NSString*) uid withC //insert messages having a body into the db and check if they are alread in there for(MLMessage* msg in [self getOrderedMamPageFor:[response findFirst:@"{urn:xmpp:mam:2}fin@queryid"]]) if(msg.messageText) - [[DataLayer sharedInstance] addMessageFrom:msg.from + { + NSNumber* historyId = [[DataLayer sharedInstance] addMessageFrom:msg.from to:msg.to forAccount:self.accountNo withBody:msg.messageText @@ -2543,19 +2545,21 @@ -(void) setMAMQueryMostRecentForJid:(NSString*) jid before:(NSString*) uid withC encrypted:msg.encrypted backwards:YES displayMarkerWanted:NO - withCompletion:^(BOOL success, NSString* newMessageType, NSNumber* historyId) { - //add successfully added messages to our display list - if(success) - [messageList addObject:msg]; - }]; - DDLogVerbose(@"collected mam:2 before-pages now contain %lu messages in summary not already in history", (unsigned long)[messageList count]); + ]; + msg.messageDBId = historyId; //retrofit messageDBId + //add successfully added messages to our display list + if(historyId != nil) + [messageList addObject:msg]; + } + + DDLogDebug(@"collected mam:2 before-pages now contain %lu messages in summary not already in history", (unsigned long)[messageList count]); //call completion to display all messages saved in db if we have enough messages or reached end of mam archive if([messageList count] >= 25) completion(messageList); else { - //page through to get more messages (a page possibly contians fewer than 25 messages having a body) - //but because we query for 50 stanzas we easily could get more than 25 messages having a body, too + //page through to get more messages (a page possibly contains fewer than 25 messages having a body) + //but because we query for 50 stanzas we could easily get more than 25 messages having a body, too if( ![[response findFirst:@"{urn:xmpp:mam:2}fin@complete|bool"] boolValue] && [response check:@"{urn:xmpp:mam:2}fin/{http://jabber.org/protocol/rsm}set/first#"] @@ -2565,13 +2569,13 @@ -(void) setMAMQueryMostRecentForJid:(NSString*) jid before:(NSString*) uid withC } else { - DDLogVerbose(@"Reached upper end of mam:2 archive, returning %lu messages to ui", (unsigned long)[messageList count]); + DDLogDebug(@"Reached upper end of mam:2 archive, returning %lu messages to ui", (unsigned long)[messageList count]); completion(messageList); //can be fewer than 25 messages because we reached the upper end of the mam archive } } }; - query = ^(NSString* before) { - DDLogVerbose(@"Loading (next) mam:2 page before: %@", before); + query = ^(NSString* _Nullable before) { + DDLogDebug(@"Loading (next) mam:2 page before: %@", before); XMPPIQ* query = [[XMPPIQ alloc] initWithId:[[NSUUID UUID] UUIDString] andType:kiqSetType]; [query setMAMQueryLatestMessagesForJid:jid before:before]; //we always want to use blocks here because we want to make sure we get not interrupted by an app crash/restart @@ -2673,7 +2677,7 @@ -(void)call:(MLContact*) contact -(void)hangup:(MLContact*) contact { - XMPPIQ* jingleiq =[self.jingle terminateJinglewithId:[[NSUUID UUID] UUIDString]]; + XMPPIQ* jingleiq = [self.jingle terminateJinglewithId:[[NSUUID UUID] UUIDString]]; [self send:jingleiq]; [self.jingle rtpDisconnect]; self.jingle=nil; @@ -2697,11 +2701,10 @@ -(void)declineCall:(NSDictionary*) userInfo -(void) jingleResult:(XMPPIQ*) iqNode { //confirmation of set call after we accepted - if([[iqNode findFirst:@"/@id"] isEqualToString:self.jingle.idval]) + NSString* from = iqNode.fromUser; + if(from && [[iqNode findFirst:@"/@id"] isEqualToString:self.jingle.idval]) { - NSString* from = iqNode.fromUser; NSString* fullName = from; - if(!fullName) fullName = from; NSDictionary* userDic=@{@"buddy_name":from, @"full_name":fullName, kAccountID:self.accountNo @@ -2851,7 +2854,7 @@ -(void) changePassword:(NSString *) newPass withCompletion:(xmppCompletion) comp //dispatch completion handler outside of the receiveQueue if(completion) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - completion(NO, error && [error check:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}text#"] ? [error findFirst:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}text#"]: @""); + completion(NO, error ? [HelperTools extractXMPPError:error withDescription:@"Could not change password"] : @""); }); }]; } @@ -2897,7 +2900,7 @@ -(void) requestRegForm //dispatch completion handler outside of the receiveQueue if(_regFormErrorCompletion) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - _regFormErrorCompletion(nil, nil); + _regFormErrorCompletion(NO, [HelperTools extractXMPPError:error withDescription:@"Could not request registration form"]); }); }]; } @@ -2917,7 +2920,7 @@ -(void) submitRegForm //dispatch completion handler outside of the receiveQueue if(_regFormSubmitCompletion) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - _regFormSubmitCompletion(NO, [error findFirst:@"error/{urn:ietf:params:xml:ns:xmpp-stanzas}text#"]); + _regFormSubmitCompletion(NO, [HelperTools extractXMPPError:error withDescription:@"Could not submit registration"]); }); }]; } @@ -2963,22 +2966,22 @@ - (void)stream:(NSStream*) stream handleEvent:(NSStreamEvent) eventCode switch(st_error.code) { case errSSLXCertChainInvalid: { - message = NSLocalizedString(@"SSL Error: Certificate chain is invalid",@ ""); + message = NSLocalizedString(@"SSL Error: Certificate chain is invalid", @""); break; } case errSSLUnknownRootCert: { - message = NSLocalizedString(@"SSL Error: Unknown root certificate",@ ""); + message = NSLocalizedString(@"SSL Error: Unknown root certificate", @""); break; } case errSSLCertExpired: { - message = NSLocalizedString(@"SSL Error: Certificate expired",@ ""); + message = NSLocalizedString(@"SSL Error: Certificate expired", @""); break; } case errSSLHostNameMismatch: { - message = NSLocalizedString(@"SSL Error: Host name mismatch",@ ""); + message = NSLocalizedString(@"SSL Error: Host name mismatch", @""); break; } @@ -2987,7 +2990,7 @@ - (void)stream:(NSStream*) stream handleEvent:(NSStreamEvent) eventCode { // Do not show "Connection refused" message if there are more SRV records to try if(!_SRVDiscoveryDone || (_SRVDiscoveryDone && [_usableServersList count] == 0) || st_error.code != 61) - [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:@[self, message]]; + [[NSNotificationCenter defaultCenter] postNotificationName:kXMPPError object:self userInfo:@{@"message": message, @"isSevere": @NO}]; } DDLogInfo(@"stream error, calling reconnect"); @@ -3179,7 +3182,7 @@ -(void) mamFinished } } --(MLMessage*) parseMessageToMLMessage:(XMPPMessage*) messageNode withBody:(NSString*) body andEncrypted:(BOOL) encrypted andShowAlert:(BOOL) showAlert andMessageType:(NSString*) messageType andActualFrom:(NSString*) actualFrom +-(MLMessage*) parseMessageToMLMessage:(XMPPMessage*) messageNode withBody:(NSString*) body andEncrypted:(BOOL) encrypted andMessageType:(NSString*) messageType andActualFrom:(NSString*) actualFrom { MLMessage* message = [[MLMessage alloc] init]; message.from = messageNode.fromUser; @@ -3191,7 +3194,6 @@ -(MLMessage*) parseMessageToMLMessage:(XMPPMessage*) messageNode withBody:(NSStr message.encrypted = encrypted; message.delayTimeStamp = [messageNode findFirst:@"{urn:xmpp:delay}delay@stamp|datetime"]; message.timestamp = [NSDate date]; - message.shouldShowAlert = showAlert; message.messageType = messageType; message.hasBeenSent = YES; //if it came in it has been sent to the server message.stanzaId = [messageNode findFirst:@"{urn:xmpp:sid:0}stanza-id@id"]; @@ -3199,9 +3201,10 @@ -(MLMessage*) parseMessageToMLMessage:(XMPPMessage*) messageNode withBody:(NSStr return message; } --(void) addMessageToMamPageArray:(XMPPMessage* _Nonnull) messageNode forOuterMessageNode:(XMPPMessage* _Nonnull) outerMessageNode withBody:(NSString* _Nonnull) body andEncrypted:(BOOL) encrypted andShowAlert:(BOOL) showAlert andMessageType:(NSString* _Nonnull) messageType +-(void) addMessageToMamPageArray:(XMPPMessage*) messageNode forOuterMessageNode:(XMPPMessage*) outerMessageNode withBody:(NSString*) body andEncrypted:(BOOL) encrypted andMessageType:(NSString*) messageType { - MLMessage* message = [self parseMessageToMLMessage:messageNode withBody:body andEncrypted:encrypted andShowAlert:showAlert andMessageType:messageType andActualFrom:nil]; + MLMessage* message = [self parseMessageToMLMessage:messageNode withBody:body andEncrypted:encrypted andMessageType:messageType andActualFrom:nil]; + message.stanzaId = [outerMessageNode findFirst:@"{urn:xmpp:mam:2}result@id"]; //use the stanza id provided directly by mam @synchronized(_mamPageArrays) { if(!_mamPageArrays[[outerMessageNode findFirst:@"{urn:xmpp:mam:2}result@queryid"]]) _mamPageArrays[[outerMessageNode findFirst:@"{urn:xmpp:mam:2}result@queryid"]] = [[NSMutableArray alloc] init]; @@ -3209,7 +3212,7 @@ -(void) addMessageToMamPageArray:(XMPPMessage* _Nonnull) messageNode forOuterMes } } --(NSArray* _Nullable) getOrderedMamPageFor:(NSString* _Nonnull) mamQueryId +-(NSArray* _Nullable) getOrderedMamPageFor:(NSString*) mamQueryId { NSArray* array; @synchronized(_mamPageArrays) { @@ -3308,4 +3311,19 @@ -(void) publishAvatar:(UIImage*) image }); } +-(void) publishStatusMessage:(NSString*) message +{ + self.statusMessage = message; + [self sendPresence]; +} + +-(void) sendLMCForId:(NSString*) messageid withNewBody:(NSString*) newBody to:(NSString*) to +{ + XMPPMessage* LMCNode = [[XMPPMessage alloc] init]; + LMCNode.attributes[@"type"] = kMessageChatType; + LMCNode.attributes[@"to"] = to; + [LMCNode setLMCFor:messageid withNewBody:newBody]; + [self send:LMCNode]; +} + @end diff --git a/Monal/Monal.xcodeproj/project.pbxproj b/Monal/Monal.xcodeproj/project.pbxproj old mode 100755 new mode 100644 index 886608d25d..00937aa39d --- a/Monal/Monal.xcodeproj/project.pbxproj +++ b/Monal/Monal.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 53; objects = { /* Begin PBXBuildFile section */ @@ -11,7 +11,6 @@ 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 1E779E002AE14108CB07C10A /* Pods_jrtplib_static.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25CAD84C2B49BA56A354B822 /* Pods_jrtplib_static.framework */; }; 2601D9CB0FBF25EF004DB939 /* sworim.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 2601D9CA0FBF25EF004DB939 /* sworim.sqlite */; }; 2601D9D40FBF262E004DB939 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2601D9D30FBF262E004DB939 /* libsqlite3.dylib */; }; 2602A70922A5431B006D3E72 /* MLRegSuccessViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2602A70822A5431A006D3E72 /* MLRegSuccessViewController.m */; }; @@ -25,7 +24,6 @@ 26189C8B238A526500EAD70C /* MLSubscriptionTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 26189C8A238A526500EAD70C /* MLSubscriptionTableViewController.m */; }; 261A6281176C055400059090 /* ContactsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 261A627F176C055400059090 /* ContactsViewController.m */; }; 261A6285176C156500059090 /* ActiveChatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 261A6284176C156500059090 /* ActiveChatsViewController.m */; }; - 261A6288176C157D00059090 /* MLDisplaySettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 261A6287176C157D00059090 /* MLDisplaySettingsViewController.m */; }; 261A628B176C159000059090 /* AccountsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 261A628A176C159000059090 /* AccountsViewController.m */; }; 261A628E176C15AD00059090 /* ChatLogsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 261A628D176C15AD00059090 /* ChatLogsViewController.m */; }; 261E542523A0A1D300394F59 /* monalxmpp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26CC579223A0867400ABB92A /* monalxmpp.framework */; }; @@ -229,8 +227,7 @@ 26F9794D1ACAC73A0008E005 /* MLContactCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 26F9794C1ACAC73A0008E005 /* MLContactCell.xib */; }; 26FE3BCB1C61A6C3003CC230 /* MLResizingTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 26FE3BCA1C61A6C3003CC230 /* MLResizingTextView.m */; }; 288765A50DF7441C002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765A40DF7441C002DB57D /* CoreGraphics.framework */; }; - 322AD0CC78815E4551793930 /* Pods_Monal_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F59B43550E822D135D1127E /* Pods_Monal_Tests.framework */; }; - 3517ED822FCB1456A8D492B1 /* Pods_Monal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C40CE7301D792181A28A1F8 /* Pods_Monal.framework */; }; + 2BA392FE2644C7215E2A6EEE /* Pods_Monal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D6C5046292E68BB30C97B38 /* Pods_Monal.framework */; }; 38720923251EDE07001837EB /* MLXEPSlashMeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 38720921251EDE07001837EB /* MLXEPSlashMeHandler.m */; }; 540A3FAD24D674BD0008965D /* MLSQLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 540A3FAC24D674BD0008965D /* MLSQLite.m */; }; 540BD0D224D8D1F40087A743 /* IPC.h in Headers */ = {isa = PBXBuildFile; fileRef = 540BD0D124D8D1F20087A743 /* IPC.h */; }; @@ -250,16 +247,23 @@ 541E4CC7254D370200FD7B28 /* MLPubSubProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 541E4CC6254D370100FD7B28 /* MLPubSubProcessor.m */; }; 544656BB2534910D006B2953 /* XMPPDataForm.m in Sources */ = {isa = PBXBuildFile; fileRef = 544656BA2534910D006B2953 /* XMPPDataForm.m */; }; 544656BD25349133006B2953 /* XMPPDataForm.h in Headers */ = {isa = PBXBuildFile; fileRef = 544656BC25349133006B2953 /* XMPPDataForm.h */; }; + 54507CE5255D8C14007092F4 /* MLFiletransfer.m in Sources */ = {isa = PBXBuildFile; fileRef = 54507CE4255D8C14007092F4 /* MLFiletransfer.m */; }; + 54507CE8255D8C47007092F4 /* MLFiletransfer.h in Headers */ = {isa = PBXBuildFile; fileRef = 54507CE7255D8C47007092F4 /* MLFiletransfer.h */; }; 5455BC3324EB776F0024D80F /* MLUDPLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 5455BC3024EB776B0024D80F /* MLUDPLogger.m */; }; 5455BC3424EB776F0024D80F /* MLUDPLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 5455BC3224EB776E0024D80F /* MLUDPLogger.h */; }; 54D2307E24CB0F4600638D65 /* monalxmpp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26CC579223A0867400ABB92A /* monalxmpp.framework */; }; - 54D2308224CB0F5400638D65 /* Pods_NotificaionService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C28AE0807330E8B6F972591 /* Pods_NotificaionService.framework */; }; 54D2308424CB10EE00638D65 /* MLLogFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C15D504A24C727CC002F75BB /* MLLogFileManager.m */; }; 54E594BD2523C34B00E4172B /* MLPubSub.h in Headers */ = {isa = PBXBuildFile; fileRef = 54E594BA2523C34900E4172B /* MLPubSub.h */; }; 54E594BE2523C34B00E4172B /* MLPubSub.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E594BC2523C34A00E4172B /* MLPubSub.m */; }; + 5E7A3AAABB0B465D0486DD14 /* Pods_Monal_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 190BE683A0C65E5A839F52BD /* Pods_Monal_Tests.framework */; }; + 6C0881EAB68D8D4760C8F4B8 /* Pods_jrtplib_static.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FD41A731431B8DC2D41B808 /* Pods_jrtplib_static.framework */; }; + B2F4C53CD9A8A4046C237072 /* Pods_NotificaionService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E865A50FFD3C29AD8F3DFD0 /* Pods_NotificaionService.framework */; }; + B6C2B2781C2EDD10E9341233 /* Pods_monalxmpp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F17B168B23E046B18971F63 /* Pods_monalxmpp.framework */; }; + BE24210F5A8DEA2D7DC913F1 /* Pods_shareSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12CB538E8FC0AAFD96856CDD /* Pods_shareSheet.framework */; }; C12436142434AB5D00B8F074 /* MLAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = C12436132434AB5D00B8F074 /* MLAttributedLabel.m */; }; C13EBB8E24DC685C008AADDA /* MLPrivacySettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C13EBB8D24DC685C008AADDA /* MLPrivacySettingsViewController.m */; }; C1414E9D24312F0100948788 /* MLChatMapsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C1414E9C24312F0100948788 /* MLChatMapsCell.m */; }; + C15489B925680BBE00BBA2F0 /* MLQRCodeScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15489B825680BBE00BBA2F0 /* MLQRCodeScanner.swift */; }; C1613B5A2520723D0062C0C2 /* MLBasePaser.h in Headers */ = {isa = PBXBuildFile; fileRef = C1613B572520723C0062C0C2 /* MLBasePaser.h */; }; C1613B5B2520723D0062C0C2 /* MLBasePaser.m in Sources */ = {isa = PBXBuildFile; fileRef = C1613B592520723C0062C0C2 /* MLBasePaser.m */; }; C18E757A245E8AE900AE8FB7 /* MLPipe.h in Headers */ = {isa = PBXBuildFile; fileRef = C18E7578245E8AE900AE8FB7 /* MLPipe.h */; }; @@ -270,9 +274,7 @@ C1C839DD24F15DF800BBCF17 /* MLOMEMO.h in Headers */ = {isa = PBXBuildFile; fileRef = C1C839DA24F15DF800BBCF17 /* MLOMEMO.h */; }; C1C839DE24F15DF800BBCF17 /* MLOMEMO.m in Sources */ = {isa = PBXBuildFile; fileRef = C1C839DC24F15DF800BBCF17 /* MLOMEMO.m */; }; C1E4654824EE517000CA5AAF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1E4654624EE517000CA5AAF /* Localizable.strings */; }; - D838DFA541E4102BCA92A60E /* Pods_monalxmpp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC355146D687D83135E2BBF7 /* Pods_monalxmpp.framework */; }; E8DED06225388BE8003167FF /* MLSearchViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E8DED06125388BE8003167FF /* MLSearchViewController.m */; }; - FE9BEACD951D0F8D3EBE264D /* Pods_shareSheet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99727E17D81471E803966620 /* Pods_shareSheet.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -390,14 +392,19 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 04A27909A4CEFD3AEB9E267A /* Pods_Monal_OSX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Monal_OSX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 10A52735AA22DE00F1CE98E6 /* Pods_monalxmppmac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_monalxmppmac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 1CB94D0245DE1717B2AA3064 /* Pods-Monal.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-Monal/Pods-Monal.adhoc.xcconfig"; sourceTree = ""; }; + 061EF1BEDEE7A71FDF9AB402 /* Pods-shareSheet.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shareSheet.appstore.xcconfig"; path = "Target Support Files/Pods-shareSheet/Pods-shareSheet.appstore.xcconfig"; sourceTree = ""; }; + 0D0362F6B26B9407BE313F36 /* Pods-NotificaionService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificaionService.debug.xcconfig"; path = "Target Support Files/Pods-NotificaionService/Pods-NotificaionService.debug.xcconfig"; sourceTree = ""; }; + 0EC0A5305E72C0F7F9E1CDEF /* Pods-shareSheet.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shareSheet.adhoc.xcconfig"; path = "Target Support Files/Pods-shareSheet/Pods-shareSheet.adhoc.xcconfig"; sourceTree = ""; }; + 127D3489728DDC3A97F4293A /* Pods-jrtplib-static.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static.adhoc.xcconfig"; path = "Target Support Files/Pods-jrtplib-static/Pods-jrtplib-static.adhoc.xcconfig"; sourceTree = ""; }; + 12CB538E8FC0AAFD96856CDD /* Pods_shareSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_shareSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 190BE683A0C65E5A839F52BD /* Pods_Monal_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Monal_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D3623240D0F684500981E51 /* MonalAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MonalAppDelegate.h; path = Classes/MonalAppDelegate.h; sourceTree = ""; }; 1D3623250D0F684500981E51 /* MonalAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MonalAppDelegate.m; path = Classes/MonalAppDelegate.m; sourceTree = ""; }; + 1D46F251C198E3D8FA55692F /* Pods-Monal.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal.appstore.xcconfig"; path = "Target Support Files/Pods-Monal/Pods-Monal.appstore.xcconfig"; sourceTree = ""; }; 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - 25CAD84C2B49BA56A354B822 /* Pods_jrtplib_static.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_jrtplib_static.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1F17B168B23E046B18971F63 /* Pods_monalxmpp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_monalxmpp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 21E99538324C14220843F325 /* Pods-shareSheet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shareSheet.debug.xcconfig"; path = "Target Support Files/Pods-shareSheet/Pods-shareSheet.debug.xcconfig"; sourceTree = ""; }; 2601D9A70FBF255D004DB939 /* DataLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataLayer.m; sourceTree = ""; }; 2601D9A80FBF255D004DB939 /* DataLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataLayer.h; sourceTree = ""; }; 2601D9CA0FBF25EF004DB939 /* sworim.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = sworim.sqlite; sourceTree = ""; }; @@ -435,8 +442,6 @@ 261A627F176C055400059090 /* ContactsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactsViewController.m; sourceTree = ""; }; 261A6283176C156500059090 /* ActiveChatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ActiveChatsViewController.h; sourceTree = ""; }; 261A6284176C156500059090 /* ActiveChatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ActiveChatsViewController.m; sourceTree = ""; }; - 261A6286176C157D00059090 /* MLDisplaySettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLDisplaySettingsViewController.h; sourceTree = ""; }; - 261A6287176C157D00059090 /* MLDisplaySettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLDisplaySettingsViewController.m; sourceTree = ""; }; 261A6289176C159000059090 /* AccountsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccountsViewController.h; sourceTree = ""; }; 261A628A176C159000059090 /* AccountsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AccountsViewController.m; sourceTree = ""; }; 261A628C176C15AD00059090 /* ChatLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChatLogsViewController.h; sourceTree = ""; }; @@ -808,16 +813,10 @@ 26FE3BCA1C61A6C3003CC230 /* MLResizingTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLResizingTextView.m; sourceTree = ""; }; 288765A40DF7441C002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 2B13C0878F8FA2F0DABB9F0A /* Pods-monalxmpp.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmpp.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-monalxmpp/Pods-monalxmpp.appstore.xcconfig"; sourceTree = ""; }; - 31A80863B17A9EB9D4C08364 /* Pods-jrtplib-static.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-jrtplib-static/Pods-jrtplib-static.adhoc.xcconfig"; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* SworIM_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SworIM_Prefix.pch; sourceTree = ""; }; 3872091F251EDE07001837EB /* MLXEPSlashMeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLXEPSlashMeHandler.h; sourceTree = ""; }; 38720921251EDE07001837EB /* MLXEPSlashMeHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLXEPSlashMeHandler.m; sourceTree = ""; }; - 3C40CE7301D792181A28A1F8 /* Pods_Monal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Monal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3EDC0EB6935929A159956EB5 /* Pods-Monal-OSXTests.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal-OSXTests.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-Monal-OSXTests/Pods-Monal-OSXTests.adhoc.xcconfig"; sourceTree = ""; }; - 428F57D57919ACB0B97869A6 /* Pods-jrtplib-static-OSX.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static-OSX.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-jrtplib-static-OSX/Pods-jrtplib-static-OSX.adhoc.xcconfig"; sourceTree = ""; }; - 4425FD9C1F3AFFA64619FF67 /* Pods-jrtplib-static-OSX.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static-OSX.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-jrtplib-static-OSX/Pods-jrtplib-static-OSX.appstore.xcconfig"; sourceTree = ""; }; - 4C28AE0807330E8B6F972591 /* Pods_NotificaionService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificaionService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A614910EEF29D66DD4B37E3 /* Pods-NotificaionService.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificaionService.adhoc.xcconfig"; path = "Target Support Files/Pods-NotificaionService/Pods-NotificaionService.adhoc.xcconfig"; sourceTree = ""; }; 540A3FAC24D674BD0008965D /* MLSQLite.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLSQLite.m; sourceTree = ""; }; 540BD0D124D8D1F20087A743 /* IPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IPC.h; sourceTree = ""; }; 540BD0D324D8D1FF0087A743 /* IPC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IPC.m; sourceTree = ""; }; @@ -834,38 +833,31 @@ 541E4CC6254D370100FD7B28 /* MLPubSubProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLPubSubProcessor.m; sourceTree = ""; }; 544656BA2534910D006B2953 /* XMPPDataForm.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XMPPDataForm.m; sourceTree = ""; }; 544656BC25349133006B2953 /* XMPPDataForm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XMPPDataForm.h; sourceTree = ""; }; + 54507CE4255D8C14007092F4 /* MLFiletransfer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLFiletransfer.m; sourceTree = ""; }; + 54507CE7255D8C47007092F4 /* MLFiletransfer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLFiletransfer.h; sourceTree = ""; }; 5455BC3024EB776B0024D80F /* MLUDPLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLUDPLogger.m; sourceTree = ""; }; 5455BC3224EB776E0024D80F /* MLUDPLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLUDPLogger.h; sourceTree = ""; }; 54E594BA2523C34900E4172B /* MLPubSub.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLPubSub.h; sourceTree = ""; }; 54E594BC2523C34A00E4172B /* MLPubSub.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLPubSub.m; sourceTree = ""; }; - 5ED93030541F175E7BEB1FC2 /* Pods-NotificaionService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificaionService.debug.xcconfig"; path = "Pods/Target Support Files/Pods-NotificaionService/Pods-NotificaionService.debug.xcconfig"; sourceTree = ""; }; - 5F59B43550E822D135D1127E /* Pods_Monal_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Monal_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6253B2B3FE987619C2714CF5 /* Pods-Monal Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Monal Tests/Pods-Monal Tests.debug.xcconfig"; sourceTree = ""; }; - 67334C3FACBBF127DFE076DF /* Pods-jrtplib-static.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static.debug.xcconfig"; path = "Pods/Target Support Files/Pods-jrtplib-static/Pods-jrtplib-static.debug.xcconfig"; sourceTree = ""; }; - 67BD5F27C043F196A88D9241 /* Pods-monalxmpp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmpp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-monalxmpp/Pods-monalxmpp.debug.xcconfig"; sourceTree = ""; }; - 6D9328A93187B01B20E88F29 /* Pods-Monal.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Monal/Pods-Monal.debug.xcconfig"; sourceTree = ""; }; - 861E9C836D3317B11641B333 /* Pods-jrtplib-static.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-jrtplib-static/Pods-jrtplib-static.appstore.xcconfig"; sourceTree = ""; }; - 8A54C0030602B177997D366B /* Pods-Monal-OSXTests.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal-OSXTests.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-Monal-OSXTests/Pods-Monal-OSXTests.appstore.xcconfig"; sourceTree = ""; }; - 8A76EEB1785A33983DFDC52B /* Pods-monalxmppmac.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmppmac.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-monalxmppmac/Pods-monalxmppmac.appstore.xcconfig"; sourceTree = ""; }; + 6142E73A9912F6E327A8CD14 /* Pods-Monal Tests.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal Tests.adhoc.xcconfig"; path = "Target Support Files/Pods-Monal Tests/Pods-Monal Tests.adhoc.xcconfig"; sourceTree = ""; }; + 6A6D58F695CDFAF204F3B3EB /* Pods-Monal Tests.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal Tests.appstore.xcconfig"; path = "Target Support Files/Pods-Monal Tests/Pods-Monal Tests.appstore.xcconfig"; sourceTree = ""; }; + 6D6C5046292E68BB30C97B38 /* Pods_Monal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Monal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E865A50FFD3C29AD8F3DFD0 /* Pods_NotificaionService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificaionService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7FA9582E4CC566FE5466C557 /* Pods-Monal.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal.debug.xcconfig"; path = "Target Support Files/Pods-Monal/Pods-Monal.debug.xcconfig"; sourceTree = ""; }; + 86CF04FDE084C646CA14B774 /* Pods-Monal Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal Tests.debug.xcconfig"; path = "Target Support Files/Pods-Monal Tests/Pods-Monal Tests.debug.xcconfig"; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Monal-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Monal-Info.plist"; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; - 99727E17D81471E803966620 /* Pods_shareSheet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_shareSheet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9D4B7A0FB4CCDAAA5EEC8690 /* Pods-jrtplib-static-OSX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static-OSX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-jrtplib-static-OSX/Pods-jrtplib-static-OSX.debug.xcconfig"; sourceTree = ""; }; - A0FA7061BCF979AE4AE0E558 /* Pods-NotificaionService.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificaionService.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-NotificaionService/Pods-NotificaionService.appstore.xcconfig"; sourceTree = ""; }; - A1F5E44F27B64591044FB28E /* Pods-Monal-OSX.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal-OSX.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-Monal-OSX/Pods-Monal-OSX.adhoc.xcconfig"; sourceTree = ""; }; - A8C31DD80551F41EE933896F /* Pods-monalxmpp.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmpp.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-monalxmpp/Pods-monalxmpp.adhoc.xcconfig"; sourceTree = ""; }; - AC355146D687D83135E2BBF7 /* Pods_monalxmpp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_monalxmpp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - ADFC4F2D8AFC07D6C84B0961 /* Pods-Monal.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-Monal/Pods-Monal.appstore.xcconfig"; sourceTree = ""; }; - AEE82B918E1D2978170F5151 /* Pods-Monal-OSXTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal-OSXTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Monal-OSXTests/Pods-Monal-OSXTests.debug.xcconfig"; sourceTree = ""; }; - B14247E40468E759E0E3A5C6 /* Pods-shareSheet.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shareSheet.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-shareSheet/Pods-shareSheet.appstore.xcconfig"; sourceTree = ""; }; - B15AF16CBD0F4AC6113EB3BA /* Pods-shareSheet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shareSheet.debug.xcconfig"; path = "Pods/Target Support Files/Pods-shareSheet/Pods-shareSheet.debug.xcconfig"; sourceTree = ""; }; - B7CF94A039065B2420A65087 /* Pods-NotificaionService.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificaionService.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-NotificaionService/Pods-NotificaionService.adhoc.xcconfig"; sourceTree = ""; }; - B93583E9A8D07409E6847BCE /* Pods-Monal-OSX.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal-OSX.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Monal-OSX/Pods-Monal-OSX.debug.xcconfig"; sourceTree = ""; }; + 9FD41A731431B8DC2D41B808 /* Pods_jrtplib_static.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_jrtplib_static.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AAFC73E987D41BA8D91E9F95 /* Pods-monalxmpp.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmpp.appstore.xcconfig"; path = "Target Support Files/Pods-monalxmpp/Pods-monalxmpp.appstore.xcconfig"; sourceTree = ""; }; + AD0E234056402EE91A36D628 /* Pods-Monal.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal.adhoc.xcconfig"; path = "Target Support Files/Pods-Monal/Pods-Monal.adhoc.xcconfig"; sourceTree = ""; }; + B4C8C4A8BA0B48E5DA219993 /* Pods-jrtplib-static.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static.appstore.xcconfig"; path = "Target Support Files/Pods-jrtplib-static/Pods-jrtplib-static.appstore.xcconfig"; sourceTree = ""; }; + B55DCA2ABBDB6E635D46D69A /* Pods-monalxmpp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmpp.debug.xcconfig"; path = "Target Support Files/Pods-monalxmpp/Pods-monalxmpp.debug.xcconfig"; sourceTree = ""; }; C12436122434AB5D00B8F074 /* MLAttributedLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLAttributedLabel.h; sourceTree = ""; }; C12436132434AB5D00B8F074 /* MLAttributedLabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLAttributedLabel.m; sourceTree = ""; }; C13EBB8C24DC685C008AADDA /* MLPrivacySettingsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLPrivacySettingsViewController.h; sourceTree = ""; }; C13EBB8D24DC685C008AADDA /* MLPrivacySettingsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLPrivacySettingsViewController.m; sourceTree = ""; }; C1414E9B24312F0100948788 /* MLChatMapsCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MLChatMapsCell.h; sourceTree = ""; }; C1414E9C24312F0100948788 /* MLChatMapsCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MLChatMapsCell.m; sourceTree = ""; }; + C15489B825680BBE00BBA2F0 /* MLQRCodeScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MLQRCodeScanner.swift; sourceTree = ""; }; C15D504824C727CC002F75BB /* MLLogFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLLogFileManager.h; sourceTree = ""; }; C15D504A24C727CC002F75BB /* MLLogFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLLogFileManager.m; sourceTree = ""; }; C1613B572520723C0062C0C2 /* MLBasePaser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLBasePaser.h; sourceTree = ""; }; @@ -938,7 +930,6 @@ C1C839C824F1255600BBCF17 /* zh-Hant-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-HK"; path = "localization/zh-Hant-HK.lproj/iosShare.strings"; sourceTree = ""; }; C1C839DA24F15DF800BBCF17 /* MLOMEMO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLOMEMO.h; sourceTree = ""; }; C1C839DC24F15DF800BBCF17 /* MLOMEMO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLOMEMO.m; sourceTree = ""; }; - C1D27E0D3E8B8D0D625C5D2A /* Pods-monalxmppmac.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmppmac.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-monalxmppmac/Pods-monalxmppmac.adhoc.xcconfig"; sourceTree = ""; }; C1E4654724EE517000CA5AAF /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = localization/de.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; C1E4654924EE51EC00CA5AAF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = localization/Base.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; C1E4654A24EE520D00CA5AAF /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = localization/fr.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; @@ -964,15 +955,11 @@ C1E4655E24EE520F00CA5AAF /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = localization/cs.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; C1E4655F24EE520F00CA5AAF /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = localization/el.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; C1E4656024EE520F00CA5AAF /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = localization/fa.lproj/Localizable.strings; sourceTree = SOURCE_ROOT; }; - C8C2F3F80367A8A1D3C24092 /* Pods-Monal Tests.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal Tests.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-Monal Tests/Pods-Monal Tests.adhoc.xcconfig"; sourceTree = ""; }; - DFF6FBF8AB797FDAA75C0B92 /* Pods-monalxmppmac.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmppmac.debug.xcconfig"; path = "Pods/Target Support Files/Pods-monalxmppmac/Pods-monalxmppmac.debug.xcconfig"; sourceTree = ""; }; - E141DB69283EC3EA1A55B30F /* libPods-Monal-OSXTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Monal-OSXTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D310A8387B2EB10761312F77 /* Pods-NotificaionService.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificaionService.appstore.xcconfig"; path = "Target Support Files/Pods-NotificaionService/Pods-NotificaionService.appstore.xcconfig"; sourceTree = ""; }; + E06DB4446BAE2F9C0192D055 /* Pods-monalxmpp.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-monalxmpp.adhoc.xcconfig"; path = "Target Support Files/Pods-monalxmpp/Pods-monalxmpp.adhoc.xcconfig"; sourceTree = ""; }; E8DED05F25388BE8003167FF /* MLSearchViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MLSearchViewController.h; sourceTree = ""; }; E8DED06125388BE8003167FF /* MLSearchViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MLSearchViewController.m; sourceTree = ""; }; - EB7F14851D3C002B88ABBBF2 /* Pods-Monal Tests.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal Tests.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-Monal Tests/Pods-Monal Tests.appstore.xcconfig"; sourceTree = ""; }; - F5BECCAF4500573C1631F09F /* Pods-shareSheet.adhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shareSheet.adhoc.xcconfig"; path = "Pods/Target Support Files/Pods-shareSheet/Pods-shareSheet.adhoc.xcconfig"; sourceTree = ""; }; - FEB13B684255D50BCF0CE6C6 /* Pods-Monal-OSX.appstore.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Monal-OSX.appstore.xcconfig"; path = "Pods/Target Support Files/Pods-Monal-OSX/Pods-Monal-OSX.appstore.xcconfig"; sourceTree = ""; }; - FF9482F030E43311100BC741 /* Pods_jrtplib_static_OSX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_jrtplib_static_OSX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EF5FE59D4A5076BE0A48DDE4 /* Pods-jrtplib-static.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jrtplib-static.debug.xcconfig"; path = "Target Support Files/Pods-jrtplib-static/Pods-jrtplib-static.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -997,7 +984,7 @@ 263F568D121B5E970027D85D /* MediaPlayer.framework in Frameworks */, 26292F4F1309CAE100097280 /* CFNetwork.framework in Frameworks */, 26B69A75131C522F002FCBEB /* Security.framework in Frameworks */, - 3517ED822FCB1456A8D492B1 /* Pods_Monal.framework in Frameworks */, + 2BA392FE2644C7215E2A6EEE /* Pods_Monal.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1005,8 +992,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 54D2308224CB0F5400638D65 /* Pods_NotificaionService.framework in Frameworks */, 54D2307E24CB0F4600638D65 /* monalxmpp.framework in Frameworks */, + B2F4C53CD9A8A4046C237072 /* Pods_NotificaionService.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1015,7 +1002,7 @@ buildActionMask = 2147483647; files = ( 261E542823A0A4CF00394F59 /* monalxmpp.framework in Frameworks */, - 322AD0CC78815E4551793930 /* Pods_Monal_Tests.framework in Frameworks */, + 5E7A3AAABB0B465D0486DD14 /* Pods_Monal_Tests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1023,7 +1010,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1E779E002AE14108CB07C10A /* Pods_jrtplib_static.framework in Frameworks */, + 6C0881EAB68D8D4760C8F4B8 /* Pods_jrtplib_static.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1032,7 +1019,7 @@ buildActionMask = 2147483647; files = ( 26CC57EB23A08F4400ABB92A /* monalxmpp.framework in Frameworks */, - FE9BEACD951D0F8D3EBE264D /* Pods_shareSheet.framework in Frameworks */, + BE24210F5A8DEA2D7DC913F1 /* Pods_shareSheet.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1041,8 +1028,8 @@ buildActionMask = 2147483647; files = ( 26CC57E623A08EAA00ABB92A /* libjrtplib-static.a in Frameworks */, - D838DFA541E4102BCA92A60E /* Pods_monalxmpp.framework in Frameworks */, 26E88CDA23F3C6B8001E9478 /* MLCrypto.framework in Frameworks */, + B6C2B2781C2EDD10E9341233 /* Pods_monalxmpp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1118,6 +1105,8 @@ 26611016238F08AC0030A4EE /* MLXMPPConnection.m */, 3872091F251EDE07001837EB /* MLXEPSlashMeHandler.h */, 38720921251EDE07001837EB /* MLXEPSlashMeHandler.m */, + 54507CE4255D8C14007092F4 /* MLFiletransfer.m */, + 54507CE7255D8C47007092F4 /* MLFiletransfer.h */, ); name = XMPP; sourceTree = ""; @@ -1240,8 +1229,6 @@ 2644D4981FF29E5600F46AB5 /* MLSettingsTableViewController.m */, 26C1EA691FF937320085547A /* MLNotificationSettingsViewController.h */, 26C1EA6A1FF937320085547A /* MLNotificationSettingsViewController.m */, - 261A6286176C157D00059090 /* MLDisplaySettingsViewController.h */, - 261A6287176C157D00059090 /* MLDisplaySettingsViewController.m */, 265BD14E21A2D15600A59251 /* MLBackgroundSettings.h */, 265BD14F21A2D15600A59251 /* MLBackgroundSettings.m */, 26B0CA8721AE2E3C0080B133 /* MLSoundsTableViewController.h */, @@ -1284,6 +1271,7 @@ 26715E5E17650AF900684F3D /* View Controllers */ = { isa = PBXGroup; children = ( + C15489B825680BBE00BBA2F0 /* MLQRCodeScanner.swift */, 26E8462A24EABAED00ECE419 /* Main.storyboard */, 26F396CE2196830E00EF5720 /* OnBoard */, 26DB52111777C83100B50353 /* Modal views */, @@ -1699,7 +1687,7 @@ 26CC579323A0867400ABB92A /* monalxmpp */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, - 8E499B2EE45BEF75226B5B32 /* Pods */, + 82B5509417C9F86AAC2B4FA1 /* Pods */, ); name = CustomTemplate; sourceTree = ""; @@ -1741,55 +1729,39 @@ 262FE9351D821CD000A70506 /* WebKit.framework */, 26B2A5001B730A0D00272E63 /* OSX */, 26B2A4FF1B730A0900272E63 /* iOS */, - E141DB69283EC3EA1A55B30F /* libPods-Monal-OSXTests.a */, - 3C40CE7301D792181A28A1F8 /* Pods_Monal.framework */, - 5F59B43550E822D135D1127E /* Pods_Monal_Tests.framework */, - 04A27909A4CEFD3AEB9E267A /* Pods_Monal_OSX.framework */, - 4C28AE0807330E8B6F972591 /* Pods_NotificaionService.framework */, - 25CAD84C2B49BA56A354B822 /* Pods_jrtplib_static.framework */, - FF9482F030E43311100BC741 /* Pods_jrtplib_static_OSX.framework */, - 99727E17D81471E803966620 /* Pods_shareSheet.framework */, - AC355146D687D83135E2BBF7 /* Pods_monalxmpp.framework */, - 10A52735AA22DE00F1CE98E6 /* Pods_monalxmppmac.framework */, + 6D6C5046292E68BB30C97B38 /* Pods_Monal.framework */, + 190BE683A0C65E5A839F52BD /* Pods_Monal_Tests.framework */, + 7E865A50FFD3C29AD8F3DFD0 /* Pods_NotificaionService.framework */, + 9FD41A731431B8DC2D41B808 /* Pods_jrtplib_static.framework */, + 1F17B168B23E046B18971F63 /* Pods_monalxmpp.framework */, + 12CB538E8FC0AAFD96856CDD /* Pods_shareSheet.framework */, ); name = Frameworks; sourceTree = ""; }; - 8E499B2EE45BEF75226B5B32 /* Pods */ = { + 82B5509417C9F86AAC2B4FA1 /* Pods */ = { isa = PBXGroup; children = ( - 6D9328A93187B01B20E88F29 /* Pods-Monal.debug.xcconfig */, - 1CB94D0245DE1717B2AA3064 /* Pods-Monal.adhoc.xcconfig */, - ADFC4F2D8AFC07D6C84B0961 /* Pods-Monal.appstore.xcconfig */, - 6253B2B3FE987619C2714CF5 /* Pods-Monal Tests.debug.xcconfig */, - C8C2F3F80367A8A1D3C24092 /* Pods-Monal Tests.adhoc.xcconfig */, - EB7F14851D3C002B88ABBBF2 /* Pods-Monal Tests.appstore.xcconfig */, - B93583E9A8D07409E6847BCE /* Pods-Monal-OSX.debug.xcconfig */, - A1F5E44F27B64591044FB28E /* Pods-Monal-OSX.adhoc.xcconfig */, - FEB13B684255D50BCF0CE6C6 /* Pods-Monal-OSX.appstore.xcconfig */, - AEE82B918E1D2978170F5151 /* Pods-Monal-OSXTests.debug.xcconfig */, - 3EDC0EB6935929A159956EB5 /* Pods-Monal-OSXTests.adhoc.xcconfig */, - 8A54C0030602B177997D366B /* Pods-Monal-OSXTests.appstore.xcconfig */, - 67334C3FACBBF127DFE076DF /* Pods-jrtplib-static.debug.xcconfig */, - 31A80863B17A9EB9D4C08364 /* Pods-jrtplib-static.adhoc.xcconfig */, - 861E9C836D3317B11641B333 /* Pods-jrtplib-static.appstore.xcconfig */, - 9D4B7A0FB4CCDAAA5EEC8690 /* Pods-jrtplib-static-OSX.debug.xcconfig */, - 428F57D57919ACB0B97869A6 /* Pods-jrtplib-static-OSX.adhoc.xcconfig */, - 4425FD9C1F3AFFA64619FF67 /* Pods-jrtplib-static-OSX.appstore.xcconfig */, - B15AF16CBD0F4AC6113EB3BA /* Pods-shareSheet.debug.xcconfig */, - F5BECCAF4500573C1631F09F /* Pods-shareSheet.adhoc.xcconfig */, - B14247E40468E759E0E3A5C6 /* Pods-shareSheet.appstore.xcconfig */, - 5ED93030541F175E7BEB1FC2 /* Pods-NotificaionService.debug.xcconfig */, - B7CF94A039065B2420A65087 /* Pods-NotificaionService.adhoc.xcconfig */, - A0FA7061BCF979AE4AE0E558 /* Pods-NotificaionService.appstore.xcconfig */, - 67BD5F27C043F196A88D9241 /* Pods-monalxmpp.debug.xcconfig */, - A8C31DD80551F41EE933896F /* Pods-monalxmpp.adhoc.xcconfig */, - 2B13C0878F8FA2F0DABB9F0A /* Pods-monalxmpp.appstore.xcconfig */, - DFF6FBF8AB797FDAA75C0B92 /* Pods-monalxmppmac.debug.xcconfig */, - C1D27E0D3E8B8D0D625C5D2A /* Pods-monalxmppmac.adhoc.xcconfig */, - 8A76EEB1785A33983DFDC52B /* Pods-monalxmppmac.appstore.xcconfig */, - ); - name = Pods; + 7FA9582E4CC566FE5466C557 /* Pods-Monal.debug.xcconfig */, + AD0E234056402EE91A36D628 /* Pods-Monal.adhoc.xcconfig */, + 1D46F251C198E3D8FA55692F /* Pods-Monal.appstore.xcconfig */, + 86CF04FDE084C646CA14B774 /* Pods-Monal Tests.debug.xcconfig */, + 6142E73A9912F6E327A8CD14 /* Pods-Monal Tests.adhoc.xcconfig */, + 6A6D58F695CDFAF204F3B3EB /* Pods-Monal Tests.appstore.xcconfig */, + 0D0362F6B26B9407BE313F36 /* Pods-NotificaionService.debug.xcconfig */, + 4A614910EEF29D66DD4B37E3 /* Pods-NotificaionService.adhoc.xcconfig */, + D310A8387B2EB10761312F77 /* Pods-NotificaionService.appstore.xcconfig */, + EF5FE59D4A5076BE0A48DDE4 /* Pods-jrtplib-static.debug.xcconfig */, + 127D3489728DDC3A97F4293A /* Pods-jrtplib-static.adhoc.xcconfig */, + B4C8C4A8BA0B48E5DA219993 /* Pods-jrtplib-static.appstore.xcconfig */, + B55DCA2ABBDB6E635D46D69A /* Pods-monalxmpp.debug.xcconfig */, + E06DB4446BAE2F9C0192D055 /* Pods-monalxmpp.adhoc.xcconfig */, + AAFC73E987D41BA8D91E9F95 /* Pods-monalxmpp.appstore.xcconfig */, + 21E99538324C14220843F325 /* Pods-shareSheet.debug.xcconfig */, + 0EC0A5305E72C0F7F9E1CDEF /* Pods-shareSheet.adhoc.xcconfig */, + 061EF1BEDEE7A71FDF9AB402 /* Pods-shareSheet.appstore.xcconfig */, + ); + path = Pods; sourceTree = ""; }; C1E4654424EE515200CA5AAF /* GeneralLocalization */ = { @@ -1868,6 +1840,7 @@ C18E757A245E8AE900AE8FB7 /* MLPipe.h in Headers */, 54179CC3251CBB2B008F398E /* XMPPStanza.h in Headers */, 544656BD25349133006B2953 /* XMPPDataForm.h in Headers */, + 54507CE8255D8C47007092F4 /* MLFiletransfer.h in Headers */, 540E13A224CDCE3B0038FDA0 /* MLProcessLock.h in Headers */, 541E4CBC254AA08700FD7B28 /* MLSQLite.h in Headers */, C1613B5A2520723D0062C0C2 /* MLBasePaser.h in Headers */, @@ -1889,7 +1862,7 @@ isa = PBXNativeTarget; buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "Monal" */; buildPhases = ( - 76717EE198DD2D4EE34FBBED /* [CP] Check Pods Manifest.lock */, + 0105BB524F492059A59FAB32 /* [CP] Check Pods Manifest.lock */, 2665C11B1C4ABF0B00CC9A04 /* Build number */, 1D60588D0D05DD3D006BFB54 /* Resources */, 1D60588E0D05DD3D006BFB54 /* Sources */, @@ -1897,9 +1870,9 @@ 2665C11C1C4ABF7100CC9A04 /* Remove Build number */, 2665C11D1C4ABFB500CC9A04 /* Tag Git */, 26AA70212146BBB900598605 /* Embed App Extensions */, - 1F803E9D8411BA33D0C0AE27 /* [CP] Embed Pods Frameworks */, 261E542723A0A1D300394F59 /* Embed Frameworks */, C1C839B024F11BC100BBCF17 /* ShellScript */, + B76834F7D41432AA1A41545E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1918,7 +1891,7 @@ isa = PBXNativeTarget; buildConfigurationList = 260773CC232FC4E800BFD50F /* Build configuration list for PBXNativeTarget "NotificaionService" */; buildPhases = ( - E5EBCF72C8A7028D3B4EB323 /* [CP] Check Pods Manifest.lock */, + E29F37602913131122522B1F /* [CP] Check Pods Manifest.lock */, 260773BC232FC4E800BFD50F /* Sources */, 260773BD232FC4E800BFD50F /* Frameworks */, 260773BE232FC4E800BFD50F /* Resources */, @@ -1937,11 +1910,11 @@ isa = PBXNativeTarget; buildConfigurationList = 2630FE3A1AD550C00079F5C0 /* Build configuration list for PBXNativeTarget "Monal Tests" */; buildPhases = ( - C1B7FF5BDDB9A46D5FEEC536 /* [CP] Check Pods Manifest.lock */, + 5293EE413420D7ADC477126B /* [CP] Check Pods Manifest.lock */, 2630FE2B1AD550C00079F5C0 /* Sources */, 2630FE2C1AD550C00079F5C0 /* Frameworks */, 2630FE2D1AD550C00079F5C0 /* Resources */, - 1C83178C1853CE497FA975DF /* [CP] Embed Pods Frameworks */, + 9D4415B2EB337F741D2667B9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1958,7 +1931,7 @@ isa = PBXNativeTarget; buildConfigurationList = 268BCD57140B25CC0025E4D1 /* Build configuration list for PBXNativeTarget "jrtplib-static" */; buildPhases = ( - 70921ECBDF172CEC71CF4732 /* [CP] Check Pods Manifest.lock */, + A2FEE42268434719904C5019 /* [CP] Check Pods Manifest.lock */, 268BCD52140B25CC0025E4D1 /* Sources */, 268BCD53140B25CC0025E4D1 /* Frameworks */, 268BCD54140B25CC0025E4D1 /* Headers */, @@ -1976,7 +1949,7 @@ isa = PBXNativeTarget; buildConfigurationList = 26AA70202146BBB900598605 /* Build configuration list for PBXNativeTarget "shareSheet" */; buildPhases = ( - 24612D6D814D34634030DF43 /* [CP] Check Pods Manifest.lock */, + AEFD0BBC538F58642A554A94 /* [CP] Check Pods Manifest.lock */, 26AA700D2146BBB800598605 /* Sources */, 26AA700E2146BBB800598605 /* Frameworks */, 26AA700F2146BBB800598605 /* Resources */, @@ -1995,7 +1968,7 @@ isa = PBXNativeTarget; buildConfigurationList = 26CC579F23A0867400ABB92A /* Build configuration list for PBXNativeTarget "monalxmpp" */; buildPhases = ( - 2C2F76B3D7BFA8599420658A /* [CP] Check Pods Manifest.lock */, + D180E765B155419143F176E9 /* [CP] Check Pods Manifest.lock */, 26CC578D23A0867400ABB92A /* Headers */, 26CC578E23A0867400ABB92A /* Sources */, 26CC578F23A0867400ABB92A /* Frameworks */, @@ -2018,13 +1991,16 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; CLASSPREFIX = ML; DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1160; + LastUpgradeCheck = 1220; ORGANIZATIONNAME = Monal.im; TargetAttributes = { 1D6058900D05DD3D006BFB54 = { - LastSwiftMigration = 1150; + DevelopmentTeam = 33XS7DE5NZ; + LastSwiftMigration = 1220; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -2045,6 +2021,8 @@ }; 260773BF232FC4E800BFD50F = { CreatedOnToolsVersion = 11.0; + DevelopmentTeam = 33XS7DE5NZ; + ProvisioningStyle = Manual; }; 2630FE2E1AD550C00079F5C0 = { CreatedOnToolsVersion = 6.2; @@ -2196,52 +2174,22 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1C83178C1853CE497FA975DF /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Monal Tests/Pods-Monal Tests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Monal Tests/Pods-Monal Tests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Monal Tests/Pods-Monal Tests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 1F803E9D8411BA33D0C0AE27 /* [CP] Embed Pods Frameworks */ = { + 0105BB524F492059A59FAB32 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Monal/Pods-Monal-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Monal/Pods-Monal-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Monal/Pods-Monal-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 24612D6D814D34634030DF43 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-shareSheet-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Monal-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2290,34 +2238,68 @@ shellPath = /bin/sh; shellScript = "if [ \"${CONFIGURATION}\" == \"AppStore\" ]; then\noldBuildNumber=$(git tag --sort=\"v:refname\" |grep \"Build_iOS\" | tail -n1 | sed 's/Build_iOS_//g')\nbuildNumber=$(expr $oldBuildNumber + 1)\ngit tag Build_iOS_$buildNumber\ngit push origin Build_iOS_$buildNumber\nfi\n"; }; - 2C2F76B3D7BFA8599420658A /* [CP] Check Pods Manifest.lock */ = { + 5293EE413420D7ADC477126B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-monalxmpp-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Monal Tests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 70921ECBDF172CEC71CF4732 /* [CP] Check Pods Manifest.lock */ = { + 9D4415B2EB337F741D2667B9 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Monal Tests/Pods-Monal Tests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", + "${BUILT_PRODUCTS_DIR}/SignalProtocolC/SignalProtocolC.framework", + "${BUILT_PRODUCTS_DIR}/SignalProtocolObjC/SignalProtocolObjC.framework", + "${BUILT_PRODUCTS_DIR}/SAMKeychain/SAMKeychain.framework", + "${BUILT_PRODUCTS_DIR}/TPCircularBuffer/TPCircularBuffer.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SignalProtocolC.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SignalProtocolObjC.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SAMKeychain.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TPCircularBuffer.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Monal Tests/Pods-Monal Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A2FEE42268434719904C5019 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-jrtplib-static-checkManifestLockResult.txt", ); @@ -2326,40 +2308,76 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 76717EE198DD2D4EE34FBBED /* [CP] Check Pods Manifest.lock */ = { + AEFD0BBC538F58642A554A94 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Monal-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-shareSheet-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - C1B7FF5BDDB9A46D5FEEC536 /* [CP] Check Pods Manifest.lock */ = { + B76834F7D41432AA1A41545E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "${PODS_ROOT}/Target Support Files/Pods-Monal/Pods-Monal-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/DACircularProgress/DACircularProgress.framework", + "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", + "${BUILT_PRODUCTS_DIR}/EAIntroView/EAIntroView.framework", + "${BUILT_PRODUCTS_DIR}/EARestrictedScrollView/EARestrictedScrollView.framework", + "${BUILT_PRODUCTS_DIR}/IDMPhotoBrowser/IDMPhotoBrowser.framework", + "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", + "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", + "${BUILT_PRODUCTS_DIR}/TOCropViewController/TOCropViewController.framework", + "${BUILT_PRODUCTS_DIR}/pop/pop.framework", + "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", + "${BUILT_PRODUCTS_DIR}/SignalProtocolC/SignalProtocolC.framework", + "${BUILT_PRODUCTS_DIR}/SignalProtocolObjC/SignalProtocolObjC.framework", + "${BUILT_PRODUCTS_DIR}/SAMKeychain/SAMKeychain.framework", + "${BUILT_PRODUCTS_DIR}/TPCircularBuffer/TPCircularBuffer.framework", ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Monal Tests-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DACircularProgress.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EAIntroView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EARestrictedScrollView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IDMPhotoBrowser.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TOCropViewController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SignalProtocolC.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SignalProtocolObjC.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SAMKeychain.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TPCircularBuffer.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Monal/Pods-Monal-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; C1C839B024F11BC100BBCF17 /* ShellScript */ = { @@ -2379,16 +2397,42 @@ shellPath = /bin/sh; shellScript = "bash ../scripts/updateLocalization.sh\n"; }; - E5EBCF72C8A7028D3B4EB323 /* [CP] Check Pods Manifest.lock */ = { + D180E765B155419143F176E9 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-monalxmpp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E29F37602913131122522B1F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-NotificaionService-checkManifestLockResult.txt", ); @@ -2420,8 +2464,8 @@ 261A6285176C156500059090 /* ActiveChatsViewController.m in Sources */, 2602A70922A5431B006D3E72 /* MLRegSuccessViewController.m in Sources */, 26F396DC2197507200EF5720 /* GLTintButton.m in Sources */, + C15489B925680BBE00BBA2F0 /* MLQRCodeScanner.swift in Sources */, 2609B5291FD5B26800F09FA1 /* MLSplitViewDelegate.m in Sources */, - 261A6288176C157D00059090 /* MLDisplaySettingsViewController.m in Sources */, 2673CAC021D9242E00256507 /* MLKeysTableViewController.m in Sources */, 261A628B176C159000059090 /* AccountsViewController.m in Sources */, 261A628E176C15AD00059090 /* ChatLogsViewController.m in Sources */, @@ -2574,6 +2618,7 @@ 26CC57B423A086CC00ABB92A /* XMPPPresence.m in Sources */, 541E4CC0254AA0E700FD7B28 /* MLHandler.m in Sources */, 540E13A724CF78900038FDA0 /* MLLogFormatter.m in Sources */, + 54507CE5255D8C14007092F4 /* MLFiletransfer.m in Sources */, 26CC57C923A0892800ABB92A /* MLMessageProcessor.m in Sources */, 26CC57D623A08D7100ABB92A /* MLMetaInfo.m in Sources */, C18E757C245E8AE900AE8FB7 /* MLPipe.m in Sources */, @@ -2875,7 +2920,7 @@ /* Begin XCBuildConfiguration section */ 1D6058940D05DD3E006BFB54 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6D9328A93187B01B20E88F29 /* Pods-Monal.debug.xcconfig */; + baseConfigurationReference = 7FA9582E4CC566FE5466C557 /* Pods-Monal.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -2889,6 +2934,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; + DEFINES_MODULE = NO; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; ENABLE_BITCODE = YES; @@ -2906,7 +2952,9 @@ "$(inherited)", "DEBUG=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_PEDANTIC = NO; INFOPLIST_FILE = "Monal-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2924,16 +2972,19 @@ "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SUPPORTS_MACCATALYST = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Classes/Monal-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "Monal-Swift.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; }; name = Debug; }; 260773C9232FC4E800BFD50F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5ED93030541F175E7BEB1FC2 /* Pods-NotificaionService.debug.xcconfig */; + baseConfigurationReference = 0D0362F6B26B9407BE313F36 /* Pods-NotificaionService.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -2952,6 +3003,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = dwarf; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; @@ -2964,7 +3016,9 @@ "$(inherited)", "TARGET_IS_EXTENSION=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = NotificaionService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.3; @@ -2981,6 +3035,7 @@ PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "Monal iOS Notification filtering dev"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2989,7 +3044,7 @@ }; 260773CA232FC4E800BFD50F /* Adhoc */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B7CF94A039065B2420A65087 /* Pods-NotificaionService.adhoc.xcconfig */; + baseConfigurationReference = 4A614910EEF29D66DD4B37E3 /* Pods-NotificaionService.adhoc.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -3026,6 +3081,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; @@ -3039,8 +3095,10 @@ "COCOAPODS=1", "TARGET_IS_EXTENSION=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; @@ -3060,6 +3118,7 @@ PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "Monal iOS Notification filtering"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; @@ -3070,7 +3129,7 @@ }; 260773CB232FC4E800BFD50F /* AppStore */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A0FA7061BCF979AE4AE0E558 /* Pods-NotificaionService.appstore.xcconfig */; + baseConfigurationReference = D310A8387B2EB10761312F77 /* Pods-NotificaionService.appstore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -3107,6 +3166,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; @@ -3120,8 +3180,10 @@ "COCOAPODS=1", "TARGET_IS_EXTENSION=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; @@ -3141,6 +3203,7 @@ PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "Monal iOS Notification filtering dev"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; @@ -3151,8 +3214,9 @@ }; 2630FE371AD550C00079F5C0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6253B2B3FE987619C2714CF5 /* Pods-Monal Tests.debug.xcconfig */; + baseConfigurationReference = 86CF04FDE084C646CA14B774 /* Pods-Monal Tests.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -3177,7 +3241,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "Monal Tests/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3194,8 +3258,9 @@ }; 2630FE381AD550C00079F5C0 /* Adhoc */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C8C2F3F80367A8A1D3C24092 /* Pods-Monal Tests.adhoc.xcconfig */; + baseConfigurationReference = 6142E73A9912F6E327A8CD14 /* Pods-Monal Tests.adhoc.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -3228,7 +3293,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "Monal Tests/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3246,8 +3311,9 @@ }; 2630FE391AD550C00079F5C0 /* AppStore */ = { isa = XCBuildConfiguration; - baseConfigurationReference = EB7F14851D3C002B88ABBBF2 /* Pods-Monal Tests.appstore.xcconfig */; + baseConfigurationReference = 6A6D58F695CDFAF204F3B3EB /* Pods-Monal Tests.appstore.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -3280,7 +3346,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "Monal Tests/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3313,6 +3379,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -3343,7 +3410,7 @@ }; 2675EF5A18B98C2D0059C5C3 /* AppStore */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ADFC4F2D8AFC07D6C84B0961 /* Pods-Monal.appstore.xcconfig */; + baseConfigurationReference = 1D46F251C198E3D8FA55692F /* Pods-Monal.appstore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -3357,6 +3424,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = YES; DEAD_CODE_STRIPPING = YES; + DEFINES_MODULE = NO; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; ENABLE_BITCODE = YES; @@ -3369,7 +3437,9 @@ GCC_INPUT_FILETYPE = automatic; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = SworIM_Prefix.pch; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_PEDANTIC = NO; INFOPLIST_FILE = "Monal-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3387,15 +3457,18 @@ "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SUPPORTS_MACCATALYST = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Classes/Monal-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "Monal-Swift.h"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; }; name = AppStore; }; 2675EF5B18B98C2D0059C5C3 /* AppStore */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 861E9C836D3317B11641B333 /* Pods-jrtplib-static.appstore.xcconfig */; + baseConfigurationReference = B4C8C4A8BA0B48E5DA219993 /* Pods-jrtplib-static.appstore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CODE_SIGN_IDENTITY = "iPhone Distribution: Anurodh Pokharel (33XS7DE5NZ)"; @@ -3427,7 +3500,7 @@ }; 268BCD58140B25CC0025E4D1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 67334C3FACBBF127DFE076DF /* Pods-jrtplib-static.debug.xcconfig */; + baseConfigurationReference = EF5FE59D4A5076BE0A48DDE4 /* Pods-jrtplib-static.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; COPY_PHASE_STRIP = NO; @@ -3460,7 +3533,7 @@ }; 26AA701D2146BBB900598605 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B15AF16CBD0F4AC6113EB3BA /* Pods-shareSheet.debug.xcconfig */; + baseConfigurationReference = 21E99538324C14220843F325 /* Pods-shareSheet.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -3478,6 +3551,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = dwarf; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; @@ -3489,7 +3563,9 @@ "DEBUG=1", "$(inherited)", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/shareSheet-iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; @@ -3504,6 +3580,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3512,7 +3589,7 @@ }; 26AA701E2146BBB900598605 /* Adhoc */ = { isa = XCBuildConfiguration; - baseConfigurationReference = F5BECCAF4500573C1631F09F /* Pods-shareSheet.adhoc.xcconfig */; + baseConfigurationReference = 0EC0A5305E72C0F7F9E1CDEF /* Pods-shareSheet.adhoc.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -3548,6 +3625,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; @@ -3556,8 +3634,10 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; @@ -3575,6 +3655,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; @@ -3585,7 +3666,7 @@ }; 26AA701F2146BBB900598605 /* AppStore */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B14247E40468E759E0E3A5C6 /* Pods-shareSheet.appstore.xcconfig */; + baseConfigurationReference = 061EF1BEDEE7A71FDF9AB402 /* Pods-shareSheet.appstore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -3621,6 +3702,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; @@ -3629,8 +3711,10 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; @@ -3648,6 +3732,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; @@ -3658,9 +3743,10 @@ }; 26CC579C23A0867400ABB92A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 67BD5F27C043F196A88D9241 /* Pods-monalxmpp.debug.xcconfig */; + baseConfigurationReference = B55DCA2ABBDB6E635D46D69A /* Pods-monalxmpp.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; @@ -3671,11 +3757,11 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; @@ -3692,7 +3778,9 @@ "$(inherited)", "TARGET_OS_IPHONE=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = monalxmpp/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3709,6 +3797,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3719,9 +3808,10 @@ }; 26CC579D23A0867400ABB92A /* Adhoc */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A8C31DD80551F41EE933896F /* Pods-monalxmpp.adhoc.xcconfig */; + baseConfigurationReference = E06DB4446BAE2F9C0192D055 /* Pods-monalxmpp.adhoc.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; @@ -3750,11 +3840,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; @@ -3772,8 +3862,10 @@ "COCOAPODS=1", "TARGET_OS_IPHONE=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; @@ -3793,6 +3885,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; @@ -3805,9 +3898,10 @@ }; 26CC579E23A0867400ABB92A /* AppStore */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2B13C0878F8FA2F0DABB9F0A /* Pods-monalxmpp.appstore.xcconfig */; + baseConfigurationReference = AAFC73E987D41BA8D91E9F95 /* Pods-monalxmpp.appstore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; @@ -3836,11 +3930,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; @@ -3858,8 +3952,10 @@ "COCOAPODS=1", "TARGET_OS_IPHONE=1", ); + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_PEDANTIC = NO; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; @@ -3879,6 +3975,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; @@ -3906,6 +4003,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -3936,7 +4034,7 @@ }; 26E74FBE17B06D2200FD91AE /* Adhoc */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1CB94D0245DE1717B2AA3064 /* Pods-Monal.adhoc.xcconfig */; + baseConfigurationReference = AD0E234056402EE91A36D628 /* Pods-Monal.adhoc.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -3950,6 +4048,7 @@ CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = YES; DEAD_CODE_STRIPPING = YES; + DEFINES_MODULE = NO; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = 33XS7DE5NZ; ENABLE_BITCODE = YES; @@ -3962,7 +4061,9 @@ GCC_INPUT_FILETYPE = automatic; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = SworIM_Prefix.pch; + GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + GCC_WARN_PEDANTIC = NO; INFOPLIST_FILE = "Monal-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3980,15 +4081,18 @@ "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + RUN_CLANG_STATIC_ANALYZER = YES; SUPPORTS_MACCATALYST = YES; - SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Classes/Monal-Bridging-Header.h"; + SWIFT_OBJC_INTERFACE_HEADER_NAME = "Monal-Swift.h"; + SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_VERSION = 5.0; }; name = Adhoc; }; 26E74FBF17B06D2200FD91AE /* Adhoc */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 31A80863B17A9EB9D4C08364 /* Pods-jrtplib-static.adhoc.xcconfig */; + baseConfigurationReference = 127D3489728DDC3A97F4293A /* Pods-jrtplib-static.adhoc.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CODE_SIGN_IDENTITY = "iPhone Distribution: Anurodh Pokharel (33XS7DE5NZ)"; @@ -4035,6 +4139,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; diff --git a/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Tests.xcscheme b/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Tests.xcscheme index 418b1d94b4..0ae95ed71c 100644 --- a/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Tests.xcscheme +++ b/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal Tests.xcscheme @@ -1,6 +1,6 @@ + + + + diff --git a/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal.xcscheme b/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal.xcscheme index 8f7718b83b..c7757dd207 100644 --- a/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal.xcscheme +++ b/Monal/Monal.xcodeproj/xcshareddata/xcschemes/Monal.xcscheme @@ -1,6 +1,6 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) NSExtension NSExtensionPointIdentifier diff --git a/Monal/NotificaionService/NotificationService.m b/Monal/NotificaionService/NotificationService.m index bc39267ce6..3113394dac 100644 --- a/Monal/NotificaionService/NotificationService.m +++ b/Monal/NotificaionService/NotificationService.m @@ -13,6 +13,7 @@ #import "MLProcessLock.h" #import "MLXMPPManager.h" #import "MLNotificationManager.h" +#import "xmpp.h" @interface Push : NSObject @property (atomic, strong) NSMutableArray* handlerList; @@ -139,6 +140,7 @@ -(void) feedAllWaitingHandlers //repeated calls to this method will do nothing (every handler will already be used and every content will already be posted) @synchronized(self) { DDLogInfo(@"Disconnecting all accounts and feeding all pending handlers: %lu", [self.handlerList count]); + //this has to be synchronous because we only want to continue if all accounts are completely disconnected [[MLXMPPManager sharedInstance] disconnectAll]; @@ -180,7 +182,7 @@ -(void) listNotifications -(void) nowIdle:(NSNotification*) notification { - //this method will be called inside the receive queue or send queue and immediately disconnect the account + //this method will be called inside the receive queue and immediately disconnect the account //this is needed to not leak incoming stanzas while no instance of the NotificaionService class is active xmpp* xmppAccount = (xmpp*)notification.object; @@ -200,6 +202,10 @@ -(void) nowIdle:(NSNotification*) notification if([[MLXMPPManager sharedInstance] allAccountsIdle]) { DDLogInfo(@"notification handler: all accounts idle --> terminating extension"); + + //remove syncError notification because all accounts are idle and fully synced now + [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[@"syncError"]]; + [self feedAllWaitingHandlers]; } } @@ -207,32 +213,17 @@ -(void) nowIdle:(NSNotification*) notification -(void) xmppError:(NSNotification*) notification { DDLogInfo(@"notification handler: got xmpp error"); - //dispatch in another thread to avoid blocking the thread posting this notification (most probably the receiveQueue) - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - //display an error notification and disconnect this account, leaving the extension running until all accounts are idle - //(disconnected accounts count as idle) - DDLogInfo(@"notification handler: account error --> publishing this as error notification and disconnecting this account"); - //extract error contents and disconnect the account - NSArray* payload = [notification.object copy]; - NSString* message = payload[1]; - xmpp* xmppAccount = payload.firstObject; - DDLogVerbose(@"error(%@): %@", xmppAccount.connectionProperties.identity.jid, message); - //this will result in an idle notification for this account ultimately leading to the termination of this app extension - [xmppAccount disconnect]; - - //display error notification - NSString* idval = xmppAccount.connectionProperties.identity.jid; //use this to only show the newest error notification per account - UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; - content.title = xmppAccount.connectionProperties.identity.jid; - content.body = message; - content.sound = [UNNotificationSound defaultSound]; - UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; - UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:idval content:content trigger:nil]; - [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { - if(error) - DDLogError(@"Error posting xmppError notification: %@", error); - }]; - }); + if([notification.userInfo[@"isSevere"] boolValue]) + { + //dispatch in another thread to avoid blocking the thread posting this notification (most probably the receiveQueue) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + //disconnect this account and make sure the account is marked as idle afterwards + //(which will ultimately lead to the termination of this app extension) + DDLogWarn(@"notification handler: severe account error --> disconnecting this account"); + [notification.object disconnect]; + [self nowIdle:notification]; + }); + } } @end diff --git a/Monal/Podfile b/Monal/Podfile index 1c57ea6744..af39029de2 100644 --- a/Monal/Podfile +++ b/Monal/Podfile @@ -1,4 +1,5 @@ project 'Monal.xcodeproj' +source 'https://cdn.cocoapods.org/' # Uncomment the next line to define a global platform for your project platform :ios, '12.0' @@ -8,51 +9,42 @@ inhibit_all_warnings! target 'jrtplib-static' do # Uncomment the next line if you're using Swift or would like to use dynamic frameworks - use_frameworks! - - # Pods for jrtplib-static - + use_frameworks! end target 'shareSheet' do # Uncomment the next line if you're using Swift or would like to use dynamic frameworks - use_frameworks! - inhibit_all_warnings! - pod 'CocoaLumberjack' - + use_frameworks! + inhibit_all_warnings! + pod 'CocoaLumberjack' end target 'NotificaionService' do # Uncomment the next line if you're using Swift or would like to use dynamic frameworks - use_frameworks! - inhibit_all_warnings! - pod 'CocoaLumberjack' - pod 'SignalProtocolObjC' - pod 'SignalProtocolC' - + use_frameworks! + inhibit_all_warnings! + pod 'CocoaLumberjack' + pod 'SignalProtocolObjC' + pod 'SignalProtocolC' end target 'Monal' do # Uncomment the next line if you're using Swift or would like to use dynamic frameworks use_frameworks! inhibit_all_warnings! -pod 'MBProgressHUD', '~> 1.2.0' - -pod 'NotificationBannerSwift', '~> 3.0.2' -pod 'IDMPhotoBrowser' -pod 'SDWebImage', '~> 5.8.3' - -pod 'DZNEmptyDataSet' -pod 'EAIntroView' -pod 'TOCropViewController' - # Pods for Monal + pod 'MBProgressHUD', '~> 1.2.0' + pod 'IDMPhotoBrowser' + pod 'SDWebImage', '~> 5.9.5' + pod 'DZNEmptyDataSet' + pod 'EAIntroView' + pod 'TOCropViewController' + pod 'NotificationBannerSwift', '~> 3.0.2' target 'Monal Tests' do inherit! :search_paths # Pods for testing end - end target 'monalxmpp' do @@ -65,6 +57,47 @@ target 'monalxmpp' do pod 'SignalProtocolObjC' pod 'SignalProtocolC' - pod 'OpenSSL-Universal', '1.0.2.20' + pod 'OpenSSL-Universal', '1.0.2.20' +end + +# see https://stackoverflow.com/a/36547646/3528174 +post_install do |installer| + fix_deployment_target(installer) + installer.pods_project.targets.each do |target| + target.build_configurations.each do |configuration| + configuration.build_settings.delete('ARCHS') +# configuration.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' +# if target.name == "TPCircularBuffer" +# configuration.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES' +# end + end + end +end +# see https://github.com/CocoaPods/CocoaPods/issues/7314 +def fix_deployment_target(pod_installer) + if !pod_installer + return + end + puts "Make the pods deployment target version the same as our target" + + project = pod_installer.pods_project + deploymentMap = {} + project.build_configurations.each do |config| + deploymentMap[config.name] = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] + end + # p deploymentMap + + project.targets.each do |t| + puts " #{t.name}" + t.build_configurations.each do |config| + oldTarget = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] + newTarget = deploymentMap[config.name] + if oldTarget == newTarget + next + end + puts " #{config.name} deployment target: #{oldTarget} => #{newTarget}" + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = newTarget + end + end end diff --git a/Monal/Podfile.lock b/Monal/Podfile.lock index 809a8042f5..9f60e6c2d6 100644 --- a/Monal/Podfile.lock +++ b/Monal/Podfile.lock @@ -4,7 +4,7 @@ PODS: - CocoaLumberjack/Core (3.7.0) - DACircularProgress (2.3.1) - DZNEmptyDataSet (1.8.1) - - EAIntroView (2.12.0): + - EAIntroView (2.13.0): - EARestrictedScrollView (~> 1.1.0) - EARestrictedScrollView (1.1.0) - IDMPhotoBrowser (1.10.2): @@ -21,9 +21,9 @@ PODS: - OpenSSL-Universal/Static (1.0.2.20) - pop (1.0.12) - SAMKeychain (1.5.3) - - SDWebImage (5.8.4): - - SDWebImage/Core (= 5.8.4) - - SDWebImage/Core (5.8.4) + - SDWebImage (5.9.5): + - SDWebImage/Core (= 5.9.5) + - SDWebImage/Core (5.9.5) - SignalProtocolC (2.3.2) - SignalProtocolObjC (1.1.0): - SignalProtocolC (~> 2.3.2) @@ -40,7 +40,7 @@ DEPENDENCIES: - NotificationBannerSwift (~> 3.0.2) - OpenSSL-Universal (= 1.0.2.20) - SAMKeychain - - SDWebImage (~> 5.8.3) + - SDWebImage (~> 5.9.5) - SignalProtocolC - SignalProtocolObjC - TOCropViewController @@ -71,7 +71,7 @@ SPEC CHECKSUMS: CocoaLumberjack: e8955b9d337ac307103b0a34fd141c32f27e53c5 DACircularProgress: 4dd437c0fc3da5161cb289e07ac449493d41db71 DZNEmptyDataSet: 9525833b9e68ac21c30253e1d3d7076cc828eaa7 - EAIntroView: 249559079ddfdbe80e5bcb755c04c3b964b2ac27 + EAIntroView: 9adedfc6b5c3fd3ebe19766da01dae573e6ce053 EARestrictedScrollView: b0c2a3f92fb2610bb44d71c5e4893777c89e45ef IDMPhotoBrowser: 4bb459b1faffc228e92bccdf7c54ad6d6b7e8c37 MarqueeLabel: 00cc0bcd087111dca575878b3531af980559707d @@ -80,13 +80,13 @@ SPEC CHECKSUMS: OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd pop: d582054913807fd11fd50bfe6a539d91c7e1a55a SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SDWebImage: cf6922231e95550934da2ada0f20f2becf2ceba9 + SDWebImage: 0b2ba0d56479bf6a45ecddbfd5558bea93150d25 SignalProtocolC: 051512e9f3abfeb4120c5e9673af9ae0ee370070 SignalProtocolObjC: b992ed29c3b7b453510ff0102ed16ef143f7efd2 SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb TOCropViewController: da59f531f8ac8a94ef6d6c0fc34009350f9e8bfe TPCircularBuffer: c13243556527551c4d320709c7b14a6d20cdc30a -PODFILE CHECKSUM: 2bd9c81d0cb080794974dfbb334985bf61102788 +PODFILE CHECKSUM: 51c22442ce264e72ebbf3184800f617ddb0f7540 -COCOAPODS: 1.9.3 +COCOAPODS: 1.10.0 diff --git a/Monal/jrtplib/rtcpcompoundpacketbuilder.cpp b/Monal/jrtplib/rtcpcompoundpacketbuilder.cpp index 637a25cc90..36cf5a432a 100644 --- a/Monal/jrtplib/rtcpcompoundpacketbuilder.cpp +++ b/Monal/jrtplib/rtcpcompoundpacketbuilder.cpp @@ -334,10 +334,9 @@ int RTCPCompoundPacketBuilder::AddSDESNormalItem(RTCPSDESPacket::ItemType t,cons return ERR_RTP_OUTOFMEM; len = sizeof(RTCPSDESHeader)+(size_t)itemlength; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(buf); + ((RTCPSDESHeader*)buf)->sdesid = itemid; + ((RTCPSDESHeader*)buf)->length = itemlength; - sdeshdr->sdesid = itemid; - sdeshdr->length = itemlength; if (itemlength != 0) memcpy((buf + sizeof(RTCPSDESHeader)),itemdata,(size_t)itemlength); @@ -376,10 +375,8 @@ int RTCPCompoundPacketBuilder::AddSDESPrivateItem(const void *prefixdata,uint8_t return ERR_RTP_OUTOFMEM; len = sizeof(RTCPSDESHeader)+(size_t)itemlength; - RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(buf); - - sdeshdr->sdesid = RTCP_SDES_ID_PRIVATE; - sdeshdr->length = itemlength; + ((RTCPSDESHeader*)buf)->sdesid = RTCP_SDES_ID_PRIVATE; + ((RTCPSDESHeader*)buf)->length = itemlength; buf[sizeof(RTCPSDESHeader)] = prefixlength; if (prefixlength != 0) diff --git a/Monal/jrtplib/rtcpsdespacket.cpp b/Monal/jrtplib/rtcpsdespacket.cpp index a567130f58..eb0f45de26 100644 --- a/Monal/jrtplib/rtcpsdespacket.cpp +++ b/Monal/jrtplib/rtcpsdespacket.cpp @@ -81,8 +81,6 @@ RTCPSDESPacket::RTCPSDESPacket(uint8_t *data,size_t datalength) while ((ssrccount > 0) && (len > 0)) { - chunkoffset = 0; - if (len < (sizeof(uint32_t)*2)) // chunk must contain at least a SSRC identifier return; // and a (possibly empty) item diff --git a/Monal/jrtplib/rtpinternalsourcedata.cpp b/Monal/jrtplib/rtpinternalsourcedata.cpp index ea819bc368..c56b03fb46 100644 --- a/Monal/jrtplib/rtpinternalsourcedata.cpp +++ b/Monal/jrtplib/rtpinternalsourcedata.cpp @@ -209,20 +209,18 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid,const uint8_t *data,si break; case RTCP_SDES_ID_NAME: { - uint8_t *oldname; size_t oldlen; - oldname = SDESinf.GetName(&oldlen); + SDESinf.GetName(&oldlen); if (oldlen == 0) // Name not set return SDESinf.SetName(data,itemlen); } break; case RTCP_SDES_ID_EMAIL: { - uint8_t *oldemail; size_t oldlen; - oldemail = SDESinf.GetEMail(&oldlen); + SDESinf.GetEMail(&oldlen); if (oldlen == 0) return SDESinf.SetEMail(data,itemlen); } @@ -233,10 +231,9 @@ int RTPInternalSourceData::ProcessSDESItem(uint8_t sdesid,const uint8_t *data,si return SDESinf.SetLocation(data,itemlen); case RTCP_SDES_ID_TOOL: { - uint8_t *oldtool; size_t oldlen; - oldtool = SDESinf.GetTool(&oldlen); + SDESinf.GetTool(&oldlen); if (oldlen == 0) return SDESinf.SetTool(data,itemlen); } diff --git a/Monal/jrtplib/rtppacket.cpp b/Monal/jrtplib/rtppacket.cpp index 69b8d85490..08b37aaa7f 100644 --- a/Monal/jrtplib/rtppacket.cpp +++ b/Monal/jrtplib/rtppacket.cpp @@ -165,10 +165,9 @@ int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack) else { rtpextheader = 0; - exthdrlen = 0; } - payloadlength = packetlen-numpadbytes-payloadoffset; + payloadlength = (int)(packetlen-numpadbytes-payloadoffset); if (payloadlength < 0) return ERR_RTP_PACKET_INVALIDPACKET; diff --git a/Monal/jrtplib/rtpsession.cpp b/Monal/jrtplib/rtpsession.cpp index 9da2d50a23..74d0155d2b 100644 --- a/Monal/jrtplib/rtpsession.cpp +++ b/Monal/jrtplib/rtpsession.cpp @@ -752,7 +752,7 @@ int RTPSession::SendRTCPAPPPacket(uint8_t subtype, const uint8_t name[4], const sentpackets = true; PACKSENT_UNLOCK - return pb.GetCompoundPacketLength(); + return (int)pb.GetCompoundPacketLength(); } #endif // RTP_SUPPORT_SENDAPP diff --git a/Monal/jrtplib/rtpsources.cpp b/Monal/jrtplib/rtpsources.cpp index 3393046f7d..3f73dbc866 100644 --- a/Monal/jrtplib/rtpsources.cpp +++ b/Monal/jrtplib/rtpsources.cpp @@ -1082,10 +1082,9 @@ void RTPSources::NoteTimeout(const RTPTime &curtime,const RTPTime &timeoutdelay) while (sourcelist.HasCurrentElement()) { RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); - uint8_t *note; size_t notelen; - note = srcdat->SDES_GetNote(¬elen); + srcdat->SDES_GetNote(¬elen); if (notelen != 0) // Note has been set { RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); @@ -1149,7 +1148,6 @@ void RTPSources::MultipleTimeouts(const RTPTime &curtime,const RTPTime &senderti RTPInternalSourceData *srcdat = sourcelist.GetCurrentElement(); bool deleted,issender,isactive; bool byetimeout,normaltimeout,notetimeout; - uint8_t *note; size_t notelen; issender = srcdat->IsSender(); @@ -1159,7 +1157,7 @@ void RTPSources::MultipleTimeouts(const RTPTime &curtime,const RTPTime &senderti normaltimeout = false; notetimeout = false; - note = srcdat->SDES_GetNote(¬elen); + srcdat->SDES_GetNote(¬elen); if (notelen != 0) // Note has been set { RTPTime notetime = srcdat->INF_GetLastSDESNoteTime(); diff --git a/Monal/jrtplib/rtpudpv4transmitter.cpp b/Monal/jrtplib/rtpudpv4transmitter.cpp index 49598ce5d3..23dfffa18d 100644 --- a/Monal/jrtplib/rtpudpv4transmitter.cpp +++ b/Monal/jrtplib/rtpudpv4transmitter.cpp @@ -508,7 +508,7 @@ int RTPUDPv4Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) ip = (*it); RTP_SNPRINTF(str,16,"%d.%d.%d.%d",(int)((ip>>24)&0xFF),(int)((ip>>16)&0xFF),(int)((ip>>8)&0xFF),(int)(ip&0xFF)); - len = strlen(str); + len = (int)strlen(str); localhostnamelength = len; localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength + 1]; @@ -1278,7 +1278,7 @@ int RTPUDPv4Transmitter::PollSocket(bool rtp) { RTPTime curtime = RTPTime::CurrentTime(); fromlen = sizeof(struct sockaddr_in); - recvlen = recvfrom(sock,packetbuffer,RTPUDPV4TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); + recvlen = (int)recvfrom(sock,packetbuffer,RTPUDPV4TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); if (recvlen > 0) { bool acceptdata; @@ -1772,7 +1772,6 @@ void RTPUDPv4Transmitter::GetLocalIPList_DNS() if (he == 0) return; - ip = 0; i = 0; done = false; while (!done) diff --git a/Monal/jrtplib/rtpudpv6transmitter.cpp b/Monal/jrtplib/rtpudpv6transmitter.cpp index 771e711620..3d13c5b819 100644 --- a/Monal/jrtplib/rtpudpv6transmitter.cpp +++ b/Monal/jrtplib/rtpudpv6transmitter.cpp @@ -517,7 +517,7 @@ int RTPUDPv6Transmitter::GetLocalHostName(uint8_t *buffer,size_t *bufferlength) } RTP_SNPRINTF(str,48,"%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X",(int)ip16[0],(int)ip16[1],(int)ip16[2],(int)ip16[3],(int)ip16[4],(int)ip16[5],(int)ip16[6],(int)ip16[7]); - len = strlen(str); + len = (int)strlen(str); localhostnamelength = len; localhostname = RTPNew(GetMemoryManager(),RTPMEM_TYPE_OTHER) uint8_t [localhostnamelength+1]; @@ -1291,7 +1291,7 @@ int RTPUDPv6Transmitter::PollSocket(bool rtp) { RTPTime curtime = RTPTime::CurrentTime(); fromlen = sizeof(struct sockaddr_in6); - recvlen = recvfrom(sock,packetbuffer,RTPUDPV6TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); + recvlen = (int)recvfrom(sock,packetbuffer,RTPUDPV6TRANS_MAXPACKSIZE,0,(struct sockaddr *)&srcaddr,&fromlen); if (recvlen > 0) { bool acceptdata; @@ -1712,7 +1712,6 @@ bool RTPUDPv6Transmitter::GetLocalIPList_Interfaces() void RTPUDPv6Transmitter::GetLocalIPList_DNS() { - int status; char name[1024]; gethostname(name,1023); @@ -1726,7 +1725,7 @@ void RTPUDPv6Transmitter::GetLocalIPList_DNS() hints.ai_socktype = 0; hints.ai_protocol = 0; - if ((status = getaddrinfo(name,0,&hints,&res)) != 0) + if (getaddrinfo(name,0,&hints,&res) != 0) return; tmp = res; diff --git a/Monal/localization/Base.lproj/ContactDetails.storyboard b/Monal/localization/Base.lproj/ContactDetails.storyboard index da9bcada00..3346997a59 100644 --- a/Monal/localization/Base.lproj/ContactDetails.storyboard +++ b/Monal/localization/Base.lproj/ContactDetails.storyboard @@ -64,7 +64,7 @@ -