mirror of
https://github.com/MacPass/MacPass.git
synced 2025-12-22 21:09:33 +00:00
173 lines
6.7 KiB
Objective-C
173 lines
6.7 KiB
Objective-C
// MPDocument+Search.m
|
|
//
|
|
// MacPass
|
|
//
|
|
// Created by Michael Starke on 25.02.14.
|
|
// Copyright (c) 2014 HicknHack Software GmbH. All rights reserved.
|
|
//
|
|
|
|
#import "MPDocument+Search.h"
|
|
#import "MPDocument.h"
|
|
#import "MPDocumentWindowController.h"
|
|
|
|
#import "KPKGroup.h"
|
|
#import "KPKEntry.h"
|
|
|
|
#import "MPFlagsHelper.h"
|
|
|
|
NSString *const MPDocumentDidEnterSearchNotification = @"com.hicknhack.macpass.MPDocumentDidEnterSearchNotification";
|
|
NSString *const MPDocumentDidChangeSearchFlags = @"com.hicknhack.macpass.MPDocumentDidChangeSearchFlagsNotification";
|
|
NSString *const MPDocumentDidExitSearchNotification = @"com.hicknhack.macpass.MPDocumentDidExitSearchNotification";
|
|
|
|
NSString *const MPDocumentDidChangeSearchResults = @"com.hicknhack.macpass.MPDocumentDidChangeSearchResults";
|
|
|
|
NSString *const kMPDocumentSearchResultsKey = @"kMPDocumentSearchResultsKey";
|
|
|
|
|
|
@implementation MPDocument (Search)
|
|
|
|
#pragma mark Actions
|
|
|
|
- (void)performFindPanelAction:(id)sender {
|
|
self.hasSearch = YES;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidEnterSearchNotification object:self];
|
|
[self updateSearch:self];
|
|
}
|
|
|
|
- (void)updateSearch:(id)sender {
|
|
MPDocumentWindowController *windowController = [self windowControllers][0];
|
|
self.searchString = [windowController.searchField stringValue];
|
|
if(NO == self.hasSearch) {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidEnterSearchNotification object:self];
|
|
}
|
|
self.hasSearch = YES;
|
|
MPDocument __weak *weakSelf = self;
|
|
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
dispatch_async(backgroundQueue, ^{
|
|
NSArray *results = [weakSelf _findEntriesMatchingCurrentSearch];
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
weakSelf.selectedEntry = nil;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidChangeSearchResults object:weakSelf userInfo:@{ kMPDocumentSearchResultsKey: results }];
|
|
});
|
|
});
|
|
}
|
|
|
|
- (void)exitSearch:(id)sender {
|
|
self.searchString = nil;
|
|
self.hasSearch = NO;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidExitSearchNotification object:self];
|
|
}
|
|
|
|
- (void)toggleSearchFlags:(id)sender {
|
|
static MPEntrySearchFlags oldFlags;
|
|
if(![sender respondsToSelector:@selector(tag)]) {
|
|
return; // We need to read the button tag
|
|
}
|
|
if(![sender respondsToSelector:@selector(state)]) {
|
|
return; // We need to read the button state
|
|
}
|
|
MPEntrySearchFlags toggleFlag = [sender tag];
|
|
MPEntrySearchFlags newFlags = MPEntrySearchNone;
|
|
BOOL isDoublePasswordFlag = (toggleFlag == MPEntrySearchDoublePasswords);
|
|
NSButton *button = sender;
|
|
switch([button state]) {
|
|
case NSOffState:
|
|
toggleFlag ^= MPEntrySearchAllFlags;
|
|
newFlags = isDoublePasswordFlag ? oldFlags : (self.activeFlags & toggleFlag);
|
|
break;
|
|
case NSOnState:
|
|
if(isDoublePasswordFlag) {
|
|
oldFlags = self.activeFlags;
|
|
newFlags = MPEntrySearchDoublePasswords;
|
|
}
|
|
else {
|
|
/* always mask the double passwords in case another button was pressed */
|
|
self.activeFlags &= (MPEntrySearchDoublePasswords ^ MPEntrySearchAllFlags);
|
|
newFlags = self.activeFlags | toggleFlag;
|
|
}
|
|
break;
|
|
default:
|
|
NSAssert(NO, @"Internal state is inconsistent");
|
|
return;
|
|
}
|
|
if(newFlags != self.activeFlags) {
|
|
self.activeFlags = (newFlags == MPEntrySearchNone) ? MPEntrySearchTitles : newFlags;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:MPDocumentDidChangeSearchFlags object:self];
|
|
[self updateSearch:self];
|
|
}
|
|
}
|
|
|
|
#pragma mark Search
|
|
- (NSArray *)_findEntriesMatchingCurrentSearch {
|
|
/* Filter double passwords */
|
|
if(MPTestFlagInOptions(MPEntrySearchDoublePasswords, self.activeFlags)) {
|
|
__block NSMutableDictionary *passwordToEntryMap = [[NSMutableDictionary alloc] initWithCapacity:100];
|
|
/* Build up a usage map */
|
|
[[self.root childEntries] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
|
KPKEntry *entry = obj;
|
|
/* skip entries without passwords */
|
|
if([entry.password length] > 0) {
|
|
NSMutableSet *entrySet = passwordToEntryMap[entry.password];
|
|
if(entrySet) {
|
|
[entrySet addObject:entry];
|
|
}
|
|
else {
|
|
passwordToEntryMap[entry.password] = [NSMutableSet setWithObject:entry];
|
|
}
|
|
}
|
|
}];
|
|
/* check for usage count */
|
|
__block NSMutableArray *doublePasswords = [[NSMutableArray alloc] init];
|
|
[[passwordToEntryMap allKeys] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
|
NSSet *entrySet = passwordToEntryMap[obj];
|
|
if([entrySet count] > 1) {
|
|
[doublePasswords addObjectsFromArray:[entrySet allObjects]];
|
|
}
|
|
}];
|
|
return doublePasswords;
|
|
}
|
|
/* Filter using predicates */
|
|
NSArray *predicates = [self _filterPredicatesWithString:self.searchString];
|
|
if(predicates) {
|
|
NSPredicate *fullFilter = [NSCompoundPredicate orPredicateWithSubpredicates:predicates];
|
|
return [[self.root childEntries] filteredArrayUsingPredicate:fullFilter];
|
|
}
|
|
/* No filter, just return everything */
|
|
return [self.root childEntries];
|
|
}
|
|
|
|
- (NSArray *)optionsEnabledInMode:(MPEntrySearchFlags)mode {
|
|
NSArray *allOptions = @[ @(MPEntrySearchUrls), @(MPEntrySearchUsernames),
|
|
@(MPEntrySearchTitles), @(MPEntrySearchPasswords) ,
|
|
@(MPEntrySearchNotes), @(MPEntrySearchDoublePasswords) ];
|
|
|
|
NSIndexSet *indexes = [allOptions indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
|
|
MPEntrySearchFlags flag = [obj integerValue];
|
|
return MPTestFlagInOptions(flag, mode);
|
|
}];
|
|
return [allOptions objectsAtIndexes:indexes];
|
|
}
|
|
|
|
- (NSArray *)_filterPredicatesWithString:(NSString *)string{
|
|
NSMutableArray *prediactes = [[NSMutableArray alloc] initWithCapacity:4];
|
|
|
|
if(MPTestFlagInOptions(MPEntrySearchTitles, self.activeFlags)) {
|
|
[prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.title CONTAINS[cd] %@", string]];
|
|
}
|
|
if(MPTestFlagInOptions(MPEntrySearchUsernames, self.activeFlags)) {
|
|
[prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.username CONTAINS[cd] %@", string]];
|
|
}
|
|
if(MPTestFlagInOptions(MPEntrySearchUrls, self.activeFlags)) {
|
|
[prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.url CONTAINS[cd] %@", string]];
|
|
}
|
|
if(MPTestFlagInOptions(MPEntrySearchPasswords, self.activeFlags)) {
|
|
[prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.password CONTAINS[cd] %@", string]];
|
|
}
|
|
if(MPTestFlagInOptions(MPEntrySearchNotes, self.activeFlags)) {
|
|
[prediactes addObject:[NSPredicate predicateWithFormat:@"SELF.notes CONTAINS[cd] %@", string]];
|
|
}
|
|
return prediactes;
|
|
}
|
|
|
|
@end
|