FreeRDP
BookmarkListController.m
1 /*
2  bookmarks and active session view controller
3 
4  Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5 
6  This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7  If a copy of the MPL was not distributed with this file, You can obtain one at
8  http://mozilla.org/MPL/2.0/.
9  */
10 
11 #import "BookmarkListController.h"
12 #import "Utils.h"
13 #import "BookmarkEditorController.h"
14 #import "RDPSessionViewController.h"
15 #import "Toast+UIView.h"
16 #import "Reachability.h"
17 #import "GlobalDefaults.h"
18 #import "BlockAlertView.h"
19 
20 #define SECTION_SESSIONS 0
21 #define SECTION_BOOKMARKS 1
22 #define NUM_SECTIONS 2
23 
24 @interface BookmarkListController (Private)
25 #pragma mark misc functions
26 - (UIButton *)disclosureButtonWithImage:(UIImage *)image;
27 - (void)performSearch:(NSString *)searchText;
28 #pragma mark Persisting bookmarks
29 - (void)scheduleWriteBookmarksToDataStore;
30 - (void)writeBookmarksToDataStore;
31 - (void)scheduleWriteManualBookmarksToDataStore;
32 - (void)writeManualBookmarksToDataStore;
33 - (void)readManualBookmarksFromDataStore;
34 - (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url;
35 - (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url;
36 - (NSURL *)manualBookmarksDataStoreURL;
37 - (NSURL *)connectionHistoryDataStoreURL;
38 @end
39 
40 @implementation BookmarkListController
41 
42 @synthesize searchBar = _searchBar, tableView = _tableView, bmTableCell = _bmTableCell,
43  sessTableCell = _sessTableCell;
44 
45 // The designated initializer. Override if you create the controller programmatically and want to
46 // perform customization that is not appropriate for viewDidLoad.
47 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
48 {
49  if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
50  {
51  // load bookmarks
52  [self readManualBookmarksFromDataStore];
53 
54  // load connection history
55  [self readConnectionHistoryFromDataStore];
56 
57  // init search result array
58  _manual_search_result = nil;
59 
60  // register for session notifications
61  [[NSNotificationCenter defaultCenter] addObserver:self
62  selector:@selector(sessionDisconnected:)
63  name:TSXSessionDidDisconnectNotification
64  object:nil];
65  [[NSNotificationCenter defaultCenter] addObserver:self
66  selector:@selector(sessionFailedToConnect:)
67  name:TSXSessionDidFailToConnectNotification
68  object:nil];
69 
70  // set title and tabbar controller image
71  [self setTitle:NSLocalizedString(@"Connections",
72  @"'Connections': bookmark controller title")];
73  [self setTabBarItem:[[[UITabBarItem alloc]
74  initWithTabBarSystemItem:UITabBarSystemItemBookmarks
75  tag:0] autorelease]];
76 
77  // load images
78  _star_on_img = [[UIImage
79  imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"icon_accessory_star_on"
80  ofType:@"png"]] retain];
81  _star_off_img =
82  [[UIImage imageWithContentsOfFile:[[NSBundle mainBundle]
83  pathForResource:@"icon_accessory_star_off"
84  ofType:@"png"]] retain];
85 
86  // init reachability detection
87  [[NSNotificationCenter defaultCenter] addObserver:self
88  selector:@selector(reachabilityChanged:)
89  name:kReachabilityChangedNotification
90  object:nil];
91 
92  // init other properties
93  _active_sessions = [[NSMutableArray alloc] init];
94  _temporary_bookmark = nil;
95  }
96  return self;
97 }
98 
99 - (void)loadView
100 {
101  [super loadView];
102 }
103 
104 // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
105 - (void)viewDidLoad
106 {
107  [super viewDidLoad];
108 
109  // set edit button to allow bookmark list editing
110  [[self navigationItem] setRightBarButtonItem:[self editButtonItem]];
111 }
112 
113 - (void)viewWillAppear:(BOOL)animated
114 {
115  [super viewWillAppear:animated];
116 
117  // in case we had a search - search again cause the bookmark searchable items could have changed
118  if ([[_searchBar text] length] > 0)
119  [self performSearch:[_searchBar text]];
120 
121  // to reflect any bookmark changes - reload table
122  [_tableView reloadData];
123 }
124 
125 - (void)viewWillDisappear:(BOOL)animated
126 {
127  [super viewWillDisappear:animated];
128 
129  // clear any search
130  [_searchBar setText:@""];
131  [_searchBar resignFirstResponder];
132  [self performSearch:@""];
133 }
134 
135 // Override to allow orientations other than the default portrait orientation.
136 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
137 {
138  // Return YES for supported orientations
139  return YES;
140 }
141 
142 - (void)didReceiveMemoryWarning
143 {
144  // Releases the view if it doesn't have a superview.
145  [super didReceiveMemoryWarning];
146 
147  // Release any cached data, images, etc that aren't in use.
148 }
149 
150 - (void)viewDidUnload
151 {
152  [super viewDidUnload];
153  // Release any retained subviews of the main view.
154  // e.g. self.myOutlet = nil;
155 }
156 
157 - (void)dealloc
158 {
159  [[NSNotificationCenter defaultCenter] removeObserver:self];
160 
161  [_temporary_bookmark release];
162  [_connection_history release];
163  [_active_sessions release];
164  [_manual_search_result release];
165  [_manual_bookmarks release];
166 
167  [_star_on_img release];
168  [_star_off_img release];
169 
170  [super dealloc];
171 }
172 
173 #pragma mark -
174 #pragma mark Table view data source
175 
176 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
177 {
178  // Return the number of sections.
179  return NUM_SECTIONS;
180 }
181 
182 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
183 {
184 
185  switch (section)
186  {
187  case SECTION_SESSIONS:
188  return 0;
189  break;
190 
191  case SECTION_BOOKMARKS:
192  {
193  // (+1 for Add Bookmark entry)
194  if (_manual_search_result != nil)
195  return ([_manual_search_result count] + [_history_search_result count] + 1);
196  return ([_manual_bookmarks count] + 1);
197  }
198  break;
199 
200  default:
201  break;
202  }
203  return 0;
204 }
205 
206 - (UITableViewCell *)cellForGenericListEntry
207 {
208  static NSString *CellIdentifier = @"BookmarkListCell";
209  UITableViewCell *cell = [[self tableView] dequeueReusableCellWithIdentifier:CellIdentifier];
210  if (cell == nil)
211  {
212  cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
213  reuseIdentifier:CellIdentifier];
214  [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
215  [cell setAccessoryView:[self disclosureButtonWithImage:_star_off_img]];
216  }
217 
218  return cell;
219 }
220 
221 - (BookmarkTableCell *)cellForBookmark
222 {
223  static NSString *BookmarkCellIdentifier = @"BookmarkCell";
224  BookmarkTableCell *cell = (BookmarkTableCell *)[[self tableView]
225  dequeueReusableCellWithIdentifier:BookmarkCellIdentifier];
226  if (cell == nil)
227  {
228  [[NSBundle mainBundle] loadNibNamed:@"BookmarkTableViewCell" owner:self options:nil];
229  [_bmTableCell setAccessoryView:[self disclosureButtonWithImage:_star_on_img]];
230  cell = _bmTableCell;
231  _bmTableCell = nil;
232  }
233 
234  return cell;
235 }
236 
237 // Customize the appearance of table view cells.
238 - (UITableViewCell *)tableView:(UITableView *)tableView
239  cellForRowAtIndexPath:(NSIndexPath *)indexPath
240 {
241 
242  switch ([indexPath section])
243  {
244  case SECTION_SESSIONS:
245  {
246  // get custom session cell
247  static NSString *SessionCellIdentifier = @"SessionCell";
248  SessionTableCell *cell = (SessionTableCell *)[tableView
249  dequeueReusableCellWithIdentifier:SessionCellIdentifier];
250  if (cell == nil)
251  {
252  [[NSBundle mainBundle] loadNibNamed:@"SessionTableViewCell" owner:self options:nil];
253  cell = _sessTableCell;
254  _sessTableCell = nil;
255  }
256 
257  // set cell data
258  RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]];
259  [[cell title] setText:[session sessionName]];
260  [[cell server] setText:[[session params] StringForKey:@"hostname"]];
261  [[cell username] setText:[[session params] StringForKey:@"username"]];
262  [[cell screenshot]
263  setImage:[session getScreenshotWithSize:[[cell screenshot] bounds].size]];
264  [[cell disconnectButton] setTag:[indexPath row]];
265  return cell;
266  }
267 
268  case SECTION_BOOKMARKS:
269  {
270  // special handling for first cell - quick connect/quick create Bookmark cell
271  if ([indexPath row] == 0)
272  {
273  // if a search text is entered the cell becomes a quick connect/quick create
274  // bookmark cell - otherwise it's just an add bookmark cell
275  UITableViewCell *cell = [self cellForGenericListEntry];
276  if ([[_searchBar text] length] == 0)
277  {
278  [[cell textLabel]
279  setText:[@" " stringByAppendingString:
280  NSLocalizedString(@"Add Connection",
281  @"'Add Connection': button label")]];
282  [((UIButton *)[cell accessoryView]) setHidden:YES];
283  }
284  else
285  {
286  [[cell textLabel] setText:[@" " stringByAppendingString:[_searchBar text]]];
287  [((UIButton *)[cell accessoryView]) setHidden:NO];
288  }
289 
290  return cell;
291  }
292  else
293  {
294  // do we have a history cell or bookmark cell?
295  if ([self isIndexPathToHistoryItem:indexPath])
296  {
297  UITableViewCell *cell = [self cellForGenericListEntry];
298  [[cell textLabel]
299  setText:[@" " stringByAppendingString:
300  [_history_search_result
301  objectAtIndex:
302  [self historyIndexFromIndexPath:indexPath]]]];
303  [((UIButton *)[cell accessoryView]) setHidden:NO];
304  return cell;
305  }
306  else
307  {
308  // set cell properties
309  ComputerBookmark *entry;
310  BookmarkTableCell *cell = [self cellForBookmark];
311  if (_manual_search_result == nil)
312  entry = [_manual_bookmarks
313  objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]];
314  else
315  entry = [[_manual_search_result
316  objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]
317  valueForKey:@"bookmark"];
318 
319  [[cell title] setText:[entry label]];
320  [[cell subTitle] setText:[[entry params] StringForKey:@"hostname"]];
321  return cell;
322  }
323  }
324  }
325 
326  default:
327  break;
328  }
329 
330  NSAssert(0, @"Failed to create cell");
331  return nil;
332 }
333 
334 // Override to support conditional editing of the table view.
335 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
336 {
337  // dont allow to edit Add Bookmark item
338  if ([indexPath section] == SECTION_SESSIONS)
339  return NO;
340  if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0)
341  return NO;
342  return YES;
343 }
344 
345 // Override to support editing the table view.
346 - (void)tableView:(UITableView *)tableView
347  commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
348  forRowAtIndexPath:(NSIndexPath *)indexPath
349 {
350  if (editingStyle == UITableViewCellEditingStyleDelete)
351  {
352  // Delete the row from the data source
353  switch ([indexPath section])
354  {
355  case SECTION_BOOKMARKS:
356  {
357  if (_manual_search_result == nil)
358  [_manual_bookmarks
359  removeObjectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]];
360  else
361  {
362  // history item or bookmark?
363  if ([self isIndexPathToHistoryItem:indexPath])
364  {
365  [_connection_history
366  removeObject:
367  [_history_search_result
368  objectAtIndex:[self historyIndexFromIndexPath:indexPath]]];
369  [_history_search_result
370  removeObjectAtIndex:[self historyIndexFromIndexPath:indexPath]];
371  }
372  else
373  {
374  [_manual_bookmarks
375  removeObject:
376  [[_manual_search_result
377  objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]
378  valueForKey:@"bookmark"]];
379  [_manual_search_result
380  removeObjectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]];
381  }
382  }
383  [self scheduleWriteManualBookmarksToDataStore];
384  break;
385  }
386  }
387 
388  [tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]]
389  withRowAnimation:UITableViewRowAnimationNone];
390  }
391 }
392 
393 // Override to support rearranging the table view.
394 - (void)tableView:(UITableView *)tableView
395  moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
396  toIndexPath:(NSIndexPath *)toIndexPath
397 {
398  if ([fromIndexPath compare:toIndexPath] != NSOrderedSame)
399  {
400  switch ([fromIndexPath section])
401  {
402  case SECTION_BOOKMARKS:
403  {
404  int fromIdx = [self bookmarkIndexFromIndexPath:fromIndexPath];
405  int toIdx = [self bookmarkIndexFromIndexPath:toIndexPath];
406  ComputerBookmark *temp_bookmark =
407  [[_manual_bookmarks objectAtIndex:fromIdx] retain];
408  [_manual_bookmarks removeObjectAtIndex:fromIdx];
409  if (toIdx >= [_manual_bookmarks count])
410  [_manual_bookmarks addObject:temp_bookmark];
411  else
412  [_manual_bookmarks insertObject:temp_bookmark atIndex:toIdx];
413  [temp_bookmark release];
414 
415  [self scheduleWriteManualBookmarksToDataStore];
416  break;
417  }
418  }
419  }
420 }
421 
422 // prevent that an item is moved befoer the Add Bookmark item
423 - (NSIndexPath *)tableView:(UITableView *)tableView
424  targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
425  toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
426 {
427  // don't allow to move:
428  // - items between sections
429  // - the quick connect/quick create bookmark cell
430  // - any item while a search is applied
431  if ([proposedDestinationIndexPath row] == 0 ||
432  ([sourceIndexPath section] != [proposedDestinationIndexPath section]) ||
433  _manual_search_result != nil)
434  {
435  return sourceIndexPath;
436  }
437  else
438  {
439  return proposedDestinationIndexPath;
440  }
441 }
442 
443 // Override to support conditional rearranging of the table view.
444 - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
445 {
446  // dont allow to reorder Add Bookmark item
447  if ([indexPath section] == SECTION_BOOKMARKS && [indexPath row] == 0)
448  return NO;
449  return YES;
450 }
451 
452 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
453 {
454  if (section == SECTION_SESSIONS && [_active_sessions count] > 0)
455  return NSLocalizedString(@"My Sessions", @"'My Session': section sessions header");
456  if (section == SECTION_BOOKMARKS)
457  return NSLocalizedString(@"Manual Connections",
458  @"'Manual Connections': section manual bookmarks header");
459  return nil;
460 }
461 
462 - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
463 {
464  return nil;
465 }
466 
467 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
468 {
469  if ([indexPath section] == SECTION_SESSIONS)
470  return 72;
471  return [tableView rowHeight];
472 }
473 
474 #pragma mark -
475 #pragma mark Table view delegate
476 
477 - (void)setEditing:(BOOL)editing animated:(BOOL)animated
478 {
479  [super setEditing:editing animated:animated];
480  [[self tableView] setEditing:editing animated:animated];
481 }
482 
483 - (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
484 {
485  // forward a tap on our custom accessory button to the real accessory button handler
486  NSIndexPath *indexPath =
487  [[self tableView] indexPathForRowAtPoint:[[[event touchesForView:button] anyObject]
488  locationInView:[self tableView]]];
489  if (indexPath == nil)
490  return;
491 
492  [[[self tableView] delegate] tableView:[self tableView]
493  accessoryButtonTappedForRowWithIndexPath:indexPath];
494 }
495 
496 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
497 {
498  if ([indexPath section] == SECTION_SESSIONS)
499  {
500  // resume session
501  RDPSession *session = [_active_sessions objectAtIndex:[indexPath row]];
502  UIViewController *ctrl =
503  [[[RDPSessionViewController alloc] initWithNibName:@"RDPSessionView"
504  bundle:nil
505  session:session] autorelease];
506  [ctrl setHidesBottomBarWhenPushed:YES];
507  [[self navigationController] pushViewController:ctrl animated:YES];
508  }
509  else
510  {
511  ComputerBookmark *bookmark = nil;
512  if ([indexPath section] == SECTION_BOOKMARKS)
513  {
514  // first row has either quick connect or add bookmark item
515  if ([indexPath row] == 0)
516  {
517  if ([[_searchBar text] length] == 0)
518  {
519  // show add bookmark controller
520  ComputerBookmark *bookmark =
521  [[[ComputerBookmark alloc] initWithBaseDefaultParameters] autorelease];
522  BookmarkEditorController *bookmarkEditorController =
523  [[[BookmarkEditorController alloc] initWithBookmark:bookmark] autorelease];
524  [bookmarkEditorController
525  setTitle:NSLocalizedString(@"Add Connection", @"Add Connection title")];
526  [bookmarkEditorController setDelegate:self];
527  [bookmarkEditorController setHidesBottomBarWhenPushed:YES];
528  [[self navigationController] pushViewController:bookmarkEditorController
529  animated:YES];
530  }
531  else
532  {
533  // create a quick connect bookmark and add an entry to the quick connect history
534  // (if not already in the history)
535  bookmark = [self bookmarkForQuickConnectTo:[_searchBar text]];
536  if (![_connection_history containsObject:[_searchBar text]])
537  {
538  [_connection_history addObject:[_searchBar text]];
539  [self scheduleWriteConnectionHistoryToDataStore];
540  }
541  }
542  }
543  else
544  {
545  if (_manual_search_result != nil)
546  {
547  if ([self isIndexPathToHistoryItem:indexPath])
548  {
549  // create a quick connect bookmark for a history item
550  NSString *item = [_history_search_result
551  objectAtIndex:[self historyIndexFromIndexPath:indexPath]];
552  bookmark = [self bookmarkForQuickConnectTo:item];
553  }
554  else
555  bookmark = [[_manual_search_result
556  objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]
557  valueForKey:@"bookmark"];
558  }
559  else
560  bookmark = [_manual_bookmarks
561  objectAtIndex:[self bookmarkIndexFromIndexPath:
562  indexPath]]; // -1 because of ADD BOOKMARK entry
563  }
564 
565  // set reachability status
566  WakeUpWWAN();
567  [bookmark
568  setConntectedViaWLAN:[[Reachability
569  reachabilityWithHostName:[[bookmark params]
570  StringForKey:@"hostname"]]
571  currentReachabilityStatus] == ReachableViaWiFi];
572  }
573 
574  if (bookmark != nil)
575  {
576  // create rdp session
577  RDPSession *session = [[[RDPSession alloc] initWithBookmark:bookmark] autorelease];
578  UIViewController *ctrl =
579  [[[RDPSessionViewController alloc] initWithNibName:@"RDPSessionView"
580  bundle:nil
581  session:session] autorelease];
582  [ctrl setHidesBottomBarWhenPushed:YES];
583  [[self navigationController] pushViewController:ctrl animated:YES];
584  [_active_sessions addObject:session];
585  }
586  }
587 }
588 
589 - (void)tableView:(UITableView *)tableView
590  accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
591 {
592  // get the bookmark
593  NSString *bookmark_editor_title =
594  NSLocalizedString(@"Edit Connection", @"Edit Connection title");
595  ComputerBookmark *bookmark = nil;
596  if ([indexPath section] == SECTION_BOOKMARKS)
597  {
598  if ([indexPath row] == 0)
599  {
600  // create a new bookmark and init hostname and label
601  bookmark = [self bookmarkForQuickConnectTo:[_searchBar text]];
602  bookmark_editor_title = NSLocalizedString(@"Add Connection", @"Add Connection title");
603  }
604  else
605  {
606  if (_manual_search_result != nil)
607  {
608  if ([self isIndexPathToHistoryItem:indexPath])
609  {
610  // create a new bookmark and init hostname and label
611  NSString *item = [_history_search_result
612  objectAtIndex:[self historyIndexFromIndexPath:indexPath]];
613  bookmark = [self bookmarkForQuickConnectTo:item];
614  bookmark_editor_title =
615  NSLocalizedString(@"Add Connection", @"Add Connection title");
616  }
617  else
618  bookmark = [[_manual_search_result
619  objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]
620  valueForKey:@"bookmark"];
621  }
622  else
623  bookmark = [_manual_bookmarks
624  objectAtIndex:[self bookmarkIndexFromIndexPath:indexPath]]; // -1 because of ADD
625  // BOOKMARK entry
626  }
627  }
628 
629  // bookmark found? - start the editor
630  if (bookmark != nil)
631  {
632  BookmarkEditorController *editBookmarkController =
633  [[[BookmarkEditorController alloc] initWithBookmark:bookmark] autorelease];
634  [editBookmarkController setHidesBottomBarWhenPushed:YES];
635  [editBookmarkController setTitle:bookmark_editor_title];
636  [editBookmarkController setDelegate:self];
637  [[self navigationController] pushViewController:editBookmarkController animated:YES];
638  }
639 }
640 
641 #pragma mark -
642 #pragma mark Search Bar Delegates
643 
644 - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
645 {
646  // show cancel button
647  [searchBar setShowsCancelButton:YES animated:YES];
648  return YES;
649 }
650 
651 - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
652 {
653  // clear search result
654  [_manual_search_result release];
655  _manual_search_result = nil;
656 
657  // clear text and remove cancel button
658  [searchBar setText:@""];
659  [searchBar resignFirstResponder];
660 }
661 
662 - (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
663 {
664  [searchBar setShowsCancelButton:NO animated:YES];
665 
666  // re-enable table selection
667  [_tableView setAllowsSelection:YES];
668 
669  return YES;
670 }
671 
672 - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
673 {
674  [_searchBar resignFirstResponder];
675 }
676 
677 - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
678 {
679  [self performSearch:searchText];
680  [_tableView reloadData];
681 }
682 
683 #pragma mark - Session handling
684 
685 // session was added
686 - (void)sessionDisconnected:(NSNotification *)notification
687 {
688  // remove session from active sessions
689  RDPSession *session = (RDPSession *)[notification object];
690  [_active_sessions removeObject:session];
691 
692  // if this view is currently active refresh entries
693  if ([[self navigationController] visibleViewController] == self)
694  [_tableView reloadSections:[NSIndexSet indexSetWithIndex:SECTION_SESSIONS]
695  withRowAnimation:UITableViewRowAnimationNone];
696 
697  // if session's bookmark is not in the bookmark list ask the user if he wants to add it
698  // (this happens if the session is created using the quick connect feature)
699  if (![_manual_bookmarks containsObject:[session bookmark]])
700  {
701  // retain the bookmark in case we want to save it later
702  _temporary_bookmark = [[session bookmark] retain];
703 
704  // ask the user if he wants to save the bookmark
705  NSString *title =
706  NSLocalizedString(@"Save Connection Settings?", @"Save connection settings title");
707  NSString *message = NSLocalizedString(
708  @"Your Connection Settings have not been saved. Do you want to save them?",
709  @"Save connection settings message");
710  BlockAlertView *alert = [BlockAlertView alertWithTitle:title message:message];
711  [alert setCancelButtonWithTitle:NSLocalizedString(@"No", @"No Button") block:nil];
712  [alert addButtonWithTitle:NSLocalizedString(@"Yes", @"Yes Button")
713  block:^{
714  if (_temporary_bookmark)
715  {
716  [_manual_bookmarks addObject:_temporary_bookmark];
717  [_tableView
718  reloadSections:[NSIndexSet
719  indexSetWithIndex:SECTION_BOOKMARKS]
720  withRowAnimation:UITableViewRowAnimationNone];
721  [_temporary_bookmark autorelease];
722  _temporary_bookmark = nil;
723  }
724  }];
725  [alert show];
726  }
727 }
728 
729 - (void)sessionFailedToConnect:(NSNotification *)notification
730 {
731  // remove session from active sessions
732  RDPSession *session = (RDPSession *)[notification object];
733  [_active_sessions removeObject:session];
734 
735  // display error toast
736  [[self view] makeToast:NSLocalizedString(@"Failed to connect to session!",
737  @"Failed to connect error message")
738  duration:ToastDurationNormal
739  position:@"center"];
740 }
741 
742 #pragma mark - Reachability notification
743 - (void)reachabilityChanged:(NSNotification *)notification
744 {
745  // no matter how the network changed - we will disconnect
746  // disconnect session (if there is any)
747  if ([_active_sessions count] > 0)
748  {
749  RDPSession *session = [_active_sessions objectAtIndex:0];
750  [session disconnect];
751  }
752 }
753 
754 #pragma mark - BookmarkEditorController delegate
755 
756 - (void)commitBookmark:(ComputerBookmark *)bookmark
757 {
758  // if we got a manual bookmark that is not in the list yet - add it otherwise replace it
759  BOOL found = NO;
760  for (int idx = 0; idx < [_manual_bookmarks count]; ++idx)
761  {
762  if ([[bookmark uuid] isEqualToString:[[_manual_bookmarks objectAtIndex:idx] uuid]])
763  {
764  [_manual_bookmarks replaceObjectAtIndex:idx withObject:bookmark];
765  found = YES;
766  break;
767  }
768  }
769  if (!found)
770  [_manual_bookmarks addObject:bookmark];
771 
772  // remove any quick connect history entry with the same hostname
773  NSString *hostname = [[bookmark params] StringForKey:@"hostname"];
774  if ([_connection_history containsObject:hostname])
775  {
776  [_connection_history removeObject:hostname];
777  [self scheduleWriteConnectionHistoryToDataStore];
778  }
779 
780  [self scheduleWriteManualBookmarksToDataStore];
781 }
782 
783 - (IBAction)disconnectButtonPressed:(id)sender
784 {
785  // disconnect session and refresh table view
786  RDPSession *session = [_active_sessions objectAtIndex:[sender tag]];
787  [session disconnect];
788 }
789 
790 #pragma mark - Misc functions
791 
792 - (BOOL)hasNoBookmarks
793 {
794  return ([_manual_bookmarks count] == 0);
795 }
796 
797 - (UIButton *)disclosureButtonWithImage:(UIImage *)image
798 {
799  // we make the button a little bit bigger (image widht * 2, height + 10) so that the user
800  // doesn't accidentally connect to the bookmark ...
801  UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
802  [button setFrame:CGRectMake(0, 0, [image size].width * 2, [image size].height + 10)];
803  [button setImage:image forState:UIControlStateNormal];
804  [button addTarget:self
805  action:@selector(accessoryButtonTapped:withEvent:)
806  forControlEvents:UIControlEventTouchUpInside];
807  [button setUserInteractionEnabled:YES];
808  return button;
809 }
810 
811 - (void)performSearch:(NSString *)searchText
812 {
813  [_manual_search_result autorelease];
814 
815  if ([searchText length] > 0)
816  {
817  _manual_search_result = [FilterBookmarks(
818  _manual_bookmarks,
819  [searchText componentsSeparatedByCharactersInSet:[NSCharacterSet
820  whitespaceAndNewlineCharacterSet]])
821  retain];
822  _history_search_result = [FilterHistory(_connection_history, searchText) retain];
823  }
824  else
825  {
826  _history_search_result = nil;
827  _manual_search_result = nil;
828  }
829 }
830 
831 - (int)bookmarkIndexFromIndexPath:(NSIndexPath *)indexPath
832 {
833  return [indexPath row] -
834  ((_history_search_result != nil) ? [_history_search_result count] : 0) - 1;
835 }
836 
837 - (int)historyIndexFromIndexPath:(NSIndexPath *)indexPath
838 {
839  return [indexPath row] - 1;
840 }
841 
842 - (BOOL)isIndexPathToHistoryItem:(NSIndexPath *)indexPath
843 {
844  return (([indexPath row] - 1) < [_history_search_result count]);
845 }
846 
847 - (ComputerBookmark *)bookmarkForQuickConnectTo:(NSString *)host
848 {
849  ComputerBookmark *bookmark =
850  [[[ComputerBookmark alloc] initWithBaseDefaultParameters] autorelease];
851  [bookmark setLabel:host];
852  [[bookmark params] setValue:host forKey:@"hostname"];
853  return bookmark;
854 }
855 
856 #pragma mark - Persisting bookmarks
857 
858 - (void)scheduleWriteBookmarksToDataStore
859 {
860  [[NSOperationQueue mainQueue] addOperationWithBlock:^{
861  [self writeBookmarksToDataStore];
862  }];
863 }
864 
865 - (void)writeBookmarksToDataStore
866 {
867  [self writeManualBookmarksToDataStore];
868 }
869 
870 - (void)scheduleWriteManualBookmarksToDataStore
871 {
872  [[NSOperationQueue mainQueue]
873  addOperation:[[[NSInvocationOperation alloc]
874  initWithTarget:self
875  selector:@selector(writeManualBookmarksToDataStore)
876  object:nil] autorelease]];
877 }
878 
879 - (void)writeManualBookmarksToDataStore
880 {
881  [self writeArray:_manual_bookmarks toDataStoreURL:[self manualBookmarksDataStoreURL]];
882 }
883 
884 - (void)scheduleWriteConnectionHistoryToDataStore
885 {
886  [[NSOperationQueue mainQueue]
887  addOperation:[[[NSInvocationOperation alloc]
888  initWithTarget:self
889  selector:@selector(writeConnectionHistoryToDataStore)
890  object:nil] autorelease]];
891 }
892 
893 - (void)writeConnectionHistoryToDataStore
894 {
895  [self writeArray:_connection_history toDataStoreURL:[self connectionHistoryDataStoreURL]];
896 }
897 
898 - (void)writeArray:(NSArray *)bookmarks toDataStoreURL:(NSURL *)url
899 {
900  NSData *archived_data = [NSKeyedArchiver archivedDataWithRootObject:bookmarks];
901  [archived_data writeToURL:url atomically:YES];
902 }
903 
904 - (void)readManualBookmarksFromDataStore
905 {
906  [_manual_bookmarks autorelease];
907  _manual_bookmarks = [self arrayFromDataStoreURL:[self manualBookmarksDataStoreURL]];
908 
909  if (_manual_bookmarks == nil)
910  {
911  _manual_bookmarks = [[NSMutableArray alloc] init];
912  [_manual_bookmarks
913  addObject:[[[GlobalDefaults sharedGlobalDefaults] newTestServerBookmark] autorelease]];
914  }
915 }
916 
917 - (void)readConnectionHistoryFromDataStore
918 {
919  [_connection_history autorelease];
920  _connection_history = [self arrayFromDataStoreURL:[self connectionHistoryDataStoreURL]];
921 
922  if (_connection_history == nil)
923  _connection_history = [[NSMutableArray alloc] init];
924 }
925 
926 - (NSMutableArray *)arrayFromDataStoreURL:(NSURL *)url
927 {
928  NSData *archived_data = [NSData dataWithContentsOfURL:url];
929 
930  if (!archived_data)
931  return nil;
932 
933  return [[NSKeyedUnarchiver unarchiveObjectWithData:archived_data] retain];
934 }
935 
936 - (NSURL *)manualBookmarksDataStoreURL
937 {
938  return [NSURL
939  fileURLWithPath:[NSString stringWithFormat:@"%@/%@",
940  [NSSearchPathForDirectoriesInDomains(
941  NSDocumentDirectory, NSUserDomainMask, YES)
942  lastObject],
943  @"com.freerdp.ifreerdp.bookmarks.plist"]];
944 }
945 
946 - (NSURL *)connectionHistoryDataStoreURL
947 {
948  return [NSURL
949  fileURLWithPath:[NSString
950  stringWithFormat:@"%@/%@",
951  [NSSearchPathForDirectoriesInDomains(
952  NSDocumentDirectory, NSUserDomainMask, YES)
953  lastObject],
954  @"com.freerdp.ifreerdp.connection_history.plist"]];
955 }
956 
957 @end