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