/* bookmarks and active session view controller Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import "BookmarkListController.h" #import "Utils.h" #import "BookmarkEditorController.h" #import "RDPSessionViewController.h" #import "Toast+UIView.h" #import "Reachability.h" #import "GlobalDefaults.h" #import "BlockAlertView.h" #define SECTION_SESSIONS 0 #define SECTION_BOOKMARKS 1 #define NUM_SECTIONS 2 @interface BookmarkListController (Private) #pragma mark misc functions - (UIButton *)disclosureButtonWithImage:(UIImage *)image; - (void)performSearch:(NSString *)searchText; #pragma mark Persisting bookmarks - (void)scheduleWriteBookmarksToDataStore; - (void)writeBookmarksToDataStore; - (void)scheduleWriteManualBookmarksToDataStore; - (void)writeManualBookmarksToDataStore; - (void)readManualBookmarksFromDataStore; - (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url; - (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url; - (NSURL *)manualBookmarksDataStoreURL; - (NSURL *)connectionHistoryDataStoreURL; @end @implementation BookmarkListController @synthesize searchBar = _searchBar, tableView = _tableView, bmTableCell = _bmTableCell, sessTableCell = _sessTableCell; // The designated initializer. Override if you create the controller programmatically and want to // perform customization that is not appropriate for viewDidLoad. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { // load bookmarks [self readManualBookmarksFromDataStore]; // load connection history [self readConnectionHistoryFromDataStore]; // init search result array _manual_search_result = nil; // register for session notifications [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionDisconnected:) name:TSXSessionDidDisconnectNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionFailedToConnect:) name:TSXSessionDidFailToConnectNotification object:nil]; // set title and tabbar controller image [self setTitle:NSLocalizedString(@"Connections", @"'Connections': bookmark controller title")]; [self setTabBarItem:[[[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemBookmarks tag:0] autorelease]]; // load images _star_on_img = [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"icon_accessory_star_on" ofType:@"png"]] retain]; _star_off_img = [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"icon_accessory_star_off" ofType:@"png"]] retain]; // init reachability detection [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil]; // init other properties _active_sessions = [[NSMutableArray alloc] init]; _temporary_bookmark = nil; } return self; } - (void)loadView { [super loadView]; } // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { [super viewDidLoad]; // set edit button to allow bookmark list editing [[self navigationItem] setRightBarButtonItem:[self editButtonItem]]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // in case we had a search - search again cause the bookmark searchable items could have changed if ([[_searchBar text] length] > 0) [self performSearch:[_searchBar text]]; // to reflect any bookmark changes - reload table [_tableView reloadData]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // clear any search [_searchBar setText:@""]; [_searchBar resignFirstResponder]; [self performSearch:@""]; } // Override to allow orientations other than the default portrait orientation. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return YES; } - (void)didReceiveMemoryWarning { // Releases the view if it doesn't have a superview. [super didReceiveMemoryWarning]; // Release any cached data, images, etc that aren't in use. } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [_temporary_bookmark release]; [_connection_history release]; [_active_sessions release]; [_manual_search_result release]; [_manual_bookmarks release]; [_star_on_img release]; [_star_off_img release]; [super dealloc]; } #pragma mark - #pragma mark Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return NUM_SECTIONS; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case SECTION_SESSIONS: return 0; break; case SECTION_BOOKMARKS: { // (+1 for Add Bookmark entry) if (_manual_search_result != nil) return ([_manual_search_result count] + [_history_search_result count] + 1); return ([_manual_bookmarks count] + 1); } break; default: break; } return 0; } - (UITableViewCell *)cellForGenericListEntry { static NSString *CellIdentifier = @"BookmarkListCell"; UITableViewCell *cell = [[self tableView] dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; [cell setAccessoryView:[self disclosureButtonWithImage:_star_off_img]]; } return cell; } - (BookmarkTableCell *)cellForBookmark { static NSString *BookmarkCellIdentifier = @"BookmarkCell"; BookmarkTableCell *cell = (BookmarkTableCell *)[[self tableView] dequeueReusableCellWithIdentifier:BookmarkCellIdentifier]; if (cell == nil) { [[NSBundle mainBundle] loadNibNamed:@"BookmarkTableViewCell" owner:self options:nil]; [_bmTableCell setAccessoryView:[self disclosureButtonWithImage:_star_on_img]]; cell = _bmTableCell; _bmTableCell = nil; } return cell; } // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { switch ([indexPath section]) { case SECTION_SESSIONS: { // get custom session cell static NSString *SessionCellIdentifier = @"SessionCell"; SessionTableCell *cell = (SessionTableCell *)[tableView dequeueReusableCellWithIdentifier:SessionCellIdentifier]; if (cell == nil) { [[NSBundle mainBundle] loadNibNamed:@"SessionTableViewCell" owner:self options:nil]; cell = _sessTableCell; _sessTableCell = nil; } // set cell data RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]]; [[cell title] setText:[session sessionName]]; [[cell server] setText:[[session params] StringForKey:@"hostname"]]; [[cell username] setText:[[session params] StringForKey:@"username"]]; [[cell screenshot] setImage:[session getScreenshotWithSize:[[cell screenshot] bounds].size]]; [[cell disconnectButton] setTag:[indexPath row]]; return cell; } case SECTION_BOOKMARKS: { // special handling for first cell - quick connect/quick create Bookmark cell if ([indexPath row] == 0) { // if a search text is entered the cell becomes a quick connect/quick create // bookmark cell - otherwise it's just an add bookmark cell UITableViewCell *cell = [self cellForGenericListEntry]; if ([[_searchBar text] length] == 0) { [[cell textLabel] setText:[@" " stringByAppendingString: NSLocalizedString(@"Add Connection", @"'Add Connection': button label")]]; [((UIButton *)[cell accessoryView]) setHidden:YES]; } else { [[cell textLabel] setText:[@" " stringByAppendingString:[_searchBar text]]]; [((UIButton *)[cell accessoryView]) setHidden:NO]; } return cell; } else { // do we have a history cell or bookmark cell? if ([self isIndexPathToHistoryItem:indexPath]) { UITableViewCell *cell = [self cellForGenericListEntry]; [[cell textLabel] setText:[@" " stringByAppendingString: [_history_search_result objectAtIndex: [self historyIndexFromIndexPath:indexPath]]]]; [((UIButton *)[cell accessoryView]) setHidden:NO]; return cell; } else { // set cell properties ComputerBookmark *entry; BookmarkTableCell *cell = [self cellForBookmark]; if (_manual_search_result == nil) entry = [_manual_bookmarks objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]; else entry = [[_manual_search_result objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]] valueForKey:@"bookmark"]; [[cell title] setText:[entry label]]; [[cell subTitle] setText:[[entry params] StringForKey:@"hostname"]]; return cell; } } } default: break; } NSAssert(0, @"Failed to create cell"); return nil; } // Override to support conditional editing of the table view. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { // dont allow to edit Add Bookmark item if ([indexPath section] == SECTION_SESSIONS) return NO; if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0) return NO; return YES; } // Override to support editing the table view. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // Delete the row from the data source switch ([indexPath section]) { case SECTION_BOOKMARKS: { if (_manual_search_result == nil) [_manual_bookmarks removeObjectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]; else { // history item or bookmark? if ([self isIndexPathToHistoryItem:indexPath]) { [_connection_history removeObject: [_history_search_result objectAtIndex:[self historyIndexFromIndexPath:indexPath]]]; [_history_search_result removeObjectAtIndex:[self historyIndexFromIndexPath:indexPath]]; } else { [_manual_bookmarks removeObject: [[_manual_search_result objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]] valueForKey:@"bookmark"]]; [_manual_search_result removeObjectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]; } } [self scheduleWriteManualBookmarksToDataStore]; break; } } [tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone]; } } // Override to support rearranging the table view. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { if ([fromIndexPath compare:toIndexPath] != NSOrderedSame) { switch ([fromIndexPath section]) { case SECTION_BOOKMARKS: { int fromIdx = [self bookmarkIndexFromIndexPath:fromIndexPath]; int toIdx = [self bookmarkIndexFromIndexPath:toIndexPath]; ComputerBookmark *temp_bookmark = [[_manual_bookmarks objectAtIndex:fromIdx] retain]; [_manual_bookmarks removeObjectAtIndex:fromIdx]; if (toIdx >= [_manual_bookmarks count]) [_manual_bookmarks addObject:temp_bookmark]; else [_manual_bookmarks insertObject:temp_bookmark atIndex:toIdx]; [temp_bookmark release]; [self scheduleWriteManualBookmarksToDataStore]; break; } } } } // prevent that an item is moved befoer the Add Bookmark item - (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath { // don't allow to move: // - items between sections // - the quick connect/quick create bookmark cell // - any item while a search is applied if ([proposedDestinationIndexPath row] == 0 || ([sourceIndexPath section] != [proposedDestinationIndexPath section]) || _manual_search_result != nil) { return sourceIndexPath; } else { return proposedDestinationIndexPath; } } // Override to support conditional rearranging of the table view. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { // dont allow to reorder Add Bookmark item if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0) return NO; return YES; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (section == SECTION_SESSIONS && [_active_sessions count] > 0) return NSLocalizedString(@"My Sessions", @"'My Session': section sessions header"); if (section == SECTION_BOOKMARKS) return NSLocalizedString(@"Manual Connections", @"'Manual Connections': section manual bookmarks header"); return nil; } - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { return nil; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if ([indexPath section] == SECTION_SESSIONS) return 72; return [tableView rowHeight]; } #pragma mark - #pragma mark Table view delegate - (void)setEditing:(BOOL)editing animated:(BOOL)animated { [super setEditing:editing animated:animated]; [[self tableView] setEditing:editing animated:animated]; } - (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event { // forward a tap on our custom accessory button to the real accessory button handler NSIndexPath *indexPath = [[self tableView] indexPathForRowAtPoint:[[[event touchesForView:button] anyObject] locationInView:[self tableView]]]; if (indexPath == nil) return; [[[self tableView] delegate] tableView:[self tableView] accessoryButtonTappedForRowWithIndexPath:indexPath]; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if ([indexPath section] == SECTION_SESSIONS) { // resume session RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]]; UIViewController *ctrl = [[[RDPSessionViewController alloc] initWithNibName:@"RDPSessionView" bundle:nil session:session] autorelease]; [ctrl setHidesBottomBarWhenPushed:YES]; [[self navigationController] pushViewController:ctrl animated:YES]; } else { ComputerBookmark *bookmark = nil; if ([indexPath section] == SECTION_BOOKMARKS) { // first row has either quick connect or add bookmark item if ([indexPath row] == 0) { if ([[_searchBar text] length] == 0) { // show add bookmark controller ComputerBookmark *bookmark = [[[ComputerBookmark alloc] initWithBaseDefaultParameters] autorelease]; BookmarkEditorController *bookmarkEditorController = [[[BookmarkEditorController alloc] initWithBookmark:bookmark] autorelease]; [bookmarkEditorController setTitle:NSLocalizedString(@"Add Connection", @"Add Connection title")]; [bookmarkEditorController setDelegate:self]; [bookmarkEditorController setHidesBottomBarWhenPushed:YES]; [[self navigationController] pushViewController:bookmarkEditorController animated:YES]; } else { // create a quick connect bookmark and add an entry to the quick connect history // (if not already in the history) bookmark = [self bookmarkForQuickConnectTo:[_searchBar text]]; if (![_connection_history containsObject:[_searchBar text]]) { [_connection_history addObject:[_searchBar text]]; [self scheduleWriteConnectionHistoryToDataStore]; } } } else { if (_manual_search_result != nil) { if ([self isIndexPathToHistoryItem:indexPath]) { // create a quick connect bookmark for a history item NSString *item = [_history_search_result objectAtIndex:[self historyIndexFromIndexPath:indexPath]]; bookmark = [self bookmarkForQuickConnectTo:item]; } else bookmark = [[_manual_search_result objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]] valueForKey:@"bookmark"]; } else bookmark = [_manual_bookmarks objectAtIndex:[self bookmarkIndexFromIndexPath: indexPath]]; // -1 because of ADD BOOKMARK entry } // set reachability status WakeUpWWAN(); [bookmark setConntectedViaWLAN:[[Reachability reachabilityWithHostName:[[bookmark params] StringForKey:@"hostname"]] currentReachabilityStatus] == ReachableViaWiFi]; } if (bookmark != nil) { // create rdp session RDPSession *session = [[[RDPSession alloc] initWithBookmark:bookmark] autorelease]; UIViewController *ctrl = [[[RDPSessionViewController alloc] initWithNibName:@"RDPSessionView" bundle:nil session:session] autorelease]; [ctrl setHidesBottomBarWhenPushed:YES]; [[self navigationController] pushViewController:ctrl animated:YES]; [_active_sessions addObject:session]; } } } - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { // get the bookmark NSString *bookmark_editor_title = NSLocalizedString(@"Edit Connection", @"Edit Connection title"); ComputerBookmark *bookmark = nil; if ([indexPath section] == SECTION_BOOKMARKS) { if ([indexPath row] == 0) { // create a new bookmark and init hostname and label bookmark = [self bookmarkForQuickConnectTo:[_searchBar text]]; bookmark_editor_title = NSLocalizedString(@"Add Connection", @"Add Connection title"); } else { if (_manual_search_result != nil) { if ([self isIndexPathToHistoryItem:indexPath]) { // create a new bookmark and init hostname and label NSString *item = [_history_search_result objectAtIndex:[self historyIndexFromIndexPath:indexPath]]; bookmark = [self bookmarkForQuickConnectTo:item]; bookmark_editor_title = NSLocalizedString(@"Add Connection", @"Add Connection title"); } else bookmark = [[_manual_search_result objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]] valueForKey:@"bookmark"]; } else bookmark = [_manual_bookmarks objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]; // -1 because of ADD // BOOKMARK entry } } // bookmark found? - start the editor if (bookmark != nil) { BookmarkEditorController *editBookmarkController = [[[BookmarkEditorController alloc] initWithBookmark:bookmark] autorelease]; [editBookmarkController setHidesBottomBarWhenPushed:YES]; [editBookmarkController setTitle:bookmark_editor_title]; [editBookmarkController setDelegate:self]; [[self navigationController] pushViewController:editBookmarkController animated:YES]; } } #pragma mark - #pragma mark Search Bar Delegates - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar { // show cancel button [searchBar setShowsCancelButton:YES animated:YES]; return YES; } - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { // clear search result [_manual_search_result release]; _manual_search_result = nil; // clear text and remove cancel button [searchBar setText:@""]; [searchBar resignFirstResponder]; } - (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar { [searchBar setShowsCancelButton:NO animated:YES]; // re-enable table selection [_tableView setAllowsSelection:YES]; return YES; } - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { [_searchBar resignFirstResponder]; } - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText { [self performSearch:searchText]; [_tableView reloadData]; } #pragma mark - Session handling // session was added - (void)sessionDisconnected:(NSNotification *)notification { // remove session from active sessions RDPSession *session = (RDPSession *)[notification object]; [_active_sessions removeObject:session]; // if this view is currently active refresh entries if ([[self navigationController] visibleViewController] == self) [_tableView reloadSections:[NSIndexSet indexSetWithIndex:SECTION_SESSIONS] withRowAnimation:UITableViewRowAnimationNone]; // if session's bookmark is not in the bookmark list ask the user if he wants to add it // (this happens if the session is created using the quick connect feature) if (![_manual_bookmarks containsObject:[session bookmark]]) { // retain the bookmark in case we want to save it later _temporary_bookmark = [[session bookmark] retain]; // ask the user if he wants to save the bookmark NSString *title = NSLocalizedString(@"Save Connection Settings?", @"Save connection settings title"); NSString *message = NSLocalizedString( @"Your Connection Settings have not been saved. Do you want to save them?", @"Save connection settings message"); BlockAlertView *alert = [BlockAlertView alertWithTitle:title message:message]; [alert setCancelButtonWithTitle:NSLocalizedString(@"No", @"No Button") block:nil]; [alert addButtonWithTitle:NSLocalizedString(@"Yes", @"Yes Button") block:^{ if (_temporary_bookmark) { [_manual_bookmarks addObject:_temporary_bookmark]; [_tableView reloadSections:[NSIndexSet indexSetWithIndex:SECTION_BOOKMARKS] withRowAnimation:UITableViewRowAnimationNone]; [_temporary_bookmark autorelease]; _temporary_bookmark = nil; } }]; [alert show]; } } - (void)sessionFailedToConnect:(NSNotification *)notification { // remove session from active sessions RDPSession *session = (RDPSession *)[notification object]; [_active_sessions removeObject:session]; // display error toast [[self view] makeToast:NSLocalizedString(@"Failed to connect to session!", @"Failed to connect error message") duration:ToastDurationNormal position:@"center"]; } #pragma mark - Reachability notification - (void)reachabilityChanged:(NSNotification *)notification { // no matter how the network changed - we will disconnect // disconnect session (if there is any) if ([_active_sessions count] > 0) { RDPSession *session = [_active_sessions objectAtIndex:0]; [session disconnect]; } } #pragma mark - BookmarkEditorController delegate - (void)commitBookmark:(ComputerBookmark *)bookmark { // if we got a manual bookmark that is not in the list yet - add it otherwise replace it BOOL found = NO; for (int idx = 0; idx < [_manual_bookmarks count]; ++idx) { if ([[bookmark uuid] isEqualToString:[[_manual_bookmarks objectAtIndex:idx] uuid]]) { [_manual_bookmarks replaceObjectAtIndex:idx withObject:bookmark]; found = YES; break; } } if (!found) [_manual_bookmarks addObject:bookmark]; // remove any quick connect history entry with the same hostname NSString *hostname = [[bookmark params] StringForKey:@"hostname"]; if ([_connection_history containsObject:hostname]) { [_connection_history removeObject:hostname]; [self scheduleWriteConnectionHistoryToDataStore]; } [self scheduleWriteManualBookmarksToDataStore]; } - (IBAction)disconnectButtonPressed:(id)sender { // disconnect session and refresh table view RDPSession *session = [_active_sessions objectAtIndex:[sender tag]]; [session disconnect]; } #pragma mark - Misc functions - (BOOL)hasNoBookmarks { return ([_manual_bookmarks count] == 0); } - (UIButton *)disclosureButtonWithImage:(UIImage *)image { // we make the button a little bit bigger (image widht * 2, height + 10) so that the user // doesn't accidentally connect to the bookmark ... UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button setFrame:CGRectMake(0, 0, [image size].width * 2, [image size].height + 10)]; [button setImage:image forState:UIControlStateNormal]; [button addTarget:self action:@selector(accessoryButtonTapped:withEvent:) forControlEvents:UIControlEventTouchUpInside]; [button setUserInteractionEnabled:YES]; return button; } - (void)performSearch:(NSString *)searchText { [_manual_search_result autorelease]; if ([searchText length] > 0) { _manual_search_result = [FilterBookmarks( _manual_bookmarks, [searchText componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) retain]; _history_search_result = [FilterHistory(_connection_history, searchText) retain]; } else { _history_search_result = nil; _manual_search_result = nil; } } - (int)bookmarkIndexFromIndexPath:(NSIndexPath *)indexPath { return [indexPath row] - ((_history_search_result != nil) ? [_history_search_result count] : 0) - 1; } - (int)historyIndexFromIndexPath:(NSIndexPath *)indexPath { return [indexPath row] - 1; } - (BOOL)isIndexPathToHistoryItem:(NSIndexPath *)indexPath { return (([indexPath row] - 1) < [_history_search_result count]); } - (ComputerBookmark *)bookmarkForQuickConnectTo:(NSString *)host { ComputerBookmark *bookmark = [[[ComputerBookmark alloc] initWithBaseDefaultParameters] autorelease]; [bookmark setLabel:host]; [[bookmark params] setValue:host forKey:@"hostname"]; return bookmark; } #pragma mark - Persisting bookmarks - (void)scheduleWriteBookmarksToDataStore { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self writeBookmarksToDataStore]; }]; } - (void)writeBookmarksToDataStore { [self writeManualBookmarksToDataStore]; } - (void)scheduleWriteManualBookmarksToDataStore { [[NSOperationQueue mainQueue] addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(writeManualBookmarksToDataStore) object:nil] autorelease]]; } - (void)writeManualBookmarksToDataStore { [self writeArray:_manual_bookmarks toDataStoreURL:[self manualBookmarksDataStoreURL]]; } - (void)scheduleWriteConnectionHistoryToDataStore { [[NSOperationQueue mainQueue] addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(writeConnectionHistoryToDataStore) object:nil] autorelease]]; } - (void)writeConnectionHistoryToDataStore { [self writeArray:_connection_history toDataStoreURL:[self connectionHistoryDataStoreURL]]; } - (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url { NSData *archived_data = [NSKeyedArchiver archivedDataWithRootObject:bookmarks]; [archived_data writeToURL:url atomically:YES]; } - (void)readManualBookmarksFromDataStore { [_manual_bookmarks autorelease]; _manual_bookmarks = [self arrayFromDataStoreURL:[self manualBookmarksDataStoreURL]]; if (_manual_bookmarks == nil) { _manual_bookmarks = [[NSMutableArray alloc] init]; [_manual_bookmarks addObject:[[[GlobalDefaults sharedGlobalDefaults] newTestServerBookmark] autorelease]]; } } - (void)readConnectionHistoryFromDataStore { [_connection_history autorelease]; _connection_history = [self arrayFromDataStoreURL:[self connectionHistoryDataStoreURL]]; if (_connection_history == nil) _connection_history = [[NSMutableArray alloc] init]; } - (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url { NSData *archived_data = [NSData dataWithContentsOfURL:url]; if (!archived_data) return nil; return [[NSKeyedUnarchiver unarchiveObjectWithData:archived_data] retain]; } - (NSURL *)manualBookmarksDataStoreURL { return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject], @"com.freerdp.ifreerdp.bookmarks.plist"]]; } - (NSURL *)connectionHistoryDataStoreURL { return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject], @"com.freerdp.ifreerdp.connection_history.plist"]]; } @end