зеркало из https://github.com/nextcloud/talk-ios.git
443 строки
16 KiB
Objective-C
443 строки
16 KiB
Objective-C
/**
|
|
* @copyright Copyright (c) 2020 Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @author Ivan Sein <ivan@nextcloud.com>
|
|
*
|
|
* @license GNU GPL version 3 or any later version
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#import "NCUtils.h"
|
|
|
|
#import <MobileCoreServices/MobileCoreServices.h>
|
|
#import <CommonCrypto/CommonDigest.h>
|
|
|
|
#import "OpenInFirefoxControllerObjC.h"
|
|
|
|
#import "NCDatabaseManager.h"
|
|
#import "NCUserDefaults.h"
|
|
#import "NSDate+DateTools.h"
|
|
|
|
|
|
static NSString *const nextcloudScheme = @"nextcloud:";
|
|
|
|
@implementation NCUtils
|
|
|
|
+ (NSString *)previewImageForFileExtension:(NSString *)fileExtension
|
|
{
|
|
CFStringRef fileExtensionSR = (__bridge CFStringRef)fileExtension;
|
|
CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtensionSR, NULL);
|
|
NSString *result = [self previewImageForFileType:fileUTI];
|
|
CFRelease(fileUTI);
|
|
|
|
return result;
|
|
}
|
|
|
|
+ (NSString *)previewImageForFileMIMEType:(NSString *)fileMIMEType
|
|
{
|
|
if (!fileMIMEType || [fileMIMEType isKindOfClass:[NSNull class]] || [fileMIMEType isEqualToString:@""]) {
|
|
return @"file";
|
|
}
|
|
CFStringRef fileMIMETypeSR = (__bridge CFStringRef)fileMIMEType;
|
|
CFStringRef fileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, fileMIMETypeSR, NULL);
|
|
|
|
NSString *resultImage = @"file";
|
|
|
|
if ([fileMIMEType isEqualToString:@"httpd/unix-directory"]) {
|
|
resultImage = @"folder";
|
|
}
|
|
|
|
if (UTTypeIsDeclared(fileUTI)) {
|
|
resultImage = [self previewImageForFileType:fileUTI];
|
|
}
|
|
|
|
CFRelease(fileUTI);
|
|
|
|
return resultImage;
|
|
}
|
|
|
|
+ (NSString *)previewImageForFileType:(CFStringRef)fileType
|
|
{
|
|
NSString *previewImage = @"file";
|
|
if (fileType) {
|
|
if (UTTypeConformsTo(fileType, kUTTypeAudio)) previewImage = @"file-audio";
|
|
else if (UTTypeConformsTo(fileType, kUTTypeMovie)) previewImage = @"file-video";
|
|
else if (UTTypeConformsTo(fileType, kUTTypeImage)) previewImage = @"file-image";
|
|
else if (UTTypeConformsTo(fileType, kUTTypeSpreadsheet)) previewImage = @"file-spreadsheet";
|
|
else if (UTTypeConformsTo(fileType, kUTTypePresentation)) previewImage = @"file-presentation";
|
|
else if (UTTypeConformsTo(fileType, kUTTypePDF)) previewImage = @"file-pdf";
|
|
else if (UTTypeConformsTo(fileType, kUTTypeVCard)) previewImage = @"file-vcard";
|
|
else if (UTTypeConformsTo(fileType, kUTTypeText)) previewImage = @"file-text";
|
|
else if ([(__bridge NSString *)fileType containsString:@"org.openxmlformats"] || [(__bridge NSString *)fileType containsString:@"org.oasis-open.opendocument"]) previewImage = @"file-document";
|
|
else if (UTTypeConformsTo(fileType, kUTTypeZipArchive)) previewImage = @"file-zip";
|
|
}
|
|
return previewImage;
|
|
}
|
|
|
|
+ (BOOL)isImageFileType:(NSString *)fileMIMEType
|
|
{
|
|
return [[self previewImageForFileMIMEType:fileMIMEType] isEqual:@"file-image"];
|
|
}
|
|
|
|
+ (BOOL)isVideoFileType:(NSString *)fileMIMEType
|
|
{
|
|
return [[self previewImageForFileMIMEType:fileMIMEType] isEqual:@"file-video"];
|
|
}
|
|
|
|
+ (BOOL)isNextcloudAppInstalled
|
|
{
|
|
BOOL isInstalled = NO;
|
|
#ifndef APP_EXTENSION
|
|
NSURL *url = [NSURL URLWithString:nextcloudScheme];
|
|
isInstalled = [[UIApplication sharedApplication] canOpenURL:url];
|
|
#endif
|
|
return isInstalled;
|
|
}
|
|
|
|
+ (void)openFileInNextcloudApp:(NSString *)path withFileLink:(NSString *)link
|
|
{
|
|
#ifndef APP_EXTENSION
|
|
if (![self isNextcloudAppInstalled]) {
|
|
return;
|
|
}
|
|
TalkAccount *activeAccount = [[NCDatabaseManager sharedInstance] activeAccount];
|
|
NSString *nextcloudURLString = [NSString stringWithFormat:@"%@//open-file?path=%@&user=%@&link=%@", nextcloudScheme, path, activeAccount.user, link];
|
|
NSURL *nextcloudURL = [NSURL URLWithString:[nextcloudURLString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
|
|
[[UIApplication sharedApplication] openURL:nextcloudURL options:@{} completionHandler:nil];
|
|
#endif
|
|
}
|
|
|
|
+ (void)openFileInNextcloudAppOrBrowser:(NSString *)path withFileLink:(NSString *)link
|
|
{
|
|
#ifndef APP_EXTENSION
|
|
if (path && link) {
|
|
if ([NCUtils isNextcloudAppInstalled]) {
|
|
[NCUtils openFileInNextcloudApp:path withFileLink:link];
|
|
} else {
|
|
[self openLinkInBrowser:link];
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
+ (void)openLinkInBrowser:(NSString *)link
|
|
{
|
|
#ifndef APP_EXTENSION
|
|
if (link) {
|
|
NSURL *url = [NSURL URLWithString:link];
|
|
if ([[NCUserDefaults defaultBrowser] isEqualToString:@"Firefox"] && [[OpenInFirefoxControllerObjC sharedInstance] isFirefoxInstalled]) {
|
|
[[OpenInFirefoxControllerObjC sharedInstance] openInFirefox:url];
|
|
} else {
|
|
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
+ (NSDate *)dateFromDateAtomFormat:(NSString *)dateAtomFormatString
|
|
{
|
|
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
|
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
|
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
|
|
|
|
return [dateFormatter dateFromString:dateAtomFormatString];
|
|
}
|
|
+ (NSString *)dateAtomFormatFromDate:(NSDate *)date
|
|
{
|
|
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
|
[dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
|
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
|
|
|
|
return [dateFormatter stringFromDate:date];
|
|
}
|
|
+ (NSString *)readableDateTimeFromDate:(NSDate *)date
|
|
{
|
|
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
|
|
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
|
|
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
|
|
dateFormatter.doesRelativeDateFormatting = YES;
|
|
return [dateFormatter stringFromDate:date];
|
|
}
|
|
|
|
+ (NSString *)readableTimeOrDateFromDate:(NSDate *)date
|
|
{
|
|
if ([date isToday]) {
|
|
return [self getTimeFromDate:date];
|
|
} else if ([date isYesterday]) {
|
|
return NSLocalizedString(@"Yesterday", nil);
|
|
}
|
|
|
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
[formatter setTimeStyle:NSDateFormatterNoStyle];
|
|
[formatter setDateStyle:NSDateFormatterShortStyle];
|
|
return [formatter stringFromDate:date];
|
|
}
|
|
|
|
+ (NSString *)getTimeFromDate:(NSDate *)date
|
|
{
|
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
[formatter setTimeStyle:NSDateFormatterShortStyle];
|
|
[formatter setDateStyle:NSDateFormatterNoStyle];
|
|
return [formatter stringFromDate:date];
|
|
}
|
|
|
|
+ (NSString *)relativeTimeFromDate:(NSDate *)date
|
|
{
|
|
NSDate *todayDate = [NSDate date];
|
|
double ti = [date timeIntervalSinceDate:todayDate];
|
|
ti = ti * -1;
|
|
if (ti < 60) {
|
|
// This minute
|
|
return NSLocalizedString(@"less than a minute ago", nil);
|
|
} else if (ti < 3600) {
|
|
// This hour
|
|
int diff = round(ti / 60);
|
|
return [NSString localizedStringWithFormat:NSLocalizedString(@"%d minutes ago", nil), diff];
|
|
} else if (ti < 86400) {
|
|
// This day
|
|
int diff = round(ti / 60 / 60);
|
|
return[NSString localizedStringWithFormat:NSLocalizedString(@"%d hours ago", nil), diff];
|
|
} else if (ti < 86400 * 30) {
|
|
// This month
|
|
int diff = round(ti / 60 / 60 / 24);
|
|
return[NSString localizedStringWithFormat:NSLocalizedString(@"%d days ago", nil), diff];
|
|
} else {
|
|
// Older than one month
|
|
NSDateFormatter *df = [[NSDateFormatter alloc] init];
|
|
[df setFormatterBehavior:NSDateFormatterBehavior10_4];
|
|
[df setDateStyle:NSDateFormatterMediumStyle];
|
|
return [df stringFromDate:date];
|
|
}
|
|
}
|
|
|
|
+ (NSString *)sha1FromString:(NSString *)string
|
|
{
|
|
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
|
|
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
|
|
|
|
CC_SHA1(data.bytes, (CC_LONG)data.length, digest);
|
|
|
|
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
|
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
|
[output appendFormat:@"%02x", digest[i]];
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
+ (UIImage *)blurImageFromImage:(UIImage *)image
|
|
{
|
|
CGFloat inputRadius = 8.0f;
|
|
CIContext *context = [CIContext contextWithOptions:nil];
|
|
CIImage *inputImage = [[CIImage alloc] initWithImage:image];
|
|
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
|
|
[filter setValue:inputImage forKey:kCIInputImageKey];
|
|
[filter setValue:[NSNumber numberWithFloat:inputRadius] forKey:@"inputRadius"];
|
|
CIImage *result = [filter valueForKey:kCIOutputImageKey];
|
|
CGRect imageRect = [inputImage extent];
|
|
CGRect cropRect = CGRectMake(imageRect.origin.x + inputRadius, imageRect.origin.y + inputRadius, imageRect.size.width - inputRadius * 2, imageRect.size.height - inputRadius * 2);
|
|
CGImageRef cgImage = [context createCGImage:result fromRect:imageRect];
|
|
CGImageRef cgImageCroped = CGImageCreateWithImageInRect(cgImage, cropRect);
|
|
|
|
UIImage *resultImage = [UIImage imageWithCGImage:cgImageCroped];
|
|
CGImageRelease(cgImage);
|
|
CGImageRelease(cgImageCroped);
|
|
|
|
return resultImage;
|
|
}
|
|
|
|
+ (UIColor *)searchbarBGColorForColor:(UIColor *)color
|
|
{
|
|
CGFloat luma = [self calculateLumaFromColor:color];
|
|
return (luma > 0.6) ? [UIColor colorWithWhite:0 alpha:0.1] : [UIColor colorWithWhite:1 alpha:0.2];
|
|
}
|
|
|
|
+ (CGFloat)calculateLumaFromColor:(UIColor *)color
|
|
{
|
|
CGFloat red, green, blue, alpha;
|
|
[color getRed: &red green: &green blue: &blue alpha: &alpha];
|
|
return (0.2126 * red + 0.7152 * green + 0.0722 * blue);
|
|
}
|
|
|
|
+ (UIColor *)colorFromHexString:(NSString *)hexString
|
|
{
|
|
BOOL isValidColorString = hexString && ![hexString isKindOfClass:[NSNull class]] && ![hexString isEqualToString:@""];
|
|
|
|
if (!isValidColorString) {
|
|
return nil;
|
|
}
|
|
|
|
// Check hex color string format (e.g."#00FF00")
|
|
NSError *error = nil;
|
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^#(?:[0-9a-fA-F]{6})$" options:NSRegularExpressionCaseInsensitive error:&error];
|
|
NSTextCheckingResult *match = [regex firstMatchInString:hexString options:0 range:NSMakeRange(0, hexString.length)];
|
|
if ([match numberOfRanges] != 1) {
|
|
return nil;
|
|
}
|
|
|
|
// Convert Hex color to UIColor
|
|
unsigned rgbValue = 0;
|
|
NSScanner *scanner = [NSScanner scannerWithString:hexString];
|
|
[scanner setScanLocation:1]; // bypass '#' character
|
|
[scanner scanHexInt:&rgbValue];
|
|
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0];
|
|
}
|
|
|
|
+ (BOOL)isValidIndexPath:(NSIndexPath *)indexPath forTableView:(UITableView *)tableView
|
|
{
|
|
return indexPath.section < tableView.numberOfSections && indexPath.row < [tableView numberOfRowsInSection:indexPath.section];
|
|
}
|
|
|
|
|
|
+ (NSString *)valueForKey:(NSString *)key fromQueryItems:(NSArray *)queryItems
|
|
{
|
|
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", key];
|
|
NSURLQueryItem *queryItem = [[queryItems filteredArrayUsingPredicate:predicate] firstObject];
|
|
return queryItem.value;
|
|
}
|
|
|
|
+ (NSURL *)getLogfilePath
|
|
{
|
|
NSURL *documentDir = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
|
|
|
if (!documentDir) {
|
|
NSLog(@"Unable to retrieve document directory");
|
|
return nil;
|
|
}
|
|
|
|
NSURL *logDir = [documentDir URLByAppendingPathComponent:@"logs"];
|
|
NSString *logPath = [logDir path];
|
|
|
|
// Allow writing to files while the app is in the background
|
|
if (![[NSFileManager defaultManager] fileExistsAtPath:logPath]) {
|
|
[[NSFileManager defaultManager] createDirectoryAtPath:logPath withIntermediateDirectories:YES attributes:nil error:nil];
|
|
}
|
|
[[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey:NSFileProtectionNone} ofItemAtPath:logPath error:nil];
|
|
|
|
return logDir;
|
|
}
|
|
|
|
+ (void)removeOldLogfiles
|
|
{
|
|
NSURL *logPathURL = [self getLogfilePath];
|
|
|
|
if (!logPathURL) {
|
|
return;
|
|
}
|
|
|
|
NSString *logPath = [logPathURL path];
|
|
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:logPath];
|
|
|
|
NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
|
|
dayComponent.day = -10;
|
|
|
|
NSDate *thresholdDate = [[NSCalendar currentCalendar] dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
|
|
NSString *file;
|
|
|
|
while (file = [enumerator nextObject])
|
|
{
|
|
NSString *filePath = [logPath stringByAppendingPathComponent:file];
|
|
NSDate *creationDate = [[fileManager attributesOfItemAtPath:filePath error:nil] fileCreationDate];
|
|
|
|
if ([creationDate compare:thresholdDate] == NSOrderedAscending && [file hasPrefix:@"debug-"] && [file hasSuffix:@".log"]) {
|
|
NSLog(@"Deleting old logfile %@", filePath);
|
|
|
|
[fileManager removeItemAtPath:filePath error:nil];
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (void)log:(NSString *)message
|
|
{
|
|
@try {
|
|
[self removeOldLogfiles];
|
|
|
|
NSURL *logPath = [self getLogfilePath];
|
|
|
|
if (!logPath) {
|
|
return;
|
|
}
|
|
|
|
dispatch_queue_t currentQueue = dispatch_get_current_queue();
|
|
|
|
int applicationState = -1;
|
|
float backgroundTimeRemaining = -1;
|
|
|
|
#ifndef APP_EXTENSION
|
|
applicationState = (int)[UIApplication sharedApplication].applicationState;
|
|
backgroundTimeRemaining = [UIApplication sharedApplication].backgroundTimeRemaining;
|
|
#endif
|
|
|
|
NSDate *now = [NSDate date];
|
|
|
|
NSString *logMessage = [NSString stringWithFormat:@"%@ (%@): %@\nState: %d, Time remaining %f\n\n",
|
|
[now formattedDateWithFormat:@"y-MM-dd H:mm:ss.SSSS"],
|
|
[currentQueue description],
|
|
message,
|
|
applicationState,
|
|
backgroundTimeRemaining
|
|
];
|
|
|
|
|
|
NSString *dateString = [now formattedDateWithFormat:@"yyyy-MM-dd"];
|
|
NSString *logfileName = [NSString stringWithFormat:@"debug-%@.log", dateString];
|
|
NSString *fullPath = [[logPath URLByAppendingPathComponent:logfileName] path];
|
|
|
|
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:fullPath];
|
|
if (fileHandle) {
|
|
[fileHandle seekToEndOfFile];
|
|
[fileHandle writeData:[logMessage dataUsingEncoding:NSUTF8StringEncoding]];
|
|
[fileHandle closeFile];
|
|
} else {
|
|
[logMessage writeToFile:fullPath atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
|
}
|
|
|
|
NSLog(@"%@", logMessage);
|
|
} @catch (NSException *exception) {
|
|
NSLog(@"Exception in NCUtils.log: %@", exception.description);
|
|
NSLog(@"Message: %@", message);
|
|
}
|
|
}
|
|
|
|
+ (BOOL)isiOSAppOnMac
|
|
{
|
|
return [NSProcessInfo processInfo].isiOSAppOnMac;
|
|
}
|
|
|
|
+ (NSString *)removeHTMLFromString:(NSString *)string
|
|
{
|
|
// Preserve newlines
|
|
string = [string stringByReplacingOccurrencesOfString:@"\n" withString:@"<br>"];
|
|
NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
|
|
NSError *error = nil;
|
|
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithData:stringData
|
|
options:@{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType,
|
|
NSCharacterEncodingDocumentAttribute:@(NSUTF8StringEncoding)
|
|
}
|
|
documentAttributes:nil
|
|
error:&error];
|
|
|
|
if (error) {
|
|
return string;
|
|
}
|
|
|
|
return [attributedString string];
|
|
}
|
|
|
|
@end
|