aboutsummaryrefslogtreecommitdiff
path: root/VKPC/Controller.m
diff options
context:
space:
mode:
Diffstat (limited to 'VKPC/Controller.m')
-rw-r--r--VKPC/Controller.m345
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