9c1437f87b826d6c3d34ff7c9beebb931de22ca2
[vlc.git] / modules / gui / macosx / AddonManager.m
1 /*****************************************************************************
2  * AddonManager.m: Addons manager for the Mac
3  ****************************************************************************
4  * Copyright (C) 2014 VideoLAN and authors
5  * Author:       Felix Paul Kühne <fkuehne # videolan.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 #import <vlc_common.h>
23 #import <vlc_events.h>
24 #import <vlc_addons.h>
25
26 #import "AddonManager.h"
27 #import "intf.h"
28 #import "MainWindow.h"
29 #import "AddonListDataSource.h"
30
31 @interface VLCAddonManager ()
32 {
33     addons_manager_t *_manager;
34     NSMutableArray *_addons;
35     NSArray *_displayedAddons;
36     BOOL _shouldRefreshSideBarOnAddonChange;
37 }
38
39 - (void)addAddon:(NSValue *)o_value;
40 - (void)discoveryEnded;
41 - (void)addonChanged:(NSValue *)o_value;
42 @end
43
44 static void addonsEventsCallback( const vlc_event_t *event, void *data )
45 {
46     @autoreleasepool {
47         if (event->type == vlc_AddonFound)
48             [[VLCAddonManager sharedInstance] performSelectorOnMainThread:@selector(addAddon:) withObject:[NSValue valueWithPointer:event->u.addon_generic_event.p_entry] waitUntilDone:NO];
49         else if (event->type == vlc_AddonsDiscoveryEnded)
50             [[VLCAddonManager sharedInstance] performSelectorOnMainThread:@selector(discoveryEnded) withObject:nil waitUntilDone:NO];
51         else if (event->type == vlc_AddonChanged)
52             [[VLCAddonManager sharedInstance] performSelectorOnMainThread:@selector(addonChanged:) withObject:[NSValue valueWithPointer:event->u.addon_generic_event.p_entry] waitUntilDone:NO];
53     }
54 }
55
56 @implementation VLCAddonManager
57
58 static VLCAddonManager *_o_sharedInstance = nil;
59
60 + (VLCAddonManager *)sharedInstance
61 {
62     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
63 }
64
65 #pragma mark - object handling
66
67 - (id)init
68 {
69     if (_o_sharedInstance)
70         [self dealloc];
71     else {
72         _o_sharedInstance = [super init];
73         _addons = [[NSMutableArray alloc] init];
74     }
75
76     return _o_sharedInstance;
77 }
78
79 - (void)dealloc
80 {
81     [_addons release];
82     [_displayedAddons release];
83     if ( _manager )
84         addons_manager_Delete(_manager);
85     [super dealloc];
86 }
87
88 #pragma mark - UI handling
89
90 - (void)awakeFromNib
91 {
92     [_typeSwitcher removeAllItems];
93     [_typeSwitcher addItemWithTitle:_NS("All")];
94     [[_typeSwitcher lastItem] setTag: -1];
95     /* no skins on OS X so far
96     [_typeSwitcher addItemWithTitle:_NS("Skins")];
97     [[_typeSwitcher lastItem] setTag:ADDON_SKIN2]; */
98     [_typeSwitcher addItemWithTitle:_NS("Playlist parsers")];
99     [[_typeSwitcher lastItem] setTag:ADDON_PLAYLIST_PARSER];
100     [_typeSwitcher addItemWithTitle:_NS("Service Discovery")];
101     [[_typeSwitcher lastItem] setTag:ADDON_SERVICE_DISCOVERY];
102     [_typeSwitcher addItemWithTitle:_NS("Extensions")];
103     [[_typeSwitcher lastItem] setTag:ADDON_EXTENSION];
104
105     [_localAddonsOnlyCheckbox setTitle:_NS("Show Installed Only")];
106     [_localAddonsOnlyCheckbox setState:NSOffState];
107     [_downloadCatalogButton setTitle:_NS("Find more addons online")];
108     [_spinner setUsesThreadedAnimation:YES];
109
110     [self updateInstallButton:NO];
111     [_installButton setHidden:YES];
112
113     [_name setStringValue:@""];
114     [_author setStringValue:@""];
115     [_version setStringValue:@""];
116     [_description setString:@""];
117     [_window setTitle:_NS("Addons Manager")];
118     [_window setReleasedWhenClosed:NO];
119
120     [[[_addonsTable tableColumnWithIdentifier:@"installed"] headerCell] setStringValue:_NS("Installed")];
121     [[[_addonsTable tableColumnWithIdentifier:@"name"] headerCell] setStringValue:_NS("Name")];
122     [[[_addonsTable tableColumnWithIdentifier:@"author"] headerCell] setStringValue:_NS("Author")];
123     [[[_addonsTable tableColumnWithIdentifier:@"type"] headerCell] setStringValue:_NS("Type")];
124
125     _manager = addons_manager_New((vlc_object_t *)VLCIntf);
126     if (!_manager)
127         return;
128
129     vlc_event_manager_t *p_em = _manager->p_event_manager;
130     vlc_event_attach(p_em, vlc_AddonFound, addonsEventsCallback, self);
131     vlc_event_attach(p_em, vlc_AddonsDiscoveryEnded, addonsEventsCallback, self);
132     vlc_event_attach(p_em, vlc_AddonChanged, addonsEventsCallback, self);
133
134     [self _findInstalled];
135 }
136
137 - (void)showWindow
138 {
139     [_window makeKeyAndOrderFront:nil];
140 }
141
142 - (IBAction)switchType:(id)sender
143 {
144     [self _refactorDataModel];
145 }
146
147 - (IBAction)toggleLocalCheckbox:(id)sender
148 {
149     [self _refactorDataModel];
150 }
151
152 - (IBAction)downloadCatalog:(id)sender
153 {
154     [self _findNewAddons];
155     [_downloadCatalogButton setHidden:YES];
156     [_localAddonsOnlyCheckbox setHidden:NO];
157 }
158
159 - (IBAction)installSelection:(id)sender
160 {
161     NSInteger selectedRow = [_addonsTable selectedRow];
162     if (selectedRow > _displayedAddons.count - 1 || selectedRow < 0)
163         return;
164
165     VLCAddon *currentAddon = [_displayedAddons objectAtIndex:selectedRow];
166     [self _installAddonWithID:[currentAddon uuid] type:[currentAddon type]];
167
168     [_installButton setEnabled:NO];
169 }
170
171 - (IBAction)uninstallSelection:(id)sender
172 {
173     NSInteger selectedRow = [_addonsTable selectedRow];
174     if (selectedRow > _displayedAddons.count - 1 || selectedRow < 0)
175         return;
176
177     VLCAddon *currentAddon = [_displayedAddons objectAtIndex:selectedRow];
178     [self _removeAddonWithID:[currentAddon uuid] type:[currentAddon type]];
179
180     [_installButton setEnabled:NO];
181 }
182
183 - (void)updateInstallButton:(BOOL)b_is_installed
184 {
185     [_installButton setHidden:NO];
186     [_installButton setEnabled:YES];
187
188     if (b_is_installed) {
189         [_installButton setTitle:_NS("Uninstall")];
190         [_installButton setAction:@selector(uninstallSelection:)];
191     } else {
192         [_installButton setTitle:_NS("Install")];
193         [_installButton setAction:@selector(installSelection:)];
194     }
195 }
196
197 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
198 {
199     return [_displayedAddons count];
200 }
201
202 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
203 {
204     NSInteger selectedRow = [_addonsTable selectedRow];
205     if (selectedRow > _displayedAddons.count - 1 || selectedRow < 0) {
206         [_name setStringValue:@""];
207         [_author setStringValue:@""];
208         [_version setStringValue:@""];
209         [_description setString:@""];
210         [_installButton setHidden:YES];
211         return;
212     }
213
214     VLCAddon *currentItem = [_displayedAddons objectAtIndex:selectedRow];
215     [_name setStringValue:[currentItem name]];
216     [_author setStringValue:[currentItem author]];
217     [_version setStringValue:[currentItem version]];
218     [_description setString:[currentItem description]];
219
220     [self updateInstallButton:[currentItem isInstalled]];
221 }
222
223 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
224 {
225     NSString *identifier = [aTableColumn identifier];
226     if ([identifier isEqualToString:@"installed"]) {
227         return [[_displayedAddons objectAtIndex:rowIndex] isInstalled] ? @"✔" : @"✘";
228     } else if([identifier isEqualToString:@"name"])
229         return [[_displayedAddons objectAtIndex:rowIndex] name];
230
231     return @"";
232 }
233
234 #pragma mark - data handling
235
236 - (void)addAddon:(NSValue *)o_value
237 {
238     addon_entry_t *p_entry = [o_value pointerValue];
239     /* no skin support on OS X so far */
240     if (p_entry->e_type != ADDON_SKIN2)
241         [_addons addObject:[[[VLCAddon alloc] initWithAddon:p_entry] autorelease]];
242 }
243
244 - (void)discoveryEnded
245 {
246     [self _refactorDataModel];
247     [_spinner stopAnimation:nil];
248 }
249
250 - (void)addonChanged:(NSValue *)o_value
251 {
252     [self _refactorDataModel];
253     if (_shouldRefreshSideBarOnAddonChange) {
254         [[VLCMainWindow sharedInstance] performSelector:@selector(reloadSidebar) withObject:nil afterDelay:0.5];
255         _shouldRefreshSideBarOnAddonChange = NO;
256     }
257 }
258
259 #pragma mark - helpers
260
261 - (void)_refactorDataModel
262 {
263     BOOL installedOnly = _localAddonsOnlyCheckbox.state == NSOnState;
264     int type = [[_typeSwitcher selectedItem] tag];
265
266     NSUInteger count = _addons.count;
267     NSMutableArray *filteredItems = [[NSMutableArray alloc] initWithCapacity:count];
268     for (NSUInteger x = 0; x < count; x++) {
269         VLCAddon *currentItem = [_addons objectAtIndex:x];
270         if (type != -1) {
271             if ([currentItem type] == type) {
272                 if (installedOnly) {
273                     if ([currentItem isInstalled])
274                         [filteredItems addObject:currentItem];
275                 } else
276                     [filteredItems addObject:currentItem];
277             }
278         } else {
279             if (installedOnly) {
280                 if ([currentItem isInstalled])
281                     [filteredItems addObject:currentItem];
282             } else
283                 [filteredItems addObject:currentItem];
284         }
285     }
286
287     if (_displayedAddons)
288         [_displayedAddons release];
289     _displayedAddons = [NSArray arrayWithArray:filteredItems];
290     [_displayedAddons retain];
291     [filteredItems release];
292
293     // update ui
294     [_addonsTable reloadData];
295     [self tableViewSelectionDidChange:nil];
296 }
297
298 - (void)_findNewAddons
299 {
300     [_spinner startAnimation:nil];
301     addons_manager_Gather(_manager, "repo://");
302 }
303
304 /* FIXME: un-used */
305 - (void)_findDesignatedAddon:(NSString *)uri
306 {
307     addons_manager_Gather(_manager, [uri UTF8String]);
308 }
309
310 - (void)_findInstalled
311 {
312     addons_manager_LoadCatalog(_manager);
313
314     // enqueue, to process the addons first
315     [self performSelectorOnMainThread:@selector(_refactorDataModel) withObject:nil waitUntilDone:NO];
316 }
317
318 - (void)_installAddonWithID:(NSData *)o_data type:(addon_type_t)type
319 {
320     addon_uuid_t uuid;
321     [o_data getBytes:uuid length:sizeof(uuid)];
322
323     if (type == ADDON_SERVICE_DISCOVERY)
324         _shouldRefreshSideBarOnAddonChange = YES;
325
326     addons_manager_Install(_manager, uuid);
327 }
328
329 - (void)_removeAddonWithID:(NSData *)o_data type:(addon_type_t)type
330 {
331     addon_uuid_t uuid;
332     [o_data getBytes:uuid length:sizeof(uuid)];
333
334     if (type == ADDON_SERVICE_DISCOVERY)
335         _shouldRefreshSideBarOnAddonChange = YES;
336
337     addons_manager_Remove(_manager, uuid);
338 }
339
340 - (NSString *)_getAddonType:(int)i_type
341 {
342     switch (i_type)
343     {
344         case ADDON_SKIN2:
345             return _NS("Skins");
346         case ADDON_PLAYLIST_PARSER:
347             return _NS("Playlist parsers");
348         case ADDON_SERVICE_DISCOVERY:
349             return _NS("Service Discovery");
350         case ADDON_EXTENSION:
351             return _NS("Extensions");
352         default:
353             return _NS("Unknown");
354     }
355 }
356
357 @end