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