bonjour: change server item type to directory
[vlc.git] / modules / services_discovery / bonjour.m
1 /*****************************************************************************
2  * bonjour.m: mDNS services discovery module based on Bonjour
3  *****************************************************************************
4  * Copyright (C) 2016 VLC authors, VideoLAN and VideoLabs
5  *
6  * Authors: Felix Paul K├╝hne <fkuehne@videolan.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include <config.h>
25 #endif
26
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_modules.h>
30 #include <vlc_services_discovery.h>
31
32 #import <Foundation/Foundation.h>
33
34 static int Open( vlc_object_t * );
35 static void Close( vlc_object_t * );
36
37 VLC_SD_PROBE_HELPER( "Bonjour", "Bonjour Network Discovery", SD_CAT_LAN )
38
39 /*
40  * Module descriptor
41  */
42 vlc_module_begin()
43     set_shortname( "Bonjour" )
44     set_description( N_( "Bonjour Network Discovery" ) )
45     set_category( CAT_PLAYLIST )
46     set_subcategory( SUBCAT_PLAYLIST_SD )
47     set_capability( "services_discovery", 0 )
48     set_callbacks( Open, Close )
49     add_shortcut( "mdns", "bonjour" )
50
51     VLC_SD_PROBE_SUBMODULE
52 vlc_module_end ()
53
54 NSString *const VLCBonjourProtocolName = @"VLCBonjourProtocolName";
55 NSString *const VLCBonjourProtocolServiceName = @"VLCBonjourProtocolServiceName";
56
57 @interface VLCNetServiceDiscoveryController : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
58 {
59 #ifdef MAC_OS_X_VERSION_10_11
60     NSArray<NSNetServiceBrowser *> *_serviceBrowsers;
61
62     NSMutableArray<NSNetService *> *_rawNetServices;
63     NSMutableArray<NSNetService *> *_resolvedNetServices;
64     NSMutableArray<NSValue *> *_inputItemsForNetServices;
65 #else
66     NSArray *_serviceBrowsers;
67
68     NSMutableArray *_rawNetServices;
69     NSMutableArray *_resolvedNetServices;
70     NSMutableArray *_inputItemsForNetServices;
71 #endif
72
73     NSArray *_activeProtocols;
74 }
75
76 @property (readwrite, nonatomic) services_discovery_t *p_sd;
77
78 - (void)startDiscovery;
79 - (void)stopDiscovery;
80
81 @end
82
83 struct services_discovery_sys_t
84 {
85     CFTypeRef _Nullable discoveryController;
86 };
87
88 @implementation VLCNetServiceDiscoveryController
89
90 - (void)startDiscovery
91 {
92     _rawNetServices = [[NSMutableArray alloc] init];
93     _resolvedNetServices = [[NSMutableArray alloc] init];
94     _inputItemsForNetServices = [[NSMutableArray alloc] init];
95
96     NSDictionary *VLCFtpProtocol = @{ VLCBonjourProtocolName : @"ftp",
97                                       VLCBonjourProtocolServiceName : @"_ftp._tcp." };
98     NSDictionary *VLCSmbProtocol = @{ VLCBonjourProtocolName : @"smb",
99                                       VLCBonjourProtocolServiceName : @"_smb._tcp." };
100     NSDictionary *VLCNfsProtocol = @{ VLCBonjourProtocolName : @"nfs",
101                                       VLCBonjourProtocolServiceName : @"_nfs._tcp." };
102     NSDictionary *VLCSftpProtocol = @{ VLCBonjourProtocolName : @"sftp",
103                                        VLCBonjourProtocolServiceName : @"_sftp-ssh._tcp." };
104
105     NSArray *VLCSupportedProtocols = @[VLCFtpProtocol,
106                                        VLCSmbProtocol,
107                                        VLCNfsProtocol,
108                                        VLCSftpProtocol];
109
110     NSUInteger count = VLCSupportedProtocols.count;
111     NSMutableArray *discoverers = [[NSMutableArray alloc] init];
112     NSMutableArray *protocols = [[NSMutableArray alloc] init];
113
114     for (NSUInteger i = 0; i < count; i++) {
115         NSDictionary *protocol = VLCSupportedProtocols[i];
116
117         /* only discover hosts if we actually have a module that can handle those */
118         if (!module_exists([protocol[VLCBonjourProtocolName] UTF8String]))
119             continue;
120
121         NSNetServiceBrowser *serviceBrowser = [[NSNetServiceBrowser alloc] init];
122         serviceBrowser.delegate = self;
123         [serviceBrowser searchForServicesOfType:protocol[VLCBonjourProtocolServiceName] inDomain:@"local."];
124         [discoverers addObject:serviceBrowser];
125         [protocols addObject:protocol];
126     }
127
128     _serviceBrowsers = [discoverers copy];
129     _activeProtocols = [protocols copy];
130 }
131
132 - (void)stopDiscovery
133 {
134     [_serviceBrowsers makeObjectsPerformSelector:@selector(stop)];
135
136     NSUInteger inputItemCount = _inputItemsForNetServices.count;
137     for (NSUInteger i = 0; i < inputItemCount; i++) {
138         input_item_t *p_input_item = [_inputItemsForNetServices[i] pointerValue];
139         if (p_input_item != NULL) {
140             services_discovery_RemoveItem(self.p_sd, p_input_item);
141             input_item_Release(p_input_item);
142         }
143     }
144
145     [_inputItemsForNetServices removeAllObjects];
146     [_resolvedNetServices removeAllObjects];
147 }
148
149 #pragma mark - functional delegation
150
151 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
152 {
153     msg_Dbg(self.p_sd, "found bonjour service: %s (%s)", [aNetService.name UTF8String], [aNetService.type UTF8String]);
154     [_rawNetServices addObject:aNetService];
155     aNetService.delegate = self;
156     [aNetService resolveWithTimeout:5.];
157 }
158
159 - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing
160 {
161     msg_Dbg(self.p_sd, "bonjour service disappeared: %s", [aNetService.name UTF8String]);
162     if ([_rawNetServices containsObject:aNetService])
163         [_rawNetServices removeObject:aNetService];
164
165     if ([_resolvedNetServices containsObject:aNetService]) {
166         NSInteger index = [_resolvedNetServices indexOfObject:aNetService];
167         if (index == NSNotFound)
168             return;
169
170         [_resolvedNetServices removeObjectAtIndex:index];
171         input_item_t *p_input_item = [_inputItemsForNetServices[index] pointerValue];
172         if (p_input_item != NULL) {
173             services_discovery_RemoveItem(self.p_sd, p_input_item);
174             input_item_Release(p_input_item);
175         }
176     }
177 }
178
179 - (void)netServiceDidResolveAddress:(NSNetService *)aNetService
180 {
181     if (![_resolvedNetServices containsObject:aNetService]) {
182         NSString *serviceType = aNetService.type;
183         NSUInteger count = _activeProtocols.count;
184         NSString *protocol = nil;
185         for (NSUInteger i = 0; i < count; i++) {
186             NSDictionary *protocolDefinition = _activeProtocols[i];
187             if ([serviceType isEqualToString:protocolDefinition[VLCBonjourProtocolServiceName]]) {
188                 protocol = protocolDefinition[VLCBonjourProtocolName];
189             }
190         }
191
192         NSString *uri = [NSString stringWithFormat:@"%@://%@:%ld",
193                          protocol,
194                          aNetService.hostName,
195                          aNetService.port];
196
197         input_item_t *p_input_item = input_item_NewWithTypeExt([uri UTF8String],
198                                                                [aNetService.name UTF8String],
199                                                                0, NULL, 0, -1,
200                                                                ITEM_TYPE_DIRECTORY, true );
201
202         if (p_input_item != NULL) {
203             services_discovery_AddItem(self.p_sd, p_input_item, NULL);
204             [_inputItemsForNetServices addObject:[NSValue valueWithPointer:p_input_item]];
205             [_resolvedNetServices addObject:aNetService];
206         }
207     }
208
209     [_rawNetServices removeObject:aNetService];
210 }
211
212 - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict
213 {
214     msg_Dbg(self.p_sd, "failed to resolve: %s", [aNetService.name UTF8String]);
215     [_rawNetServices removeObject:aNetService];
216 }
217
218 @end
219
220 static int Open(vlc_object_t *p_this)
221 {
222     services_discovery_t *p_sd = (services_discovery_t *)p_this;
223     services_discovery_sys_t *p_sys = NULL;
224
225     p_sd->p_sys = p_sys = calloc(1, sizeof(services_discovery_sys_t));
226     if (!p_sys) {
227         return VLC_ENOMEM;
228     }
229
230     VLCNetServiceDiscoveryController *discoveryController = [[VLCNetServiceDiscoveryController alloc] init];
231     discoveryController.p_sd = p_sd;
232
233     p_sys->discoveryController = CFBridgingRetain(discoveryController);
234
235     [discoveryController startDiscovery];
236
237     return VLC_SUCCESS;
238 }
239
240 static void Close(vlc_object_t *p_this)
241 {
242     services_discovery_t *p_sd = (services_discovery_t *)p_this;
243     services_discovery_sys_t *p_sys = p_sd->p_sys;
244
245     VLCNetServiceDiscoveryController *discoveryController = (__bridge VLCNetServiceDiscoveryController *)(p_sys->discoveryController);
246     [discoveryController stopDiscovery];
247
248     CFBridgingRelease(p_sys->discoveryController);
249     discoveryController = nil;
250
251     free(p_sys);
252 }