Blob Blame History Raw
/*
 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