diff options
Diffstat (limited to 'VKPC/Controller.m')
-rw-r--r-- | VKPC/Controller.m | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/VKPC/Controller.m b/VKPC/Controller.m new file mode 100644 index 0000000..825aa6f --- /dev/null +++ b/VKPC/Controller.m @@ -0,0 +1,345 @@ +// +// Controller.m +// VKPC +// +// Created by Eugene on 10/23/14. +// Copyright (c) 2014 Eugene Z. All rights reserved. +// + +#import "Controller.h" +#import "PopoverController.h" +#import "Server.h" + +static NSString * const kASExecuteJSChrome = @"execute javascript"; +static NSString * const kASExecuteJSSafari = @"do JavaScript"; +static NSString * const kASCurrentTabChrome = @"active tab"; +static NSString * const kASCurrentTabSafari = @"current tab"; +static NSString * const kASTabTitleChrome = @"title of"; +static NSString * const kASTabTitleSafari = @"name of"; + +static NSString * const kCommandAfterInjection = @"afterInjection"; +static NSString * const kCommandPlayPause = @"playpause"; +static NSString * const kCommandPrev = @"prev"; +static NSString * const kCommandNext = @"next"; +static NSString * const kCommandOperateTrack = @"operateTrack:{id}"; + +static NSArray *browsers = nil; +static NSString *scriptJS; +#ifdef DEBUG +static NSString *scriptJSUnescaped; +#endif +static NSString *scriptAS; +static NSMutableDictionary *cache = nil; +static NSTimer *timer = nil; +static NSInteger browser; +static BOOL initialized = NO; + +@implementation Controller + ++ (void)initialize { + if (initialized) { + return; + } + + browsers = @[ + @[@{@"id": @"com.google.Chrome", @"name": @"Google Chrome", @"key": @"chrome"}, @{@"id": @"com.google.Chrome.canary", @"name": @"Google Chrome Canary", @"key": @"chromecanary"}], + @[@{@"id": @"org.mozilla.firefox", @"name": @"Firefox", @"key": @"firefox"}], + @[@{@"id": @"com.apple.Safari", @"name": @"Safari", @"key": @"safari"}], + @[@{@"id": @"com.operasoftware.Opera", @"name": @"Opera", @"key": @"opera"}, @{@"id": @"com.operasoftware.OperaNext", @"name": @"Opera Next", @"key": @"operanext"}], + @[@{@"id": @"ru.yandex.desktop.yandex-browser", @"name": @"Yandex", @"key": @"yandex"}] + ]; + NSError *error = nil; + + scriptJS = GetFileFromResourceAsString(@"inject.js", &error); + scriptAS = GetFileFromResourceAsString(@"inject.as", &error); + + if (error) { + NSLog(@"Error while reading from resources: %@", error); + // TODO something + return; + } + + scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"{sid}" withString:[NSString stringWithFormat:@"%d", VKPCSessionID]]; +// scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"{debug}" withString:(VKPCIsDebug ? @"true" : @"false")]; + +#ifdef DEBUG + scriptJSUnescaped = [NSString stringWithString:scriptJS]; +#endif + + scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; + scriptJS = [scriptJS stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; + + cache = [[NSMutableDictionary alloc] init]; + +// if (NO) + [self setupTimer]; + + browser = [[NSUserDefaults standardUserDefaults] integerForKey:VKPCPreferencesBrowser]; + + [[NSUserDefaults standardUserDefaults] addObserver:(id)[Controller class] + forKeyPath:VKPCPreferencesBrowser + options:NSKeyValueObservingOptionNew + context:NULL]; + + initialized = YES; +} + ++ (void)setupTimer { + if (timer != nil) { + [timer invalidate]; + timer = nil; + } + + [self timerCallback:nil]; + timer = [NSTimer scheduledTimerWithTimeInterval:2.0 + target:[Controller class] + selector:@selector(timerCallback:) + userInfo:nil + repeats:YES]; +} + +#ifdef DEBUG ++ (void)debugSendPlay { + [Controller playpause]; +} + ++ (void)debugInject { + [Controller sendCommand:kCommandAfterInjection]; +} + ++ (void)debugCopyJS { + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; + [pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; + [pasteBoard setString:scriptJSUnescaped forType:NSStringPboardType]; +} + ++ (void)debugCopyAS { + NSString *code = [self findRunningAppAndPrepareASForCommand:kCommandAfterInjection]; + if (code != nil) { + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; + [pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; + [pasteBoard setString:code forType:NSStringPboardType]; + } else { + NSLog(@"[Controller debugCopyAS] code == nil"); + } +} +#endif + +//static BOOL playlistDelegateSet = NO; ++ (void)timerCallback:(NSTimer *)timer { + if ([[PopoverController shared] playlistTableController] != nil && [[PopoverController shared] playlistTableController].playlist.delegate == nil) { + [[PopoverController shared].playlistTableController.playlist setDelegate:(id)[self class]]; + NSLog(@"[Controller timerCallback] playlist delegate set"); +// playlistDelegateSet = YES; + } + + if ([self isASBrowser:browser]) { + [Controller sendCommand:kCommandAfterInjection]; + } else if ([Server connectedCount:browser] <= 0) { + [[PopoverController shared].playlistTableController clearPlaylist]; + } +} + ++ (void)prev { + [Controller sendCommand:kCommandPrev]; +} + ++ (void)next { + [Controller sendCommand:kCommandNext]; +} + ++ (void)playpause { + [Controller sendCommand:kCommandPlayPause]; +} + ++ (void)operateTrack:(NSString *)trackID { + [Controller sendCommand:[kCommandOperateTrack stringByReplacingOccurrencesOfString:@"{id}" withString:trackID]]; +} + ++ (void)sendCommand:(NSString *)command { + if ([self isASBrowser:browser]) { + NSString *code = [self findRunningAppAndPrepareASForCommand:command]; + if (code == nil) { + // NSLog(@"[Controller sendCommand:] code == nil, returning"); + // Clear playlist? + [[PopoverController shared].playlistTableController clearPlaylist]; + return; + } + + NSAppleScript *as = [[NSAppleScript alloc] initWithSource:code]; + NSDictionary *error = nil; + NSAppleEventDescriptor *result = [as executeAndReturnError:&error]; + + if (error) { + NSLog(@"[Controller sendCommand:] error: %@", error); + } else if ([command isEqualToString:kCommandAfterInjection]) { + int returnValue = 0; + [result.data getBytes:&returnValue length:result.data.length]; + // NSLog(@"[Controller sendCommand:] returnValue = %d", returnValue); + if (returnValue == 1) { + [[PopoverController shared].playlistTableController clearPlaylist]; + } + } + } else { + if ([Server connectedCount:browser] <= 0) { + [[PopoverController shared].playlistTableController clearPlaylist]; + return; + } + + // Send to extensions + [Server send:[self JSONForCommand:@"vkpc" data:command] forBrowser:browser]; + } +} + ++ (NSString *)findRunningAppAndPrepareASForCommand:(NSString *)command { + NSArray *list = browsers[browser]; + NSArray *apps = [[NSWorkspace sharedWorkspace] runningApplications]; + NSDictionary *app; + + BOOL found = NO; + for (int i = 0; i < apps.count; i++) { + NSRunningApplication *currentApp = apps[i]; + + for (NSDictionary *dict in list) { + if ([currentApp.bundleIdentifier isEqualToString:dict[@"id"]]) { + app = dict; + found = YES; + break; + } + } + + if (found) + break; + } + + if (!found) { +// NSLog(@"[Controller findRunningAppAndPrepareASForCommand:] %@ not found in running applications, nil will returned", (NSString *)browsers[browser][0][@"name"]); + return nil; + } + + NSString *as = scriptAS; + NSInteger playlistID = [PopoverController shared].playlistTableController.playlist.playlistID; + + NSString *ASExecuteJS, *ASCurrentTab, *ASTabTitle; + if (browser == BrowserSafari) { + ASExecuteJS = kASExecuteJSSafari; + ASCurrentTab = kASCurrentTabSafari; + ASTabTitle = kASTabTitleSafari; + } else { + ASExecuteJS = kASExecuteJSChrome; + ASCurrentTab = kASCurrentTabChrome; + ASTabTitle = kASTabTitleChrome; + } + + as = [as stringByReplacingOccurrencesOfString:@"{appName}" withString:app[@"name"]]; + as = [as stringByReplacingOccurrencesOfString:@"{js}" withString:scriptJS]; + as = [as stringByReplacingOccurrencesOfString:@"{ASExecuteJS}" withString:ASExecuteJS]; + as = [as stringByReplacingOccurrencesOfString:@"{ASCurrentTab}" withString:ASCurrentTab]; + as = [as stringByReplacingOccurrencesOfString:@"{ASTabTitle}" withString:ASTabTitle]; + + as = [as stringByReplacingOccurrencesOfString:@"{playlistID}" withString:[NSString stringWithFormat:@"%ld", playlistID]]; + as = [as stringByReplacingOccurrencesOfString:@"{command}" withString:command]; + + return as; +} + ++ (void)handleClient:(NSDictionary *)json { +// NSLog(@"[Controller handleClient] json: %@", json); + + NSInteger fromBrowser = [(NSNumber *)json[@"_browser"] integerValue]; + if (fromBrowser != browser) { +// NSLog(@"[Controller handleClient] received message from browser=%zd, but current browser=%zd, skipping", fromBrowser, browser); + return; + } + + NSString *command = json[@"command"]; + NSDictionary *data = json[@"data"]; + if (!command || [command isEqual:[NSNull null]]) { + NSLog(@"[Controller handleCommand] !json"); + return; + } + + if ([command isEqualToString:@"updatePlaylist"]) { + NSArray *tracks = data[@"tracks"]; + NSInteger playlistId = [(NSNumber *)data[@"id"] intValue]; + NSString *title = data[@"title"]; + NSDictionary *active = data[@"active"]; + NSString *browser = data[@"browser"]; + + NSString *activeStatus = active[@"status"]; + NSString *activeId = active[@"id"]; + BOOL playingStatus = ( activeStatus && ![activeStatus isEqual:[NSNull null]] && [activeStatus isEqualToString:@"play"] ) ? YES : NO; + + NSLog(@"[server] got updatePlaylist; id=%ld, activeId=%@, activeStatus=%@, browser=%@, title=%@", + playlistId, (NSString *)active[@"id"], active[@"status"], browser, title); + + if ([[PopoverController shared].playlistTableController inited]) { + NSLog(@"[Controller handleClient] call setPlaylist.."); + [[PopoverController shared].playlistTableController setPlaylistDataWithTracks:tracks title:title id:playlistId activeId:activeId activePlaying:playingStatus browser:browser]; + } else { + NSLog(@"[Controller handleClient] call preSetPlaylist.."); + [PlaylistTableController preSetPlaylistDataWithTracks:tracks title:title id:playlistId activeId:activeId activePlaying:playingStatus browser:browser]; + } + } else if ([command isEqualToString:@"operateTrack"]) { + NSString *trackId = data[@"id"]; + NSString *status = data[@"status"]; + NSInteger playlistId = [(NSNumber *)data[@"playlistId"] intValue]; + + NSLog(@"[server] got operateTrack; trackId=%@, status=%@, plId=%ld", + trackId, status, playlistId); + + PlayingStatus playingStatus = (status && ![status isEqual:[NSNull null]] && [status isEqualToString:@"play"]) ? PlayingStatusPlaying : PlayingStatusPaused; + [[PopoverController shared].playlistTableController setPlayingTrackById:trackId withStatus:playingStatus forPlaylist:playlistId]; + } else if ([command isEqualToString:@"clearPlaylist"]) { + [[PopoverController shared].playlistTableController clearPlaylist]; + } +} + +// PlaylistDeletage ++ (void)playlistIDChanged:(NSInteger)playlistID { +// NSLog(@"playlist id changed! new id: %zd", playlistID); + if (initialized) { +// NSLog(@"now send new playlist id to clients"); + [Server send:[self JSONForCommand:@"set_playlist_id" data:[NSNumber numberWithInteger:playlistID]] forBrowser:-1]; + } +} + +// KVO ++ (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([keyPath isEqualToString:VKPCPreferencesBrowser]) { + NSNumber *new = change[NSKeyValueChangeKindKey]; + if ([new integerValue] == NSKeyValueChangeSetting) { + NSInteger value = [(NSNumber *)change[NSKeyValueChangeNewKey] integerValue]; + if (browser != value) { + NSLog(@"[Controller KVO] new browser is %@", browsers[value][0][@"name"]); + browser = value; + [cache removeAllObjects]; + [[PopoverController shared].playlistTableController clearPlaylist]; + [self setupTimer]; + } + } + } +} + +// Other ++ (BOOL)isASBrowser:(NSInteger)browser { + return ![[NSUserDefaults standardUserDefaults] boolForKey:VKPCPreferencesUseExtensionMode] && ( + browser == BrowserChrome + || browser == BrowserYandex + || browser == BrowserSafari ); +} + ++ (NSString *)JSONForCommand:(NSString *)command data:(NSObject *)data { + NSDictionary *dict = @{@"command": command, @"data": data}; + NSError *error; + NSData *json = [NSJSONSerialization dataWithJSONObject:dict options:(NSJSONWritingOptions)0 error:&error]; + + if (!json) { + NSLog(@"[Controller JSONForCommand] error: %@", error.localizedDescription); + return @"{}"; + } else { + return [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding]; + } +} + +@end |