vkpc-mac/VKPC/Controller.m
2015-08-14 01:04:22 +03:00

346 lines
14 KiB
Objective-C

//
// 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