11#import "BookmarkListController.h"
13#import "BookmarkEditorController.h"
14#import "RDPSessionViewController.h"
15#import "Toast+UIView.h"
16#import "Reachability.h"
17#import "GlobalDefaults.h"
20#define SHOW_SESSIONS_SECTION 0
22#define SECTION_SESSIONS 0
23#define SECTION_BOOKMARKS 1
26@interface BookmarkListController (Private)
27#pragma mark misc functions
28- (void)performSearch:(NSString *)searchText;
29#pragma mark Persisting bookmarks
30- (void)scheduleWriteBookmarksToDataStore;
31- (void)writeBookmarksToDataStore;
32- (void)scheduleWriteManualBookmarksToDataStore;
33- (void)writeManualBookmarksToDataStore;
34- (void)readManualBookmarksFromDataStore;
35- (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url;
36- (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url;
37- (NSURL *)manualBookmarksDataStoreURL;
38- (NSURL *)connectionHistoryDataStoreURL;
43@synthesize searchBar = _searchBar, tableView = _tableView, sessTableCell = _sessTableCell;
47- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
49 if ((
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
52 [
self readManualBookmarksFromDataStore];
55 [
self readConnectionHistoryFromDataStore];
58 _manual_search_result = nil;
61 [[NSNotificationCenter defaultCenter] addObserver:self
62 selector:@selector(sessionDisconnected:)
63 name:TSXSessionDidDisconnectNotification
65 [[NSNotificationCenter defaultCenter] addObserver:self
66 selector:@selector(sessionFailedToConnect:)
67 name:TSXSessionDidFailToConnectNotification
71 [
self setTitle:NSLocalizedString(@"Connections",
72 @"'Connections': bookmark controller title")];
73 [
self setTabBarItem:[[[UITabBarItem alloc]
74 initWithTabBarSystemItem:UITabBarSystemItemBookmarks
78 [[NSNotificationCenter defaultCenter] addObserver:self
79 selector:@selector(reachabilityChanged:)
80 name:kReachabilityChangedNotification
84 _active_sessions = [[NSMutableArray alloc] init];
85 _temporary_bookmark = nil;
101 [[
self navigationItem] setRightBarButtonItem:[
self editButtonItem]];
104 [_tableView setPreservesSuperviewLayoutMargins:YES];
105 [_searchBar setPreservesSuperviewLayoutMargins:YES];
108 [_tableView setEstimatedRowHeight:UITableViewAutomaticDimension];
111- (void)viewWillAppear:(BOOL)animated
113 [
super viewWillAppear:animated];
116 if ([[_searchBar text] length] > 0)
117 [
self performSearch:[_searchBar text]];
120 [_tableView reloadData];
124- (void)viewSafeAreaInsetsDidChange
126 [
super viewSafeAreaInsetsDidChange];
127 [[
self view] setNeedsLayout];
130- (void)viewDidLayoutSubviews
132 [
super viewDidLayoutSubviews];
134 const CGRect bounds = [[
self view] bounds];
135 const UIEdgeInsets safe = [[
self view] safeAreaInsets];
136 const CGFloat x = safe.left;
137 const CGFloat w = bounds.size.width - safe.left - safe.right;
138 CGFloat searchH = CGRectGetHeight([_searchBar frame]);
140 [_searchBar setFrame:CGRectMake(x, safe.top, w, searchH)];
142 setFrame:CGRectMake(x, safe.top + searchH, w, bounds.size.height - safe.top - searchH)];
145- (void)viewWillDisappear:(BOOL)animated
147 [
super viewWillDisappear:animated];
150 [_searchBar setText:@""];
151 [_searchBar resignFirstResponder];
152 [
self performSearch:@""];
156- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
162- (void)didReceiveMemoryWarning
165 [
super didReceiveMemoryWarning];
172 [
super viewDidUnload];
179 [[NSNotificationCenter defaultCenter] removeObserver:self];
181 [_temporary_bookmark release];
182 [_connection_history release];
183 [_active_sessions release];
184 [_manual_search_result release];
185 [_manual_bookmarks release];
191#pragma mark Table view data source
193- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
199- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
204 case SECTION_SESSIONS:
208 case SECTION_BOOKMARKS:
211 if (_manual_search_result != nil)
212 return ([_manual_search_result count] + [_history_search_result count] + 1);
213 return ([_manual_bookmarks count] + 1);
223- (UITableViewCell *)cellForGenericListEntry
225 static NSString *CellIdentifier =
@"BookmarkListCell";
226 UITableViewCell *cell = [[
self tableView] dequeueReusableCellWithIdentifier:CellIdentifier];
229 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
230 reuseIdentifier:CellIdentifier] autorelease];
231 [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
232 [cell setAccessoryType:UITableViewCellAccessoryDetailButton];
238- (void)setText:(NSString *)text forGenericListEntry:(UITableViewCell *)cell
240 UIListContentConfiguration *content = [cell defaultContentConfiguration];
241 [content setText:text];
242 [cell setContentConfiguration:content];
247 static NSString *BookmarkCellIdentifier =
@"BookmarkCell";
249 dequeueReusableCellWithIdentifier:BookmarkCellIdentifier];
253 reuseIdentifier:BookmarkCellIdentifier] autorelease];
260- (UITableViewCell *)tableView:(UITableView *)tableView
261 cellForRowAtIndexPath:(NSIndexPath *)indexPath
264 switch ([indexPath section])
266 case SECTION_SESSIONS:
269 static NSString *SessionCellIdentifier =
@"SessionCell";
271 dequeueReusableCellWithIdentifier:SessionCellIdentifier];
274 [[NSBundle mainBundle] loadNibNamed:@"SessionTableViewCell" owner:self options:nil];
275 cell = _sessTableCell;
276 _sessTableCell = nil;
280 RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]];
281 [[cell title] setText:[session sessionName]];
282 [[cell server] setText:[[session params] StringForKey:@"hostname"]];
283 [[cell username] setText:[[session params] StringForKey:@"username"]];
285 setImage:[session getScreenshotWithSize:[[cell screenshot] bounds].size]];
286 [[cell disconnectButton] setTag:[indexPath row]];
290 case SECTION_BOOKMARKS:
293 if ([indexPath row] == 0)
297 UITableViewCell *cell = [
self cellForGenericListEntry];
298 if ([[_searchBar text] length] == 0)
300 [
self setText:NSLocalizedString(@"Add Connection",
301 @"'Add Connection': button label")
302 forGenericListEntry:cell];
303 [cell setAccessoryType:UITableViewCellAccessoryNone];
307 [
self setText:[_searchBar text] forGenericListEntry:cell];
308 [cell setAccessoryType:UITableViewCellAccessoryDetailButton];
316 if ([
self isIndexPathToHistoryItem:indexPath])
318 UITableViewCell *cell = [
self cellForGenericListEntry];
319 [
self setText:[_history_search_result
320 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]]
321 forGenericListEntry:cell];
322 [cell setAccessoryType:UITableViewCellAccessoryDetailButton];
330 if (_manual_search_result == nil)
331 entry = [_manual_bookmarks
332 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
334 entry = [[_manual_search_result
335 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
336 valueForKey:@"bookmark"];
338 [cell setTitle:[entry label]];
339 [cell setSubTitle:[[entry params] StringForKey:@"hostname"]];
349 NSAssert(0,
@"Failed to create cell");
354- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
357 if ([indexPath section] == SECTION_SESSIONS)
359 if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0)
365- (void)tableView:(UITableView *)tableView
366 commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
367 forRowAtIndexPath:(NSIndexPath *)indexPath
369 if (editingStyle == UITableViewCellEditingStyleDelete)
372 switch ([indexPath section])
374 case SECTION_BOOKMARKS:
376 if (_manual_search_result == nil)
378 removeObjectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
382 if ([
self isIndexPathToHistoryItem:indexPath])
386 [_history_search_result
387 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]]];
388 [_history_search_result
389 removeObjectAtIndex:[
self historyIndexFromIndexPath:indexPath]];
395 [[_manual_search_result
396 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
397 valueForKey:@"bookmark"]];
398 [_manual_search_result
399 removeObjectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
402 [
self scheduleWriteManualBookmarksToDataStore];
407 [tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]]
408 withRowAnimation:UITableViewRowAnimationNone];
413- (void)tableView:(UITableView *)tableView
414 moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
415 toIndexPath:(NSIndexPath *)toIndexPath
417 if ([fromIndexPath compare:toIndexPath] != NSOrderedSame)
419 switch ([fromIndexPath section])
421 case SECTION_BOOKMARKS:
423 NSInteger fromIdx = [
self bookmarkIndexFromIndexPath:fromIndexPath];
424 NSInteger toIdx = [
self bookmarkIndexFromIndexPath:toIndexPath];
426 [[_manual_bookmarks objectAtIndex:fromIdx] retain];
427 [_manual_bookmarks removeObjectAtIndex:fromIdx];
428 if (toIdx >= [_manual_bookmarks count])
429 [_manual_bookmarks addObject:temp_bookmark];
431 [_manual_bookmarks insertObject:temp_bookmark atIndex:toIdx];
432 [temp_bookmark release];
434 [
self scheduleWriteManualBookmarksToDataStore];
442- (NSIndexPath *)tableView:(UITableView *)tableView
443 targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
444 toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
450 if ([proposedDestinationIndexPath row] == 0 ||
451 ([sourceIndexPath section] != [proposedDestinationIndexPath section]) ||
452 _manual_search_result != nil)
454 return sourceIndexPath;
458 return proposedDestinationIndexPath;
463- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
466 if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0)
471- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
473 if (SHOW_SESSIONS_SECTION && section == SECTION_SESSIONS && [_active_sessions count] > 0)
474 return NSLocalizedString(
@"My Sessions",
@"'My Session': section sessions header");
475 if (section == SECTION_BOOKMARKS)
476 return NSLocalizedString(
@"Manual Connections",
477 @"'Manual Connections': section manual bookmarks header");
481- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
486- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
489 if (!SHOW_SESSIONS_SECTION && section == SECTION_SESSIONS)
491 return UITableViewAutomaticDimension;
494- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
497 if (!SHOW_SESSIONS_SECTION && section == SECTION_SESSIONS)
499 return [tableView sectionFooterHeight];
502- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
504 if ([indexPath section] == SECTION_SESSIONS)
507 return UITableViewAutomaticDimension;
511#pragma mark Table view delegate
513- (void)setEditing:(BOOL)editing animated:(BOOL)animated
515 [
super setEditing:editing animated:animated];
516 [[
self tableView] setEditing:editing animated:animated];
519- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
521 if ([indexPath section] == SECTION_SESSIONS)
524 RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]];
525 UIViewController *ctrl =
528 session:session] autorelease];
529 [ctrl setHidesBottomBarWhenPushed:YES];
530 [[
self navigationController] pushViewController:ctrl animated:YES];
535 if ([indexPath section] == SECTION_BOOKMARKS)
538 if ([indexPath row] == 0)
540 if ([[_searchBar text] length] == 0)
547 [bookmarkEditorController
548 setTitle:NSLocalizedString(@"Add Connection", @"Add Connection title")];
549 [bookmarkEditorController setDelegate:self];
551 if ([[UIDevice currentDevice] userInterfaceIdiom] != UIUserInterfaceIdiomPad)
552 [bookmarkEditorController setHidesBottomBarWhenPushed:YES];
553 [[
self navigationController] pushViewController:bookmarkEditorController
560 bookmark = [
self bookmarkForQuickConnectTo:[_searchBar text]];
561 if (![_connection_history containsObject:[_searchBar text]])
563 [_connection_history addObject:[_searchBar text]];
564 [
self scheduleWriteConnectionHistoryToDataStore];
570 if (_manual_search_result != nil)
572 if ([
self isIndexPathToHistoryItem:indexPath])
575 NSString *item = [_history_search_result
576 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]];
577 bookmark = [
self bookmarkForQuickConnectTo:item];
580 bookmark = [[_manual_search_result
581 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
582 valueForKey:@"bookmark"];
585 bookmark = [_manual_bookmarks
586 objectAtIndex:[
self bookmarkIndexFromIndexPath:
593 reachabilityWithHostName:[[bookmark params]
594 StringForKey:@"hostname"]]
595 currentReachabilityStatus] == ReachableViaWiFi];
602 UIViewController *ctrl =
605 session:session] autorelease];
606 [ctrl setHidesBottomBarWhenPushed:YES];
607 [[
self navigationController] pushViewController:ctrl animated:YES];
608 [_active_sessions addObject:session];
613- (void)tableView:(UITableView *)tableView
614 accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
617 NSString *bookmark_editor_title =
618 NSLocalizedString(
@"Edit Connection",
@"Edit Connection title");
620 if ([indexPath section] == SECTION_BOOKMARKS)
622 if ([indexPath row] == 0)
625 bookmark = [
self bookmarkForQuickConnectTo:[_searchBar text]];
626 bookmark_editor_title = NSLocalizedString(
@"Add Connection",
@"Add Connection title");
630 if (_manual_search_result != nil)
632 if ([
self isIndexPathToHistoryItem:indexPath])
635 NSString *item = [_history_search_result
636 objectAtIndex:[
self historyIndexFromIndexPath:indexPath]];
637 bookmark = [
self bookmarkForQuickConnectTo:item];
638 bookmark_editor_title =
639 NSLocalizedString(
@"Add Connection",
@"Add Connection title");
642 bookmark = [[_manual_search_result
643 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]]
644 valueForKey:@"bookmark"];
647 bookmark = [_manual_bookmarks
648 objectAtIndex:[
self bookmarkIndexFromIndexPath:indexPath]];
659 if ([[UIDevice currentDevice] userInterfaceIdiom] != UIUserInterfaceIdiomPad)
660 [editBookmarkController setHidesBottomBarWhenPushed:YES];
661 [editBookmarkController setTitle:bookmark_editor_title];
662 [editBookmarkController setDelegate:self];
663 [[
self navigationController] pushViewController:editBookmarkController animated:YES];
668#pragma mark Search Bar Delegates
670- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
673 [searchBar setShowsCancelButton:YES animated:YES];
677- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
680 [_manual_search_result release];
681 _manual_search_result = nil;
684 [searchBar setText:@""];
685 [searchBar resignFirstResponder];
688- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
690 [searchBar setShowsCancelButton:NO animated:YES];
693 [_tableView setAllowsSelection:YES];
698- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
700 [_searchBar resignFirstResponder];
703- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
705 [
self performSearch:searchText];
706 [_tableView reloadData];
709#pragma mark - Session handling
712- (void)sessionDisconnected:(NSNotification *)notification
716 [_active_sessions removeObject:session];
719 if ([[
self navigationController] visibleViewController] ==
self)
720 [_tableView reloadSections:[NSIndexSet indexSetWithIndex:SECTION_SESSIONS]
721 withRowAnimation:UITableViewRowAnimationNone];
725 if (![_manual_bookmarks containsObject:[session bookmark]])
728 _temporary_bookmark = [[session bookmark] retain];
732 NSLocalizedString(
@"Save Connection Settings?",
@"Save connection settings title");
733 NSString *message = NSLocalizedString(
734 @"Your Connection Settings have not been saved. Do you want to save them?",
735 @"Save connection settings message");
736 UIAlertController *alert =
737 [UIAlertController alertControllerWithTitle:title
739 preferredStyle:UIAlertControllerStyleAlert];
740 [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"No", @"No Button")
741 style:UIAlertActionStyleCancel
743 [alert addAction:[UIAlertAction
744 actionWithTitle:NSLocalizedString(@"Yes", @"Yes Button")
745 style:UIAlertActionStyleDefault
746 handler:^(UIAlertAction *action) {
747 if (_temporary_bookmark)
749 [_manual_bookmarks addObject:_temporary_bookmark];
751 reloadSections:[NSIndexSet indexSetWithIndex:
753 withRowAnimation:UITableViewRowAnimationNone];
754 [_temporary_bookmark autorelease];
755 _temporary_bookmark = nil;
758 [
self presentViewController:alert animated:YES completion:nil];
762- (void)sessionFailedToConnect:(NSNotification *)notification
766 [_active_sessions removeObject:session];
769 [[
self view] makeToast:NSLocalizedString(@"Failed to connect to session!",
770 @"Failed to connect error message")
771 duration:ToastDurationNormal
775#pragma mark - Reachability notification
776- (void)reachabilityChanged:(NSNotification *)notification
780 if ([_active_sessions count] > 0)
782 RDPSession *session = [_active_sessions objectAtIndex:0];
783 [session disconnect];
787#pragma mark - BookmarkEditorController delegate
793 for (
int idx = 0; idx < [_manual_bookmarks count]; ++idx)
795 if ([[bookmark uuid] isEqualToString:[[_manual_bookmarks objectAtIndex:idx] uuid]])
797 [_manual_bookmarks replaceObjectAtIndex:idx withObject:bookmark];
803 [_manual_bookmarks addObject:bookmark];
806 NSString *hostname = [[bookmark params] StringForKey:@"hostname"];
807 if ([_connection_history containsObject:hostname])
809 [_connection_history removeObject:hostname];
810 [
self scheduleWriteConnectionHistoryToDataStore];
813 [
self scheduleWriteManualBookmarksToDataStore];
816- (IBAction)disconnectButtonPressed:(
id)sender
819 RDPSession *session = [_active_sessions objectAtIndex:[sender tag]];
820 [session disconnect];
823#pragma mark - Misc functions
825- (BOOL)hasNoBookmarks
827 return ([_manual_bookmarks count] == 0);
830- (void)performSearch:(NSString *)searchText
832 [_manual_search_result autorelease];
834 if ([searchText length] > 0)
836 _manual_search_result = [FilterBookmarks(
838 [searchText componentsSeparatedByCharactersInSet:[NSCharacterSet
839 whitespaceAndNewlineCharacterSet]])
841 _history_search_result = [FilterHistory(_connection_history, searchText) retain];
845 _history_search_result = nil;
846 _manual_search_result = nil;
850- (NSInteger)bookmarkIndexFromIndexPath:(NSIndexPath *)indexPath
852 return [indexPath row] -
853 ((_history_search_result != nil) ? [_history_search_result count] : 0) - 1;
856- (NSInteger)historyIndexFromIndexPath:(NSIndexPath *)indexPath
858 return [indexPath row] - 1;
861- (BOOL)isIndexPathToHistoryItem:(NSIndexPath *)indexPath
863 return (([indexPath row] - 1) < [_history_search_result count]);
870 [bookmark setLabel:host];
871 [[bookmark params] setValue:host forKey:@"hostname"];
875#pragma mark - Persisting bookmarks
877- (void)scheduleWriteBookmarksToDataStore
879 [[NSOperationQueue mainQueue] addOperationWithBlock:^{
880 [
self writeBookmarksToDataStore];
884- (void)writeBookmarksToDataStore
886 [
self writeManualBookmarksToDataStore];
889- (void)scheduleWriteManualBookmarksToDataStore
891 [[NSOperationQueue mainQueue]
892 addOperation:[[[NSInvocationOperation alloc]
894 selector:@selector(writeManualBookmarksToDataStore)
895 object:nil] autorelease]];
898- (void)writeManualBookmarksToDataStore
900 [
self writeArray:_manual_bookmarks toDataStoreURL:[
self manualBookmarksDataStoreURL]];
903- (void)scheduleWriteConnectionHistoryToDataStore
905 [[NSOperationQueue mainQueue]
906 addOperation:[[[NSInvocationOperation alloc]
908 selector:@selector(writeConnectionHistoryToDataStore)
909 object:nil] autorelease]];
912- (void)writeConnectionHistoryToDataStore
914 [
self writeArray:_connection_history toDataStoreURL:[
self connectionHistoryDataStoreURL]];
917- (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url
919 NSError *error = nil;
920 NSData *archived_data = [NSKeyedArchiver archivedDataWithRootObject:bookmarks
921 requiringSecureCoding:YES
927 NSLog(
@"%s: failed to archive %@: %@", __func__, [url lastPathComponent], error);
931 [archived_data writeToURL:url atomically:YES];
934- (void)readManualBookmarksFromDataStore
936 [_manual_bookmarks autorelease];
937 _manual_bookmarks = [
self arrayFromDataStoreURL:[
self manualBookmarksDataStoreURL]];
939 if (_manual_bookmarks == nil)
941 _manual_bookmarks = [[NSMutableArray alloc] init];
945- (void)readConnectionHistoryFromDataStore
947 [_connection_history autorelease];
948 _connection_history = [
self arrayFromDataStoreURL:[
self connectionHistoryDataStoreURL]];
950 if (_connection_history == nil)
951 _connection_history = [[NSMutableArray alloc] init];
954- (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url
956 NSData *archived_data = [NSData dataWithContentsOfURL:url];
962 NSSet *classes = [NSSet setWithObjects:[NSArray class], [
ComputerBookmark class],
964 [NSString class], [NSNumber class], [NSData class], nil];
965 NSError *error = nil;
966 NSArray *decoded = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes
967 fromData:archived_data
972 NSLog(
@"%s: failed to unarchive %@: %@", __func__, [url lastPathComponent], error);
977 return [[NSMutableArray alloc] initWithArray:decoded];
980- (NSURL *)manualBookmarksDataStoreURL
983 fileURLWithPath:[NSString stringWithFormat:@"%@/%@",
984 [NSSearchPathForDirectoriesInDomains(
985 NSDocumentDirectory, NSUserDomainMask, YES)
987 @"com.freerdp.ifreerdp.bookmarks.plist"]];
990- (NSURL *)connectionHistoryDataStoreURL
993 fileURLWithPath:[NSString
994 stringWithFormat:@"%@/%@",
995 [NSSearchPathForDirectoriesInDomains(
996 NSDocumentDirectory, NSUserDomainMask, YES)
998 @"com.freerdp.ifreerdp.connection_history.plist"]];