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