diff --git a/Origami Plugin/DHAppleScriptPatch.h b/Origami Plugin/DHAppleScriptPatch.h new file mode 100644 index 0000000..8cc4667 --- /dev/null +++ b/Origami Plugin/DHAppleScriptPatch.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface DHAppleScriptPatch : QCPatch +{ + QCStringPort *inputScript; + QCBooleanPort *inputUpdateSignal; + QCVirtualPort *outputResult; +} + +@end diff --git a/Origami Plugin/DHAppleScriptPatch.m b/Origami Plugin/DHAppleScriptPatch.m new file mode 100644 index 0000000..67636cf --- /dev/null +++ b/Origami Plugin/DHAppleScriptPatch.m @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHAppleScriptPatch.h" +#import "NSAppleScript+FBAdditions.h" + +@implementation DHAppleScriptPatch + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProvider; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputScript.wasUpdated || inputUpdateSignal.wasUpdated)) { + return YES; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + NSString *error = nil; + id result = [NSAppleScript runScript:inputScript.stringValue error:&error]; + + if ([result isKindOfClass:[NSArray class]]) { + result = [[QCStructure alloc] initWithArray:result]; + } else if ([result isKindOfClass:[NSDictionary class]]) { + result = [[QCStructure alloc] initWithDictionary:result]; + } + + outputResult.rawValue = (error) ? error : result; + }); + + return YES; +} + +@end diff --git a/Origami Plugin/DHAppleScriptPatch.xml b/Origami Plugin/DHAppleScriptPatch.xml new file mode 100644 index 0000000..fefe9c9 --- /dev/null +++ b/Origami Plugin/DHAppleScriptPatch.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Executes an AppleScript. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + AppleScript + + inputAttributes + + inputScript + + description + + name + Location + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputResult + + description + + name + Result + + + + diff --git a/Origami Plugin/DHImageAtURLPatch.h b/Origami Plugin/DHImageAtURLPatch.h new file mode 100644 index 0000000..e9aa913 --- /dev/null +++ b/Origami Plugin/DHImageAtURLPatch.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHImageAtURLPatch : QCPatch +{ + QCVirtualPort *inputURLOrURLs; + QCBooleanPort *inputUseCache; + QCVirtualPort *outputImageOrStructure; + QCBooleanPort *outputDone; + + NSDictionary *_URLs; + NSMutableSet *_stillDownloading; +} + +@end diff --git a/Origami Plugin/DHImageAtURLPatch.m b/Origami Plugin/DHImageAtURLPatch.m new file mode 100644 index 0000000..ffa58a7 --- /dev/null +++ b/Origami Plugin/DHImageAtURLPatch.m @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHImageAtURLPatch.h" +#import "NSArray+FBAdditions.h" +#import "NSURL+FBAdditions.h" +#import "QCPatch+FBAdditions.h" +#import "NSDocument+FBAdditions.h" + +@interface DHImageAtURLPatch (Private) +- (void)_downloadImageAtURL:(NSString *)URL; +- (void)_updateOutputPorts; +@end + +@implementation DHImageAtURLPatch + +static NSMutableDictionary *_downloadedImages; + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + inputUseCache.booleanValue = YES; + _stillDownloading = [[NSMutableSet alloc] init]; + if (!_downloadedImages) { + _downloadedImages = [[NSMutableDictionary alloc] init]; + } + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputURLOrURLs.wasUpdated || inputUseCache.wasUpdated)) { + return YES; + } + + id inputValue = inputURLOrURLs.rawValue; + if ([inputValue isKindOfClass:[NSString class]]) { + _URLs = @{@(0): inputValue}; + } else if ([inputValue isKindOfClass:[QCStructure class]]) { + _URLs = [inputValue dictionaryRepresentation]; + } else { + _URLs = @{}; + } + + [_stillDownloading removeAllObjects]; + [self _updateOutputPorts]; + + for (id key in _URLs) { + NSString *URL = _URLs[key]; + if (URL && (inputUseCache.booleanValue == NO || _downloadedImages[URL] == nil)) { + [_stillDownloading addObject:URL]; + [NSThread detachNewThreadSelector:@selector(_downloadImageAtURL:) toTarget:self withObject:URL]; + } + } + + return YES; +} + +@end + +@implementation DHImageAtURLPatch (Private) + +- (void)_downloadImageAtURL:(NSString *)URL { + if (!(URL && [URL isKindOfClass:[NSString class]])) { + return; + } + + NSImage *image = [[NSImage alloc] initWithContentsOfURL:[NSURL URLWithQuartzComposerLocation:URL relativeToDocument:self.fb_document]]; + _downloadedImages[URL] = image ? image : [NSNull null]; + [_stillDownloading removeObject:URL]; + + [self performSelectorOnMainThread:@selector(_updateOutputPorts) withObject:nil waitUntilDone:NO]; +} + +- (void)_updateOutputPorts { + if (_URLs.count == 1) { + id result = _downloadedImages[[_URLs allValues][0]]; + outputImageOrStructure.rawValue = (result && result != [NSNull null]) ? result : nil; + outputDone.booleanValue = YES; + return; + } + + NSMutableDictionary *images = [[NSMutableDictionary alloc] initWithCapacity:_URLs.count]; + if (_downloadedImages.count) { + for (id key in _URLs) { + NSString *URL = _URLs[key]; + if (![_stillDownloading containsObject:URL]) { + NSImage *image = _downloadedImages[URL]; + if (image) { + images[key] = image; + } + } + } + } + + BOOL allKeysAreNumbers = YES; + for (id key in images) { + allKeysAreNumbers &= [key isKindOfClass:[NSNumber class]]; + } + + QCStructure *outputStructure = nil; + if (images.count) { + if (!allKeysAreNumbers) { + outputStructure = [[QCStructure alloc] initWithDictionary:images]; + } else { + NSMutableArray *sortedImages = [[NSMutableArray alloc] initWithCapacity:images.count]; + id keysArray = [[images allKeys] sortedArrayUsingAlphabeticalSort]; + for (NSNumber *key in keysArray) { + [sortedImages addObject:images[key]]; + } + outputStructure = [[QCStructure alloc] initWithArray:sortedImages]; + } + } + + outputImageOrStructure.rawValue = outputStructure; + outputDone.booleanValue = images.count ? (images.count == _URLs.count) : NO; +} + +@end diff --git a/Origami Plugin/DHImageAtURLPatch.xml b/Origami Plugin/DHImageAtURLPatch.xml new file mode 100644 index 0000000..664a30c --- /dev/null +++ b/Origami Plugin/DHImageAtURLPatch.xml @@ -0,0 +1,65 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Asynchronously downloads and caches requested images. + +If you specify a single URL (a string), the image at that URL will be returned. If you specify multiple URLs (a structure of strings), a structure of images will be returned. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Image at URL + + inputAttributes + + inputURLOrURLs + + description + + name + URL(s) + + inputUseCache + + description + + name + Use Cache + + + outputAttributes + + outputImageOrStructure + + description + + name + Image(s) + + outputDone + + description + + name + Done + + + + diff --git a/Origami Plugin/DHImageWithFormattedStrings.h b/Origami Plugin/DHImageWithFormattedStrings.h new file mode 100644 index 0000000..77544ac --- /dev/null +++ b/Origami Plugin/DHImageWithFormattedStrings.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHImageWithFormattedStrings : QCPatch +{ + QCStringPort *inputString; + QCImagePort *outputImage; + QCStringPort *outputUnformattedString; +} + +@end diff --git a/Origami Plugin/DHImageWithFormattedStrings.m b/Origami Plugin/DHImageWithFormattedStrings.m new file mode 100644 index 0000000..3ce1485 --- /dev/null +++ b/Origami Plugin/DHImageWithFormattedStrings.m @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHImageWithFormattedStrings.h" +#import "NSString+FBAdditions.h" +#import "NSColor+HTMLExtensions.h" + +@implementation DHImageWithFormattedStrings + +static NSString *DHStyleColor = @"DHStyleColor"; +static NSString *DHStyleFontName = @"DHStyleFontName"; +static NSString *DHStyleFontSize = @"DHStyleFontSize"; +static NSString *DHStyleBold = @"DHStyleBold"; +static NSString *DHStyleItalic = @"DHStyleItalic"; +static NSString *DHStyleUnderline = @"DHStyleUnderline"; +static NSString *DHStyleStrikethrough = @"DHStyleStrikethrough"; +static NSString *DHStyleMaxWidth = @"DHStyleMaxWidth"; +static NSString *DHStyleMaxHeight = @"DHStyleMaxHeight"; + +static NSString *DHFormattedStringTagIdentifier = @"identifier"; +static NSString *DHFormattedStringTagValue = @"value"; +static NSString *DHFormattedStringTagKind = @"kind"; + +typedef enum DHFormattedStringTagKinds : NSInteger { + DHFormattedStringTagKindOpenTag, + DHFormattedStringTagKindCloseTag +} DHFormattedStringTagKinds; + +- (id)initWithIdentifier:(id)identifier { + if (!(self = [super initWithIdentifier:identifier])) { + return nil; + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeIdle; +} + +- (id)_style:(id)style in:(NSDictionary *)dictionary { + id result = [dictionary objectForKey:style]; + return result ? result : @(NO); +} + +- (NSDictionary *)_attributesDictionaryFromStyles:(NSDictionary *)styles { + NSUInteger fontSize = [[self _style:DHStyleFontSize in:styles] integerValue]; + NSFont *font = [NSFont fontWithName: [self _style:DHStyleFontName in:styles] + size:fontSize]; + + if (!font) { + font = [NSFont fontWithName:@"Helvetica" size:fontSize]; + } + + if ([[self _style:DHStyleBold in:styles] boolValue] == YES) { + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + font = [fontManager convertFont:font toHaveTrait:NSBoldFontMask]; + } + + return @{ + NSFontAttributeName: font, + NSForegroundColorAttributeName: [self _style:DHStyleColor in:styles], + NSUnderlineStyleAttributeName: [self _style:DHStyleUnderline in:styles], + NSStrikethroughStyleAttributeName: [self _style:DHStyleStrikethrough in:styles], + }; +} + +- (NSImage *)_imageFromAttributedString:(NSAttributedString *)attributedString withMaxSize:(NSSize)maxSize { + + NSSize size = [attributedString size]; + if (!(size.width && size.height)) { + return nil; + } + + BOOL constrainWidth = ([@(maxSize.width) integerValue]) ? YES : NO; + BOOL constrainHeight = ([@(maxSize.height) integerValue]) ? YES : NO; + + CGFloat imageWidth = constrainWidth ? maxSize.width : size.width; + + NSStringDrawingOptions stringDrawingOptions = (NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading | NSStringDrawingTruncatesLastVisibleLine); + + NSRect boundingRect = [attributedString boundingRectWithSize:CGSizeMake(imageWidth, CGFLOAT_MAX) options:stringDrawingOptions]; + if (constrainWidth) { + boundingRect.size.width = maxSize.width; + } + if (constrainHeight) { + boundingRect.size.height = MIN(boundingRect.size.height, maxSize.height); + } + + NSImage *image = [[NSImage alloc] initWithSize:boundingRect.size]; + [image lockFocus]; + { + [attributedString drawWithRect:boundingRect options:stringDrawingOptions]; + } + [image unlockFocus]; + + return image; +} + +- (NSDictionary *)_informationForTag:(NSString *)tag { + BOOL isCloseTag = [tag hasPrefix:@"/"]; + if (isCloseTag && tag.length > 1) { + tag = [tag substringFromIndex:1]; + } + + NSArray *components = [tag componentsSeparatedByUnescapedDelimeter:@"="]; + NSString *tagIdentifier = components.count > 0 ? [components objectAtIndex:0] : @""; + NSString *tagValue = components.count > 1 ? [components objectAtIndex:1] : @""; + + // Trim whitespace + tagIdentifier = [tagIdentifier stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + tagValue = [tagValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + // Remove proceeding and trailing quotes + if ([tagValue hasPrefix:@"\""] && tag.length > 1) { + tagValue = [tagValue substringFromIndex:1]; + } + if ([tagValue hasSuffix:@"\""] && tag.length > 1) { + tagValue = [tagValue substringToIndex:tagValue.length - 1]; + } + + return @{DHFormattedStringTagIdentifier: tagIdentifier, + DHFormattedStringTagValue: tagValue, + DHFormattedStringTagKind : isCloseTag ? @(DHFormattedStringTagKindCloseTag) : @(DHFormattedStringTagKindOpenTag)}; +} + + +- (void)_applyTag:(NSString *)tag toStyles:(NSMutableDictionary **)styles { + if (!styles) { + return; + } + + NSDictionary *tagInformation = [self _informationForTag:tag]; + + NSString *tagIdentifier = tagInformation[DHFormattedStringTagIdentifier]; + NSString *tagValue = tagInformation[DHFormattedStringTagValue]; + + DHFormattedStringTagKinds tagKind = [tagInformation[DHFormattedStringTagKind] integerValue]; + BOOL isCloseTag = (tagKind == DHFormattedStringTagKindCloseTag) ? YES : NO; + + NSDictionary *styleTermonologyDictionary = @{ + @"b": DHStyleBold, + @"i": DHStyleItalic, + @"u": DHStyleUnderline, + @"s": DHStyleStrikethrough, + @"color": DHStyleColor, + @"font": DHStyleFontName, + @"size": DHStyleFontSize, + @"width": DHStyleMaxWidth, + @"height": DHStyleMaxHeight + }; + + NSString *styleKey = styleTermonologyDictionary[tagIdentifier]; + id styleValue = tagValue; + + if ([styleKey isEqualToString:DHStyleColor]) { + styleValue = [NSColor colorWithHexString:tagValue]; + } else if ([styleKey isEqualToString:DHStyleBold] || + [styleKey isEqualToString:DHStyleItalic] || + [styleKey isEqualToString:DHStyleUnderline] || + [styleKey isEqualToString:DHStyleStrikethrough]) { + styleValue = isCloseTag ? @(NO) : @(YES); + } + + if (styleKey) { + [*styles setObject:styleValue forKey:styleKey]; + } +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!inputString.wasUpdated) { + return YES; + } + + NSMutableDictionary *currentStyles = + [@{ + DHStyleColor: [NSColor whiteColor], + DHStyleFontName: @"Helvetica", + DHStyleFontSize: @20, + DHStyleBold: @(NO), + DHStyleUnderline: @(NO), + DHStyleStrikethrough: @(NO), + DHStyleMaxWidth: @0, + DHStyleMaxHeight: @0 + } mutableCopy]; + + NSMutableDictionary *rangedStylesDictionary = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *openTags = [NSMutableDictionary dictionary]; + + NSString *string = inputString.stringValue; + NSScanner *scanner = [[NSScanner alloc] initWithString:string]; + [scanner setCharactersToBeSkipped:nil]; + + NSMutableString *unformattedBuffer = [[NSMutableString alloc] init]; + while (YES) { + NSString *content = nil; + [scanner scanUpToString:@"<" intoString:&content]; + if (content) { + [unformattedBuffer appendString:content]; + } + + if ([scanner isAtEnd]) { + break; + } + + if ([content hasSuffix:@"\\"]) { + [scanner scanString:@"<" intoString:NULL]; + [unformattedBuffer deleteCharactersInRange:NSMakeRange(unformattedBuffer.length - 1, 1)]; + [unformattedBuffer appendString:@"<"]; + continue; + } + + NSString *tag = nil; + [scanner scanString:@"<" intoString:NULL]; + [scanner scanUpToString:@">" intoString:&tag]; + + NSDictionary *tagInformation = [self _informationForTag:tag]; + NSString *tagIdentifier = tagInformation[DHFormattedStringTagIdentifier]; + BOOL isCloseTag = [tag hasPrefix:@"/"]; + + if (isCloseTag) { + NSUInteger startRange = 0; + id startRangeOfOpenTag = openTags[tagIdentifier]; + if (startRangeOfOpenTag) { + startRange = [startRangeOfOpenTag integerValue]; + [openTags removeObjectForKey:tagIdentifier]; + } + + [rangedStylesDictionary setObject:[currentStyles copy] forKey:NSStringFromRange(NSMakeRange(startRange, unformattedBuffer.length - startRange))]; + } else { + if (unformattedBuffer.length && !openTags[tagIdentifier]) { + openTags[tagIdentifier] = @(unformattedBuffer.length); + } + } + + [self _applyTag:tag toStyles:¤tStyles]; + [scanner scanString:@">" intoString:NULL]; + }; + + string = unformattedBuffer; + + NSDictionary *attributesDictionary = [self _attributesDictionaryFromStyles:currentStyles]; + NSMutableAttributedString *attributedString = [[[NSAttributedString alloc] initWithString:string attributes:attributesDictionary] mutableCopy]; + + for (NSString *rangeKey in rangedStylesDictionary) { + NSRange range = NSRangeFromString(rangeKey); + NSDictionary *rangedStyles = [rangedStylesDictionary objectForKey:rangeKey]; + NSDictionary *rangedAttributes = [self _attributesDictionaryFromStyles:rangedStyles]; + [attributedString addAttributes:rangedAttributes range:range]; + } + + NSSize maxSize = NSMakeSize([[self _style:DHStyleMaxWidth in:currentStyles] floatValue], + [[self _style:DHStyleMaxHeight in:currentStyles] floatValue]); + + NSImage *image = [self _imageFromAttributedString:attributedString withMaxSize:maxSize]; + + outputImage.imageValue = [[QCImage alloc] initWithNSImage:image options:0]; + outputUnformattedString.stringValue = string; + + return YES; +} + +@end diff --git a/Origami Plugin/DHImageWithFormattedStrings.xml b/Origami Plugin/DHImageWithFormattedStrings.xml new file mode 100644 index 0000000..16beb29 --- /dev/null +++ b/Origami Plugin/DHImageWithFormattedStrings.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Creates an image of a string generated by the String Formatter patch. This lets you put together runs of styled text. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Image With Formatted String + + inputAttributes + + inputString + + description + + name + String + + + outputAttributes + + outputImage + + description + + name + Image + + outputUnformattedString + + description + + name + Unformatted String + + + + diff --git a/Origami Plugin/DHJSONImporterPatch.h b/Origami Plugin/DHJSONImporterPatch.h new file mode 100644 index 0000000..8e1612f --- /dev/null +++ b/Origami Plugin/DHJSONImporterPatch.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHJSONImporterPatch : QCPatch +{ + QCStringPort *inputJSONLocation; + QCBooleanPort *inputUpdateSignal; + QCStructurePort *outputParsedJSON; + QCBooleanPort *outputDoneSignal; +} + +@end diff --git a/Origami Plugin/DHJSONImporterPatch.m b/Origami Plugin/DHJSONImporterPatch.m new file mode 100644 index 0000000..53f4116 --- /dev/null +++ b/Origami Plugin/DHJSONImporterPatch.m @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHJSONImporterPatch.h" +#import "SBJson.h" +#import "NSURL+FBAdditions.h" +#import "QCPatch+FBAdditions.h" + +@implementation DHJSONImporterPatch + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + inputUpdateSignal.booleanValue = YES; + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeIdle; +} + +- (void)_execute:(id)sender { + outputDoneSignal.booleanValue = NO; + + if (!(inputJSONLocation.wasUpdated || (inputUpdateSignal.wasUpdated && inputUpdateSignal.booleanValue == YES))) { + return; + } + + SBJsonParser *parser = [[SBJsonParser alloc] init]; + + NSError *error; + NSURL *url = [NSURL URLWithQuartzComposerLocation:inputJSONLocation.stringValue relativeToDocument:self.fb_document]; + NSString *jsonText = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error]; + + NSUInteger arrayStartLocation = [jsonText rangeOfString:@"["].location; + NSUInteger dictionaryStartLocation = [jsonText rangeOfString:@"{"].location; + NSUInteger startLocation = NSNotFound; + + if (arrayStartLocation == NSNotFound) { + startLocation = dictionaryStartLocation; + } else if (dictionaryStartLocation == NSNotFound) { + startLocation = arrayStartLocation; + } else { + startLocation = MIN(arrayStartLocation, dictionaryStartLocation); + } + + if (startLocation != NSNotFound) { + jsonText = [jsonText substringFromIndex:startLocation]; + } + + id parsedResult = [parser objectWithString:jsonText]; + if (parsedResult) { + if ([parsedResult isKindOfClass:[NSArray class]]) { + outputParsedJSON.structureValue = [[QCStructure alloc] initWithArray:parsedResult]; + } else if ([parsedResult isKindOfClass:[NSDictionary class]]) { + outputParsedJSON.structureValue = [[QCStructure alloc] initWithDictionary:parsedResult]; + } + } else { + outputParsedJSON.structureValue = nil; + } + + outputDoneSignal.booleanValue = YES; + return; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + [NSThread detachNewThreadSelector:@selector(_execute:) toTarget:self withObject:self]; + return YES; +} + +@end diff --git a/Origami Plugin/DHJSONImporterPatch.xml b/Origami Plugin/DHJSONImporterPatch.xml new file mode 100644 index 0000000..654eb14 --- /dev/null +++ b/Origami Plugin/DHJSONImporterPatch.xml @@ -0,0 +1,63 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Downloads and parses JSON from a specified URL or file path. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + JSON Importer + + inputAttributes + + inputJSONLocation + + description + + name + Location + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputParsedJSON + + description + + name + Structure + + outputDoneSignal + + description + + name + Done Signal + + + + diff --git a/Origami Plugin/DHRESTRequest.h b/Origami Plugin/DHRESTRequest.h new file mode 100644 index 0000000..611a65d --- /dev/null +++ b/Origami Plugin/DHRESTRequest.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +static NSString *DHRESTRequestTypeCreate = @"POST"; +static NSString *DHRESTRequestTypeRead = @"GET"; +static NSString *DHRESTRequestTypeUpdate = @"PUT"; +static NSString *DHRESTRequestTypeDestroy = @"DELETE"; + +@interface DHRESTRequest : NSObject + ++ (id)createObject:(id)object atURL:(NSURL *)URL; ++ (id)readObjectAtURL:(NSURL *)URL; ++ (id)updateObjectWith:(id)objectChanges atURL:(NSURL *)URL; ++ (id)destroyObjectAtURL:(NSURL *)URL; + ++ (id)resultOfRequestType:(NSString *)requestType withObject:(id)object toURL:(NSURL *)URL; ++ (id)resultOfRequestType:(NSString *)requestType withObject:(id)object toURL:(NSURL *)URL withParameters:(NSDictionary *)parameters headers:(NSDictionary *)headers; + +@end diff --git a/Origami Plugin/DHRESTRequest.m b/Origami Plugin/DHRESTRequest.m new file mode 100644 index 0000000..efcfa05 --- /dev/null +++ b/Origami Plugin/DHRESTRequest.m @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHRESTRequest.h" +#import "Sbjson.h" + +static BOOL _DHRestRequestDebug = NO; + +static NSString * (^URLEncodeString)(NSString *) = ^ NSString * (NSString *string) { + return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), kCFStringEncodingUTF8)); +}; + +@implementation DHRESTRequest + ++ (id)createObject:(id)object atURL:(NSURL *)URL { + return [self resultOfRequestType:DHRESTRequestTypeCreate withObject:object toURL:URL]; +} + ++ (id)readObjectAtURL:(NSURL *)URL { + return [self resultOfRequestType:DHRESTRequestTypeRead withObject:nil toURL:URL]; +} + ++ (id)updateObjectWith:(id)objectChanges atURL:(NSURL *)URL { + return [self resultOfRequestType:DHRESTRequestTypeUpdate withObject:objectChanges toURL:URL]; +} + ++ (id)destroyObjectAtURL:(NSURL *)URL { + return [self resultOfRequestType:DHRESTRequestTypeDestroy withObject:nil toURL:URL]; +} + ++ (id)resultOfRequestType:(NSString *)requestType withObject:(id)object toURL:(NSURL *)URL { + return [self resultOfRequestType:requestType withObject:object toURL:URL withParameters:nil headers:nil]; +} + ++ (id)resultOfRequestType:(NSString *)requestType withObject:(id)object toURL:(NSURL *)URL withParameters:(NSDictionary *)parameters headers:(NSDictionary *)headers { + SBJsonWriter *jsonWriter; + + NSData *jsonData = nil; + if (object && ([requestType isEqualToString:DHRESTRequestTypeCreate] || [requestType isEqualToString:DHRESTRequestTypeUpdate])) { + jsonWriter = [[SBJsonWriter alloc] init]; + NSString *json = ([object isKindOfClass:[NSString class]]) ? object : [jsonWriter stringWithObject:object]; + jsonData = [json dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:NO]; + } + + // Add parameters + if (parameters && [parameters count]) { + NSMutableArray *queryParameters = [NSMutableArray arrayWithCapacity:[parameters count]]; + [parameters enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) { + NSMutableString *parameter = [NSMutableString string]; + [parameter appendString:URLEncodeString(key)]; + [parameter appendString:@"="]; + if (![obj isKindOfClass:[NSNull class]]) { + [parameter appendString:URLEncodeString(obj)]; + } + [queryParameters addObject:parameter]; + }]; + NSString *parametersString = [@"?" stringByAppendingString:[queryParameters componentsJoinedByString:@"&"]]; + NSString *URLWithParametersString = [[URL absoluteString] stringByAppendingString:parametersString]; + URL = [NSURL URLWithString:URLWithParametersString]; + } + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + [request setURL:URL]; + [request setHTTPMethod:requestType]; + [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + if (headers) { + for (id key in headers) { + id value = headers[key]; + NSString *keyString = ([key isKindOfClass:[NSString class]]) ? key : [key description]; + [request setValue:value forHTTPHeaderField:keyString]; + } + } + if (jsonData) { + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [request setHTTPBody:jsonData]; + } + + if (_DHRestRequestDebug) { + NSLog(@"Request: %@", request); + } + + NSHTTPURLResponse *response; + NSError *error; + NSData *resultData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; + if (response.statusCode != 200 && response.statusCode != 201) { + return @(NO); + } + if (!resultData) { + return @(YES); + } + + NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSASCIIStringEncoding]; + + SBJsonParser *jsonParser = [[SBJsonParser alloc] init]; + id result = [jsonParser objectWithData:resultData]; + return result ? result : resultString; +} + +@end diff --git a/Origami Plugin/DHRESTRequestPatch.h b/Origami Plugin/DHRESTRequestPatch.h new file mode 100644 index 0000000..f9f5aa7 --- /dev/null +++ b/Origami Plugin/DHRESTRequestPatch.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface DHRESTRequestPatch : QCPatch +{ + QCBooleanPort *inputEnable; + QCIndexPort *inputRequestType; + QCStringPort *inputURL; + QCStructurePort *inputParameters; + QCStructurePort *inputHeaders; + QCVirtualPort *inputObject; + QCBooleanPort *inputUpdateSignal; + QCVirtualPort *outputResult; + QCBooleanPort *outputDoneSignal; + + BOOL _debug; +} + +@end diff --git a/Origami Plugin/DHRESTRequestPatch.m b/Origami Plugin/DHRESTRequestPatch.m new file mode 100644 index 0000000..39ab401 --- /dev/null +++ b/Origami Plugin/DHRESTRequestPatch.m @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHRESTRequestPatch.h" +#import "DHRESTRequest.h" +#import "SBJson.h" + +typedef enum DHRESTRequestPatchRequestType : NSInteger { + DHRESTRequestPatchRequestTypeCreate, + DHRESTRequestPatchRequestTypeRead, + DHRESTRequestPatchRequestTypeUpdate, + DHRESTRequestPatchRequestTypeDestroy, + DHRESTRequestPatchRequestTypeCount +} DHRESTRequestPatchRequestType; + +@implementation DHRESTRequestPatch + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + inputRequestType.indexValue = DHRESTRequestPatchRequestTypeRead; + inputRequestType.maxIndexValue = DHRESTRequestPatchRequestTypeCount - 1; + + _debug = NO; + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProvider; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (void)_execute:(id)sender { + outputDoneSignal.booleanValue = NO; + if (!(inputEnable.wasUpdated || inputRequestType.wasUpdated || inputURL.wasUpdated || inputObject.wasUpdated || inputHeaders.wasUpdated || (inputUpdateSignal.wasUpdated && inputUpdateSignal.booleanValue == YES))) { + return; + } + + id requestObject = inputObject.rawValue; + if ([requestObject isKindOfClass:[QCStructure class]]) { + requestObject = [requestObject dictionaryRepresentation]; + } + + NSURL *requestURL = [NSURL URLWithString:inputURL.stringValue]; + NSDictionary *parameters = [inputParameters.structureValue dictionaryRepresentation]; + NSDictionary *headers = [inputHeaders.structureValue dictionaryRepresentation]; + + NSString *requestType = nil; + switch (inputRequestType.indexValue) { + case DHRESTRequestPatchRequestTypeCreate: + requestType = DHRESTRequestTypeCreate; + break; + case DHRESTRequestPatchRequestTypeRead: + requestType = DHRESTRequestTypeRead; + break; + case DHRESTRequestPatchRequestTypeUpdate: + requestType = DHRESTRequestTypeUpdate; + break; + case DHRESTRequestPatchRequestTypeDestroy: + requestType = DHRESTRequestTypeDestroy; + break; + } + + if (_debug) NSLog(@"%@ with %@ to %@ with %@", requestType, requestObject, requestURL, headers); + + id result = [DHRESTRequest resultOfRequestType:requestType withObject:requestObject toURL:requestURL withParameters:parameters headers:headers]; + + if (_debug) NSLog(@"Result: %@", result); + + outputResult.rawValue = result; + outputDoneSignal.booleanValue = YES; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (inputEnable.booleanValue == NO) { + outputResult.rawValue = nil; + outputDoneSignal.booleanValue = NO; + return YES; + } + + [NSThread detachNewThreadSelector:@selector(_execute:) toTarget:self withObject:self]; + return YES; +} + +@end diff --git a/Origami Plugin/DHRESTRequestPatch.xml b/Origami Plugin/DHRESTRequestPatch.xml new file mode 100644 index 0000000..d023eb5 --- /dev/null +++ b/Origami Plugin/DHRESTRequestPatch.xml @@ -0,0 +1,105 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Performs a REST request and returns the result. Supports "Create" (POST), "Read" (GET), "Update" (PUT) and "Destroy" (DELETE) request types. Allows attaching an object to a "Create" or "Update" request and specification of additional headers for any request. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + REST Request + + inputAttributes + + inputEnable + + description + + name + Enable + + inputRequestType + + description + + name + Type + menu + + Create (POST) + Read (GET) + Update (PUT) + Destroy (DELETE) + + + inputURL + + description + + name + URL + + inputParameters + + description + + name + Parameters + + inputHeaders + + description + + name + Headers + + inputObject + + description + + name + Object + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputResult + + description + + name + Result + + outputDoneSignal + + description + + name + Done Signal + + + + diff --git a/Origami Plugin/DHSoundPlayerProPatch.h b/Origami Plugin/DHSoundPlayerProPatch.h new file mode 100644 index 0000000..ee070ed --- /dev/null +++ b/Origami Plugin/DHSoundPlayerProPatch.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import +#import + +@interface DHSoundPlayerProPatch : QCPatch +{ + QCStringPort *inputSoundLocation; + QCBooleanPort *inputPlaySound; + QCBooleanPort *inputResetSignal; + QCBooleanPort *inputLooping; + QCNumberPort *inputVolume; + + QTMovieView *_movieView; +} + +@end diff --git a/Origami Plugin/DHSoundPlayerProPatch.m b/Origami Plugin/DHSoundPlayerProPatch.m new file mode 100644 index 0000000..51792d3 --- /dev/null +++ b/Origami Plugin/DHSoundPlayerProPatch.m @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHSoundPlayerProPatch.h" +#import "QCPatch+FBAdditions.h" +#import "NSURL+FBAdditions.h" + +@implementation DHSoundPlayerProPatch + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + inputVolume.minDoubleValue = 0; + inputVolume.maxDoubleValue = 1; + inputVolume.doubleValue = 1; + + _movieView = [[QTMovieView alloc] init]; + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeConsumer; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (void)disable:(QCOpenGLContext*)context { + [_movieView setMovie:nil]; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputSoundLocation.wasUpdated || inputPlaySound.wasUpdated || inputResetSignal.wasUpdated || inputLooping.wasUpdated || inputVolume.wasUpdated)) { + return YES; + } + + if (inputSoundLocation.wasUpdated || ![_movieView movie]) { + NSURL *soundLocationURL = [NSURL URLWithQuartzComposerLocation:inputSoundLocation.stringValue relativeToDocument:self.fb_document]; + + NSError *error; + [_movieView setMovie:[QTMovie movieWithURL:soundLocationURL error:&error]]; + } + + QTMovie *movie = [_movieView movie]; + [movie setAttribute:@(inputLooping.booleanValue) forKey:QTMovieLoopsAttribute]; + [movie setVolume:inputVolume.doubleValue]; + + if (inputResetSignal.booleanValue) { + [movie gotoBeginning]; + } + + if (inputPlaySound.booleanValue) { + [_movieView play:self]; + } else { + [_movieView pause:self]; + } + + return YES; +} + +@end diff --git a/Origami Plugin/DHSoundPlayerProPatch.xml b/Origami Plugin/DHSoundPlayerProPatch.xml new file mode 100644 index 0000000..cf09a53 --- /dev/null +++ b/Origami Plugin/DHSoundPlayerProPatch.xml @@ -0,0 +1,67 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Plays any QuickTime file as a sound. Supports pausing, reseting, looping, and adjusting the volume. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Sound Player Pro + + inputAttributes + + inputSoundLocation + + description + + name + Sound Location + + inputPlaySound + + description + + name + Play Sound + + inputResetSignal + + description + + name + Reset Signal + + inputLooping + + description + + name + Looping + + inputVolume + + description + + name + Volume + + + + diff --git a/Origami Plugin/DHStringAtURLPatch.h b/Origami Plugin/DHStringAtURLPatch.h new file mode 100644 index 0000000..fc7f5ae --- /dev/null +++ b/Origami Plugin/DHStringAtURLPatch.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface DHStringAtURLPatch : QCPatch +{ + QCBooleanPort *inputEnable; + QCStringPort *inputURL; + QCBooleanPort *inputUpdateSignal; + QCStringPort *outputString; + QCBooleanPort *outputDoneSignal; +} + +@end diff --git a/Origami Plugin/DHStringAtURLPatch.m b/Origami Plugin/DHStringAtURLPatch.m new file mode 100644 index 0000000..860e01c --- /dev/null +++ b/Origami Plugin/DHStringAtURLPatch.m @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHStringAtURLPatch.h" +#import "QCPatch+FBAdditions.h" +#import "NSURL+FBAdditions.h" + +@implementation DHStringAtURLPatch + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputEnable.wasUpdated || inputURL.wasUpdated || inputUpdateSignal.wasUpdated)) { + return YES; + } + + outputString.stringValue = nil; + outputDoneSignal.booleanValue = NO; + + if (!inputEnable.booleanValue) { + return YES; + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + NSError *error; + outputString.stringValue = [NSString stringWithContentsOfURL:[NSURL URLWithQuartzComposerLocation:inputURL.stringValue relativeToDocument:self.fb_document] encoding:NSUTF8StringEncoding error:&error]; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + outputDoneSignal.booleanValue = YES; + }); + }); + + return YES; +} + +@end diff --git a/Origami Plugin/DHStringAtURLPatch.xml b/Origami Plugin/DHStringAtURLPatch.xml new file mode 100644 index 0000000..325a0bd --- /dev/null +++ b/Origami Plugin/DHStringAtURLPatch.xml @@ -0,0 +1,70 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Asynchronously downloads the contents of a URL. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + String at URL + + inputAttributes + + inputEnable + + description + + name + Enable + + inputURL + + description + + name + URL + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputString + + description + + name + String + + outputDoneSignal + + description + + name + Done Signal + + + + diff --git a/Origami Plugin/DHStringFormatterPatch.h b/Origami Plugin/DHStringFormatterPatch.h new file mode 100644 index 0000000..94562ae --- /dev/null +++ b/Origami Plugin/DHStringFormatterPatch.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHStringFormatterPatch : QCPatch +{ + QCStringPort *inputString; + QCColorPort *inputColor; + QCStringPort *inputFontName; + QCNumberPort *inputFontSize; + QCBooleanPort *inputBold; + QCBooleanPort *inputUnderline; + QCBooleanPort *inputStrikethrough; + QCNumberPort *inputMaxWidth; + QCNumberPort *inputMaxHeight; + QCStringPort *outputFormattedString; +} + +@end diff --git a/Origami Plugin/DHStringFormatterPatch.m b/Origami Plugin/DHStringFormatterPatch.m new file mode 100644 index 0000000..0437392 --- /dev/null +++ b/Origami Plugin/DHStringFormatterPatch.m @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHStringFormatterPatch.h" +#import "NSColor+HTMLExtensions.h" + +@implementation DHStringFormatterPatch + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeIdle; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputString.wasUpdated || + inputColor.wasUpdated || + inputFontName.wasUpdated || + inputFontSize.wasUpdated || + inputBold.wasUpdated || + inputUnderline.wasUpdated || + inputStrikethrough.wasUpdated || + inputMaxWidth.wasUpdated || + inputMaxHeight.wasUpdated)) { + return YES; + } + + NSString *output = inputString.stringValue; + + if (!output.length) { + outputFormattedString.stringValue = @""; + return YES; + } + + // Escape existing "tags" + output = [output stringByReplacingOccurrencesOfString:@"<" withString:@"\\<"]; + + if (inputStrikethrough.booleanValue) { + output = [NSString stringWithFormat:@"%@", output]; + } + + if (inputUnderline.booleanValue) { + output = [NSString stringWithFormat:@"%@", output]; + } + + if (inputBold.booleanValue) { + output = [NSString stringWithFormat:@"%@", output]; + } + + if (inputFontSize.doubleValue) { + output = [NSString stringWithFormat:@"%@", (int)inputFontSize.doubleValue, output]; + } + + if (inputFontName.stringValue.length) { + output = [NSString stringWithFormat:@"%@", inputFontName.stringValue, output]; + } + + NSColor *defaultColor = [[NSColor whiteColor] colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + if (inputColor.value && !([inputColor.value isEqualTo:defaultColor])) { + CGFloat red, green, blue, alpha; + [inputColor getRed:&red green:&green blue:&blue alpha:&alpha]; + NSColor *color = [NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha]; + NSString *textualColorRepresentation = [color hexStringRepresentation]; + + if (textualColorRepresentation) { + output = [NSString stringWithFormat:@"%@", textualColorRepresentation, output]; + } + } + + if (inputMaxHeight.doubleValue) { + output = [NSString stringWithFormat:@"%@", (int)inputMaxHeight.doubleValue, output]; + } + + if (inputMaxWidth.doubleValue) { + output = [NSString stringWithFormat:@"%@", (int)inputMaxWidth.doubleValue, output]; + } + + outputFormattedString.stringValue = output; + + return YES; +} + +@end diff --git a/Origami Plugin/DHStringFormatterPatch.xml b/Origami Plugin/DHStringFormatterPatch.xml new file mode 100644 index 0000000..5ce55e4 --- /dev/null +++ b/Origami Plugin/DHStringFormatterPatch.xml @@ -0,0 +1,105 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Creates a formatted string for use by the Image With Formatted String patch. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + String Formatter + + inputAttributes + + inputString + + description + + name + String + + inputColor + + description + + name + Color + + inputFontName + + description + + name + Font Name + + inputFontSize + + description + + name + Font Size + + inputBold + + description + + name + Bold + + inputUnderline + + description + + name + Underline + + inputStrikethrough + + description + + name + Strikethrough + + inputMaxWidth + + description + + name + Width + + inputMaxHeight + + description + + name + Max Height + + + outputAttributes + + outputFormattedString + + description + + name + Formatted String + + + + diff --git a/Origami Plugin/DHStructureAllKeysPatch.h b/Origami Plugin/DHStructureAllKeysPatch.h new file mode 100644 index 0000000..c6713fd --- /dev/null +++ b/Origami Plugin/DHStructureAllKeysPatch.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHStructureAllKeysPatch : QCPatch +{ + QCStructurePort *inputStructure; + QCBooleanPort *inputSort; + QCStructurePort *outputStructure; +} + +@end diff --git a/Origami Plugin/DHStructureAllKeysPatch.m b/Origami Plugin/DHStructureAllKeysPatch.m new file mode 100644 index 0000000..354151d --- /dev/null +++ b/Origami Plugin/DHStructureAllKeysPatch.m @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHStructureAllKeysPatch.h" +#import "NSArray+FBAdditions.h" + +@implementation DHStructureAllKeysPatch + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputStructure.wasUpdated || inputSort.wasUpdated)) { + return YES; + } + + NSArray *allKeys = [[inputStructure.structureValue dictionaryRepresentation] allKeys]; + + if (inputSort.booleanValue) { + allKeys = [allKeys sortedArrayUsingAlphabeticalSort]; + } + + outputStructure.structureValue = [[QCStructure alloc] initWithArray:allKeys]; + + return YES; +} + +@end diff --git a/Origami Plugin/DHStructureAllKeysPatch.xml b/Origami Plugin/DHStructureAllKeysPatch.xml new file mode 100644 index 0000000..3767318 --- /dev/null +++ b/Origami Plugin/DHStructureAllKeysPatch.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Returns the keys from a named structure. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure All Keys + + inputAttributes + + inputStructure + + description + + name + Structure + + inputSort + + description + + name + Sort + + + outputAttributes + + outputStructure + + description + + name + Keys + + + + diff --git a/Origami Plugin/DHStructureAllValuesPatch.h b/Origami Plugin/DHStructureAllValuesPatch.h new file mode 100644 index 0000000..b6d26b2 --- /dev/null +++ b/Origami Plugin/DHStructureAllValuesPatch.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHStructureAllValuesPatch : QCPatch +{ + QCStructurePort *inputStructure; + QCIndexPort *inputSort; + QCStructurePort *outputStructure; +} + +typedef enum DHStructureSortMode : NSUInteger { + DHStructureSortModeNone, + DHStructureSortModeUsingKeys, + DHStructureSortModeUsingValues, + DHStructureSortModeCount +} DHStructureSortMode; + +@end diff --git a/Origami Plugin/DHStructureAllValuesPatch.m b/Origami Plugin/DHStructureAllValuesPatch.m new file mode 100644 index 0000000..620e22e --- /dev/null +++ b/Origami Plugin/DHStructureAllValuesPatch.m @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHStructureAllValuesPatch.h" +#import "NSArray+FBAdditions.h" + +@implementation DHStructureAllValuesPatch + +- (id)initWithIdentifier:(id)identifier { + if (self = [super initWithIdentifier:identifier]) { + inputSort.maxIndexValue = DHStructureSortModeCount - 1; + } + + return self; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputStructure.wasUpdated || inputSort.wasUpdated)) { + return YES; + } + + NSDictionary *dictionary = [inputStructure.structureValue dictionaryRepresentation]; + NSArray *allValues = [dictionary allValues]; + + if (inputSort.indexValue == DHStructureSortModeUsingKeys) { + NSMutableArray *sortedValues = [[NSMutableArray alloc] initWithCapacity:[allValues count]]; + NSArray *sortedKeys = [[dictionary allKeys] sortedArrayUsingAlphabeticalSort]; + for (id key in sortedKeys) { + id value = dictionary[key]; + if (value) { + [sortedValues addObject:value]; + } + } + allValues = sortedValues; + } else if (inputSort.indexValue == DHStructureSortModeUsingValues) { + allValues = [allValues sortedArrayUsingAlphabeticalSort]; + } + + outputStructure.structureValue = [[QCStructure alloc] initWithArray:allValues]; + + return YES; +} + +@end diff --git a/Origami Plugin/DHStructureAllValuesPatch.xml b/Origami Plugin/DHStructureAllValuesPatch.xml new file mode 100644 index 0000000..d52798c --- /dev/null +++ b/Origami Plugin/DHStructureAllValuesPatch.xml @@ -0,0 +1,62 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Returns the values from a structure. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure All Values + + inputAttributes + + inputStructure + + description + + name + Structure + + inputSort + + description + + name + Sort + menu + + None + Using Keys + Using Values + + + + outputAttributes + + outputStructure + + description + + name + Values + + + + diff --git a/Origami Plugin/DHStructureMultiplePathMembersPatch.h b/Origami Plugin/DHStructureMultiplePathMembersPatch.h new file mode 100644 index 0000000..3ae2e54 --- /dev/null +++ b/Origami Plugin/DHStructureMultiplePathMembersPatch.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHStructureMultiplePathMembersPatch : QCPatch +{ + QCStructurePort *inputStructure; + QCStringPort *inputPaths; + QCStructurePort *outputStructure; +} + +@end diff --git a/Origami Plugin/DHStructureMultiplePathMembersPatch.m b/Origami Plugin/DHStructureMultiplePathMembersPatch.m new file mode 100644 index 0000000..94d79b4 --- /dev/null +++ b/Origami Plugin/DHStructureMultiplePathMembersPatch.m @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHStructureMultiplePathMembersPatch.h" +#import "DHStructurePathMemberPatch.h" +#import "NSString+FBAdditions.h" + +@implementation DHStructureMultiplePathMembersPatch : QCPatch + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputStructure.wasUpdated || inputPaths.wasUpdated)) { + return YES; + } + + NSDictionary *dictionary = [inputStructure.structureValue dictionaryRepresentation]; + NSString *searchPaths = inputPaths.stringValue; + NSMutableDictionary *results = [[NSMutableDictionary alloc] init]; + + NSArray *paths = [searchPaths componentsSeparatedByUnescapedDelimeter:@","]; + for (NSString *path in paths) { + NSArray *keyAndValue = [path componentsSeparatedByUnescapedDelimeter:@"="]; + if (![keyAndValue count]) { + continue; + } + + NSString *key = [keyAndValue[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *value = [[keyAndValue lastObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + NSArray *keyComponents = [key componentsSeparatedByUnescapedDelimeter:@"/"]; + + id result = [DHStructurePathMemberPatch memberForPath:value inDictionary:dictionary untraversedPaths:NULL]; + if (result) { + if (keyComponents.count == 1) { + results[key] = result; + } else { + NSMutableDictionary *parentBucket = results; + NSMutableDictionary *bucket; + NSUInteger componentIndex; + + for (componentIndex = 0; componentIndex < (keyComponents.count - 1); componentIndex++) { + bucket = parentBucket[keyComponents[componentIndex]]; + if (bucket) { + bucket = [bucket mutableCopy]; + } else { + bucket = [[NSMutableDictionary alloc] init]; + } + parentBucket[keyComponents[componentIndex]] = bucket; + parentBucket = bucket; + } + + bucket[keyComponents.lastObject] = result; + } + } + } + + if (results && [results count]) { + outputStructure.structureValue = [[QCStructure alloc] initWithDictionary:results]; + } else { + outputStructure.structureValue = nil; + } + + return YES; +} + + +@end diff --git a/Origami Plugin/DHStructureMultiplePathMembersPatch.xml b/Origami Plugin/DHStructureMultiplePathMembersPatch.xml new file mode 100644 index 0000000..567dab1 --- /dev/null +++ b/Origami Plugin/DHStructureMultiplePathMembersPatch.xml @@ -0,0 +1,58 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Returns a structure containing the results from multiple search paths, separated by commas. Optionally specify labels for the new structure with equal signs and optionally cluster label sub-groups with a forward slash. + +For example, the path "id = story_id, user/name = actors.0.name.text, user/age = actors.0.age" would yield a structure with two top-level keys ("id" and "user") and two sub-level keys ("name" and "age" within "user"). All of the leaf-node children (in this example, "id", "name" and "age") will contain the values of their respective search path values. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure Multiple Path Members + + inputAttributes + + inputStructure + + description + + name + Structure + + inputPaths + + description + + name + Paths + + + outputAttributes + + outputStructure + + description + + name + Structure + + + + diff --git a/Origami Plugin/DHStructurePathMemberPatch.h b/Origami Plugin/DHStructurePathMemberPatch.h new file mode 100755 index 0000000..2675dc4 --- /dev/null +++ b/Origami Plugin/DHStructurePathMemberPatch.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface DHStructurePathMemberPatch : QCPatch +{ + QCStructurePort *inputStructure; + QCStringPort *inputPath; + QCVirtualPort *outputMember; + QCStructurePort *outputUntraversedPaths; +} + ++ (id)memberForPath:(NSString *)path inDictionary:(NSDictionary *)dictionary untraversedPaths:(NSArray **)untraversedPaths; + +@end diff --git a/Origami Plugin/DHStructurePathMemberPatch.m b/Origami Plugin/DHStructurePathMemberPatch.m new file mode 100755 index 0000000..72be44b --- /dev/null +++ b/Origami Plugin/DHStructurePathMemberPatch.m @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "DHStructurePathMemberPatch.h" +#import "NSDictionary+FBAdditions.h" +#import "NSString+FBAdditions.h" + +@implementation DHStructurePathMemberPatch : QCPatch + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)identifier { + return kQCPatchExecutionModeProcessor; +} + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)identifier { + return NO; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)identifier { + return kQCPatchTimeModeIdle; +} + ++ (id)memberForPath:(NSString *)path inDictionary:(NSDictionary *)dictionary untraversedPaths:(NSArray **)untraversedPaths { + NSMutableArray *pathComponents = [[path componentsSeparatedByUnescapedDelimeters:@[@"/",@"."] map:NULL] mutableCopy]; + NSUInteger emptyStringIndex = NSNotFound; + while ((emptyStringIndex = [pathComponents indexOfObject:@""]) != NSNotFound) { + [pathComponents removeObjectAtIndex:emptyStringIndex]; + } + + id result = [dictionary valueAtPath:pathComponents untraversedPaths:untraversedPaths]; + if ([result isKindOfClass:[NSArray class]] && [result count] == 1) { + result = [result lastObject]; + } + + return result; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if (!(inputStructure.wasUpdated || inputPath.wasUpdated)) { + return YES; + } + + NSDictionary *dictionary = [inputStructure.structureValue dictionaryRepresentation]; + if (!dictionary) { + outputUntraversedPaths.structureValue = nil; + return YES; + } + + NSArray *untraversedPaths = nil; + NSString *path = (NSString *)inputPath.stringValue; + if (![path length]) { + outputMember.rawValue = inputStructure.structureValue; + outputUntraversedPaths.structureValue = nil; + return YES; + } + + id result = [DHStructurePathMemberPatch memberForPath:path inDictionary:dictionary untraversedPaths:&untraversedPaths]; + + if (result && [result isKindOfClass:[NSArray class]]) { + outputMember.rawValue = [[QCStructure alloc] initWithArray:result]; + } else if (result && [result isKindOfClass:[NSDictionary class]]) { + outputMember.rawValue = [[QCStructure alloc] initWithDictionary:result]; + } else if (result && (result != [NSNull null])) { + outputMember.rawValue = result; + } else { + outputMember.rawValue = nil; + } + + if (untraversedPaths) { + outputUntraversedPaths.structureValue = [[QCStructure alloc] initWithArray:untraversedPaths]; + } else { + outputUntraversedPaths.structureValue = nil; + } + + return YES; +} + +@end diff --git a/Origami Plugin/DHStructurePathMemberPatch.xml b/Origami Plugin/DHStructurePathMemberPatch.xml new file mode 100755 index 0000000..5a99e6f --- /dev/null +++ b/Origami Plugin/DHStructurePathMemberPatch.xml @@ -0,0 +1,65 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch returns a member or members in a structure resulting from a path search. + +For example, if you had an indexed structure with two members, a structure with key "name", value "Cat", and a structure with key "name", value "Dog", you could use a path of "0.name" to get the value "Cat". If you used a key of "*.name", you'd get a structure with "Cat" and "Dog". + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure Path Member + + inputAttributes + + inputStructure + + description + + name + Structure + + inputPath + + description + + name + Path + + + outputAttributes + + outputMember + + description + + name + Member + + outputUntraversedPaths + + description + + name + Untraversed Paths + + + + diff --git a/Origami Plugin/Export for Origami.sketchplugin b/Origami Plugin/Export for Origami.sketchplugin index b855338..195c841 100644 --- a/Origami Plugin/Export for Origami.sketchplugin +++ b/Origami Plugin/Export for Origami.sketchplugin @@ -53,8 +53,6 @@ function main() { var group_name = ""; if (is_group(layer)) { group_name = [layer name]; - } - if (![layer isMemberOfClass:[MSArtboardGroup class]]) { inside_top_level_group = true; top_level_group_pos = calculate_real_position_for(layer); } @@ -211,27 +209,23 @@ function export_layer(layer, depth, artboard_name) { function metadata_for(layer, layer_copy) { loggle("Getting metadata for " + [layer name]); - var gkrect = [GKRect rectWithRect:[MSSliceTrimming trimmedRectForSlice:layer_copy]], + var cgrect = [MSSliceTrimming trimmedRectForSlice:layer_copy], position = calculate_real_position_for(layer), x,y,w,h, layer_hidden = [layer name].indexOf("@@hidden") > -1; x = position.x; y = position.y; - w = [gkrect width]; - h = [gkrect height]; + w = cgrect.size.width; + h = cgrect.size.height; - if ([layer isMemberOfClass:[MSArtboardGroup class]]) { + // If this is an artboard we should ignore its position (at least for now) + if (is_artboard(layer)) { loggle("Resetting x and y to 0 because artboard"); x = 0; y = 0; } - if (inside_top_level_group) { - x-= top_level_group_pos.x; - y-= top_level_group_pos.y; - loggle("Shifting x by: " + top_level_group_pos.x); - loggle("Shifting y by: " + top_level_group_pos.y); - } + loggle("Metadata for <" + [layer name] + ">: { x:"+x+", y:"+y+", width:"+w+", height:"+h+"}"); return { x: x, @@ -244,15 +238,15 @@ function metadata_for(layer, layer_copy) { } function calculate_real_position_for(layer) { - var gkrect = [GKRect rectWithRect:[MSSliceTrimming trimmedRectForSlice:layer]], + var cgrect = [MSSliceTrimming trimmedRectForSlice:layer], absrect = [layer absoluteRect]; var rulerDeltaX = [absrect rulerX] - [absrect x], rulerDeltaY = [absrect rulerY] - [absrect y], - GKRectRulerX = [gkrect x] + rulerDeltaX, - GKRectRulerY = [gkrect y] + rulerDeltaY; + CGRectRulerX = cgrect.origin.x + rulerDeltaX, + CGRectRulerY = cgrect.origin.y + rulerDeltaY; return { - x: Math.round(GKRectRulerX), - y: Math.round(GKRectRulerY) + x: Math.round(CGRectRulerX), + y: Math.round(CGRectRulerY) } } @@ -311,6 +305,10 @@ function is_group(layer) { return [layer isMemberOfClass:[MSLayerGroup class]] || [layer isMemberOfClass:[MSArtboardGroup class]] } +function is_artboard(layer) { + return [layer isMemberOfClass:[MSArtboardGroup class]]; +} + function is_symbol(layer) { return [layer parentOrSelfIsSymbol]; } diff --git a/Origami Plugin/FBDescriptionPatch.h b/Origami Plugin/FBDescriptionPatch.h new file mode 100644 index 0000000..4d3c8e4 --- /dev/null +++ b/Origami Plugin/FBDescriptionPatch.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface FBDescriptionPatch : QCPatch { + QCVirtualPort *inputObject; + QCStringPort *outputDescription; +} + +@end diff --git a/Origami Plugin/FBDescriptionPatch.m b/Origami Plugin/FBDescriptionPatch.m new file mode 100644 index 0000000..3834cf0 --- /dev/null +++ b/Origami Plugin/FBDescriptionPatch.m @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "FBDescriptionPatch.h" + +@implementation FBDescriptionPatch + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)fp8 { + return NO; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)fp8 { + return kQCPatchExecutionModeProcessor; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)fp8 { + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments +{ + if (inputObject.wasUpdated == NO) { + return YES; + } + + id theObject = [inputObject rawValue]; + + if ([theObject respondsToSelector:@selector(description)]) { + if ([theObject respondsToSelector:@selector(dictionaryRepresentation)]) { + theObject = [theObject dictionaryRepresentation]; + } + + NSString *description = [theObject description]; + [outputDescription setStringValue:description]; + } + + return YES; +} + +@end diff --git a/Origami Plugin/FBDescriptionPatch.xml b/Origami Plugin/FBDescriptionPatch.xml new file mode 100755 index 0000000..42e552b --- /dev/null +++ b/Origami Plugin/FBDescriptionPatch.xml @@ -0,0 +1,49 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch outputs a string description of an object. It should be similar to what you see in the tooltip when hovered over a port. It's helpful for inspecting complex structures. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + Description + + inputAttributes + + inputObject + + description + Object + name + Object + + + outputAttributes + + outputDescription + + description + Description + name + Description + + + + \ No newline at end of file diff --git a/Origami Plugin/FBFPS.h b/Origami Plugin/FBFPS.h new file mode 100644 index 0000000..b8bbe19 --- /dev/null +++ b/Origami Plugin/FBFPS.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface FBFPS : QCPatch +{ + QCNumberPort *outputFPS; + QCStringPort *outputFPSString; +} + +@end diff --git a/Origami Plugin/FBFPS.m b/Origami Plugin/FBFPS.m new file mode 100644 index 0000000..a40c3c5 --- /dev/null +++ b/Origami Plugin/FBFPS.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "FBFPS.h" + +@interface FBFPS () { + QCView *_qcView; + double _previousTime; +} +@end + +@implementation FBFPS + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)fp8 { + return NO; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)fp8 { + return kQCPatchExecutionModeProvider; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)fp8 { + return kQCPatchTimeModeIdle; +} + +- (void)enable:(QCOpenGLContext *)context { + id value = self._renderingInfo.context.userInfo[@".QCView"]; + _qcView = [value pointerValue]; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + if ([_qcView respondsToSelector:@selector(averageFPS)]) { + [outputFPS setDoubleValue:_qcView.averageFPS]; + + if (roundf(time * 2.0) / 2.0 != roundf(_previousTime * 2.0) / 2.0) { // Execute every 0.5s + [outputFPSString setStringValue:[NSString stringWithFormat:@"%.2f FPS",_qcView.averageFPS]]; + } + } + + _previousTime = time; + + return YES; +} + +@end diff --git a/Origami Plugin/FBFPS.xml b/Origami Plugin/FBFPS.xml new file mode 100755 index 0000000..d542b3e --- /dev/null +++ b/Origami Plugin/FBFPS.xml @@ -0,0 +1,38 @@ + + + + + nodeAttributes + + category + FPS + categories + + Environment + + copyright + Brandon Walkin + description + This patch outputs the frames per second of the composition. + name + FPS + + outputAttributes + + outputFPS + + description + Frames per second + name + FPS + + outputFPSString + + description + Frames per second string + name + FPS String + + + + \ No newline at end of file diff --git a/Origami Plugin/FBFileUpdated.h b/Origami Plugin/FBFileUpdated.h new file mode 100644 index 0000000..f90bc8a --- /dev/null +++ b/Origami Plugin/FBFileUpdated.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface FBFileUpdated : QCPatch { + QCStringPort *inputPath; + QCBooleanPort *outputUpdated; +} + +@end diff --git a/Origami Plugin/FBFileUpdated.m b/Origami Plugin/FBFileUpdated.m new file mode 100644 index 0000000..899b785 --- /dev/null +++ b/Origami Plugin/FBFileUpdated.m @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "FBFileUpdated.h" +#import "CDEvents.h" +#import "QCPatch+FBAdditions.h" +#import "NSString+RelativePath.h" + +@interface FBFileUpdated () +@property (retain, nonatomic) CDEvents *events; +@property (assign, nonatomic) NSDocument *document; +@property BOOL shouldSendUpdatePulse; +@end + +@implementation FBFileUpdated + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)fp8 { + return NO; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)fp8 { + return kQCPatchExecutionModeProvider; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)fp8 { + return kQCPatchTimeModeIdle; +} + +- (BOOL)setup:(QCOpenGLContext *)context { + self.document = [self fb_document]; + + return [super setup:context]; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments +{ + if (outputUpdated.booleanValue) { + outputUpdated.booleanValue = NO; + } + + if (self.shouldSendUpdatePulse) { + outputUpdated.booleanValue = YES; + self.shouldSendUpdatePulse = NO; + } + + if (inputPath.wasUpdated && ![inputPath.stringValue isEqualToString:@""]) { + NSString *path = inputPath.stringValue; + + NSString *filePath = path; + + if (![path isAbsolutePath] && self.document.fileURL) { + NSString *baseDirPath = [self.document.fileURL.path stringByDeletingLastPathComponent]; + filePath = [path absolutePathFromBaseDirPath:baseDirPath]; + } + + NSURL *url = [NSURL fileURLWithPath:filePath isDirectory:NO]; + + if (url) { + self.events = [[CDEvents alloc] initWithURLs:[NSArray arrayWithObject:url] block:^(CDEvents *watcher, CDEvent *event) { + self.shouldSendUpdatePulse = YES; + }]; + } + + } + + return YES; +} + +@end diff --git a/Origami Plugin/FBFileUpdated.xml b/Origami Plugin/FBFileUpdated.xml new file mode 100755 index 0000000..cef59fd --- /dev/null +++ b/Origami Plugin/FBFileUpdated.xml @@ -0,0 +1,49 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch outputs a pulse when the file at the specified path is modified. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + File Updated + + inputAttributes + + inputPath + + description + Path + name + Path + + + outputAttributes + + outputUpdated + + description + A pulse when the file is updated + name + Updated + + + + \ No newline at end of file diff --git a/Origami Plugin/FBLogToConsole.h b/Origami Plugin/FBLogToConsole.h new file mode 100644 index 0000000..58f8a3f --- /dev/null +++ b/Origami Plugin/FBLogToConsole.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface FBLogToConsole : QCPatch { + QCStringPort *inputMessage; +} + +@end diff --git a/Origami Plugin/FBLogToConsole.m b/Origami Plugin/FBLogToConsole.m new file mode 100644 index 0000000..b42b44a --- /dev/null +++ b/Origami Plugin/FBLogToConsole.m @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "FBLogToConsole.h" + +@implementation FBLogToConsole + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)fp8 +{ + return NO; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)fp8 +{ + return kQCPatchExecutionModeConsumer; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)fp8 +{ + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments +{ + NSLog(@"QCLOG: %@",[inputMessage stringValue]); + + return YES; +} + +@end diff --git a/Origami Plugin/FBLogToConsole.xml b/Origami Plugin/FBLogToConsole.xml new file mode 100755 index 0000000..c1d456e --- /dev/null +++ b/Origami Plugin/FBLogToConsole.xml @@ -0,0 +1,39 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch logs the input string to the console when it's enabled. It logs during each frame the viewer is rendering. Open Console.app to view the messages. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + Log to Console + + inputAttributes + + inputMessage + + description + The string you want to log. + name + Message + + + + \ No newline at end of file diff --git a/Origami Plugin/FBOrigami.xcodeproj/project.pbxproj b/Origami Plugin/FBOrigami.xcodeproj/project.pbxproj index aff60c7..d0b905e 100644 --- a/Origami Plugin/FBOrigami.xcodeproj/project.pbxproj +++ b/Origami Plugin/FBOrigami.xcodeproj/project.pbxproj @@ -142,6 +142,58 @@ B574FDC11861130100668D86 /* FBOrigamiAdditions+DimDisabledConsumers.m in Sources */ = {isa = PBXBuildFile; fileRef = B574FDC01861130100668D86 /* FBOrigamiAdditions+DimDisabledConsumers.m */; }; B574FDC41861159D00668D86 /* FBOrigamiAdditions+TextFieldShortcuts.m in Sources */ = {isa = PBXBuildFile; fileRef = B574FDC31861159D00668D86 /* FBOrigamiAdditions+TextFieldShortcuts.m */; }; B574FDCA1861289E00668D86 /* FBOrigamiAdditions+WindowMods.m in Sources */ = {isa = PBXBuildFile; fileRef = B574FDC91861289E00668D86 /* FBOrigamiAdditions+WindowMods.m */; }; + B57798FE1C94DB5000B1BDC9 /* DHJSONImporterPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B57798FC1C94DB5000B1BDC9 /* DHJSONImporterPatch.m */; }; + B57798FF1C94DB5000B1BDC9 /* DHJSONImporterPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57798FD1C94DB5000B1BDC9 /* DHJSONImporterPatch.xml */; }; + B577992A1C94DD7700B1BDC9 /* SBJsonParser.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799041C94DD7700B1BDC9 /* SBJsonParser.m */; }; + B577992B1C94DD7700B1BDC9 /* SBJsonStreamParser.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799061C94DD7700B1BDC9 /* SBJsonStreamParser.m */; }; + B577992C1C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799081C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.m */; }; + B577992D1C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = B577990A1C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.m */; }; + B577992E1C94DD7700B1BDC9 /* SBJsonStreamParserState.m in Sources */ = {isa = PBXBuildFile; fileRef = B577990C1C94DD7700B1BDC9 /* SBJsonStreamParserState.m */; }; + B577992F1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.m in Sources */ = {isa = PBXBuildFile; fileRef = B577990E1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.m */; }; + B57799301C94DD7700B1BDC9 /* SBJsonStreamWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799101C94DD7700B1BDC9 /* SBJsonStreamWriter.m */; }; + B57799311C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799121C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.m */; }; + B57799321C94DD7700B1BDC9 /* SBJsonStreamWriterState.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799141C94DD7700B1BDC9 /* SBJsonStreamWriterState.m */; }; + B57799331C94DD7700B1BDC9 /* SBJsonWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799161C94DD7700B1BDC9 /* SBJsonWriter.m */; }; + B57799341C94DD7700B1BDC9 /* SocketIO.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799181C94DD7700B1BDC9 /* SocketIO.m */; }; + B57799351C94DD7700B1BDC9 /* SocketIOJSONSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = B577991A1C94DD7700B1BDC9 /* SocketIOJSONSerialization.m */; }; + B57799361C94DD7700B1BDC9 /* SocketIOPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = B577991C1C94DD7700B1BDC9 /* SocketIOPacket.m */; }; + B57799371C94DD7700B1BDC9 /* SocketIOTransportWebsocket.m in Sources */ = {isa = PBXBuildFile; fileRef = B577991F1C94DD7700B1BDC9 /* SocketIOTransportWebsocket.m */; }; + B57799381C94DD7700B1BDC9 /* SocketIOTransportXHR.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799211C94DD7700B1BDC9 /* SocketIOTransportXHR.m */; }; + B57799391C94DD7700B1BDC9 /* base64.c in Sources */ = {isa = PBXBuildFile; fileRef = B57799231C94DD7700B1BDC9 /* base64.c */; }; + B577993A1C94DD7700B1BDC9 /* NSData+SRB64Additions.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799261C94DD7700B1BDC9 /* NSData+SRB64Additions.m */; }; + B577993B1C94DD7700B1BDC9 /* SRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799291C94DD7700B1BDC9 /* SRWebSocket.m */; }; + B577993E1C94DDB300B1BDC9 /* NSURL+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B577993D1C94DDB300B1BDC9 /* NSURL+FBAdditions.m */; }; + B57799411C94DF1000B1BDC9 /* NSString+RelativePath.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799401C94DF1000B1BDC9 /* NSString+RelativePath.m */; }; + B57799451C94EB1400B1BDC9 /* DHAppleScriptPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799431C94EB1400B1BDC9 /* DHAppleScriptPatch.m */; }; + B57799461C94EB1400B1BDC9 /* DHAppleScriptPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799441C94EB1400B1BDC9 /* DHAppleScriptPatch.xml */; }; + B577994D1C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799481C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.m */; }; + B577994E1C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B577994A1C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.m */; }; + B577994F1C94EBB600B1BDC9 /* NSDate+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B577994C1C94EBB600B1BDC9 /* NSDate+FBAdditions.m */; }; + B57799591C94F03800B1BDC9 /* DHImageAtURLPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799571C94F03800B1BDC9 /* DHImageAtURLPatch.m */; }; + B577995A1C94F03800B1BDC9 /* DHImageAtURLPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799581C94F03800B1BDC9 /* DHImageAtURLPatch.xml */; }; + B577995D1C94F29200B1BDC9 /* NSArray+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B577995C1C94F29200B1BDC9 /* NSArray+FBAdditions.m */; }; + B57799611C94F65D00B1BDC9 /* DHRESTRequestPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B577995F1C94F65D00B1BDC9 /* DHRESTRequestPatch.m */; }; + B57799621C94F65D00B1BDC9 /* DHRESTRequestPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799601C94F65D00B1BDC9 /* DHRESTRequestPatch.xml */; }; + B57799651C94F72000B1BDC9 /* DHRESTRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799641C94F72000B1BDC9 /* DHRESTRequest.m */; }; + B57799781C95029300B1BDC9 /* FBFileUpdated.m in Sources */ = {isa = PBXBuildFile; fileRef = B577996A1C95029300B1BDC9 /* FBFileUpdated.m */; }; + B57799791C95029300B1BDC9 /* FBFileUpdated.xml in Resources */ = {isa = PBXBuildFile; fileRef = B577996B1C95029300B1BDC9 /* FBFileUpdated.xml */; }; + B577997A1C95029300B1BDC9 /* FBDescriptionPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B577996D1C95029300B1BDC9 /* FBDescriptionPatch.m */; }; + B577997B1C95029300B1BDC9 /* FBDescriptionPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B577996E1C95029300B1BDC9 /* FBDescriptionPatch.xml */; }; + B577997C1C95029300B1BDC9 /* FBStructureShuffle.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799701C95029300B1BDC9 /* FBStructureShuffle.m */; }; + B577997D1C95029300B1BDC9 /* FBStructureShuffle.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799711C95029300B1BDC9 /* FBStructureShuffle.xml */; }; + B577997E1C95029300B1BDC9 /* FBLogToConsole.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799731C95029300B1BDC9 /* FBLogToConsole.m */; }; + B577997F1C95029300B1BDC9 /* FBLogToConsole.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799741C95029300B1BDC9 /* FBLogToConsole.xml */; }; + B57799801C95029300B1BDC9 /* FBFPS.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799761C95029300B1BDC9 /* FBFPS.m */; }; + B57799811C95029300B1BDC9 /* FBFPS.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799771C95029300B1BDC9 /* FBFPS.xml */; }; + B57799851C954C0F00B1BDC9 /* DHStringFormatterPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799831C954C0F00B1BDC9 /* DHStringFormatterPatch.m */; }; + B57799861C954C0F00B1BDC9 /* DHStringFormatterPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799841C954C0F00B1BDC9 /* DHStringFormatterPatch.xml */; }; + B577998A1C954C1700B1BDC9 /* DHImageWithFormattedStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799881C954C1700B1BDC9 /* DHImageWithFormattedStrings.m */; }; + B577998B1C954C1700B1BDC9 /* DHImageWithFormattedStrings.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799891C954C1700B1BDC9 /* DHImageWithFormattedStrings.xml */; }; + B577998E1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = B577998D1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.m */; }; + B57799961C963A0500B1BDC9 /* DHStringAtURLPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B577998F1C963A0500B1BDC9 /* DHStringAtURLPatch.xml */; }; + B57799971C963A0500B1BDC9 /* DHStringAtURLPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799911C963A0500B1BDC9 /* DHStringAtURLPatch.m */; }; + B57799981C963A0500B1BDC9 /* DHSoundPlayerProPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B57799921C963A0500B1BDC9 /* DHSoundPlayerProPatch.xml */; }; + B57799991C963A0500B1BDC9 /* DHSoundPlayerProPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B57799941C963A0500B1BDC9 /* DHSoundPlayerProPatch.m */; }; B57D530C1A2D301300B805D3 /* Code Export in Resources */ = {isa = PBXBuildFile; fileRef = B57D530B1A2D301300B805D3 /* Code Export */; }; B593F0A61A2737DF003C664A /* FBOrigamiAdditions+CodeExport.m in Sources */ = {isa = PBXBuildFile; fileRef = B593F0A51A2737DF003C664A /* FBOrigamiAdditions+CodeExport.m */; }; B59541B419E1F5F400B4FC3A /* FBDeviceViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B59541B219E1F5F400B4FC3A /* FBDeviceViewController.m */; }; @@ -173,6 +225,15 @@ B5BB6FF01A1020AB005725C1 /* FBMutableOrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = B5BB6FEF1A1020AB005725C1 /* FBMutableOrderedDictionary.m */; }; B5BB6FF31A1020DD005725C1 /* QCImage+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B5BB6FF21A1020DD005725C1 /* QCImage+FBAdditions.m */; }; B5BB6FF61A102146005725C1 /* FBHashValue.m in Sources */ = {isa = PBXBuildFile; fileRef = B5BB6FF51A102146005725C1 /* FBHashValue.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B5C32BE01C98AB4A00E29020 /* DHStructureAllKeysPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C32BD51C98AB4A00E29020 /* DHStructureAllKeysPatch.m */; }; + B5C32BE11C98AB4A00E29020 /* DHStructureAllKeysPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B5C32BD61C98AB4A00E29020 /* DHStructureAllKeysPatch.xml */; }; + B5C32BE21C98AB4A00E29020 /* DHStructureAllValuesPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C32BD81C98AB4A00E29020 /* DHStructureAllValuesPatch.m */; }; + B5C32BE31C98AB4A00E29020 /* DHStructureAllValuesPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B5C32BD91C98AB4A00E29020 /* DHStructureAllValuesPatch.xml */; }; + B5C32BE41C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C32BDB1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.m */; }; + B5C32BE51C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B5C32BDC1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.xml */; }; + B5C32BE61C98AB4A00E29020 /* DHStructurePathMemberPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C32BDE1C98AB4A00E29020 /* DHStructurePathMemberPatch.m */; }; + B5C32BE71C98AB4A00E29020 /* DHStructurePathMemberPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B5C32BDF1C98AB4A00E29020 /* DHStructurePathMemberPatch.xml */; }; + B5C32BEA1C98AEC500E29020 /* NSDictionary+FBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C32BE91C98AEC500E29020 /* NSDictionary+FBAdditions.m */; }; B5D7643F1A30FD9A0054F72F /* FBOStructureCreatorPatch.m in Sources */ = {isa = PBXBuildFile; fileRef = B5D7643E1A30FD9A0054F72F /* FBOStructureCreatorPatch.m */; }; B5D764421A30FEBF0054F72F /* FBOStructureCreatorPatchUI.m in Sources */ = {isa = PBXBuildFile; fileRef = B5D764411A30FEBF0054F72F /* FBOStructureCreatorPatchUI.m */; }; B5D88F9418E0A6F200CFBB7D /* FBOMultiSwitchPatch.xml in Resources */ = {isa = PBXBuildFile; fileRef = B5D88F9218E0A6F200CFBB7D /* FBOMultiSwitchPatch.xml */; }; @@ -409,6 +470,100 @@ B574FDC31861159D00668D86 /* FBOrigamiAdditions+TextFieldShortcuts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "FBOrigamiAdditions+TextFieldShortcuts.m"; sourceTree = ""; }; B574FDC81861289E00668D86 /* FBOrigamiAdditions+WindowMods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FBOrigamiAdditions+WindowMods.h"; sourceTree = ""; }; B574FDC91861289E00668D86 /* FBOrigamiAdditions+WindowMods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "FBOrigamiAdditions+WindowMods.m"; sourceTree = ""; }; + B57798FB1C94DB5000B1BDC9 /* DHJSONImporterPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHJSONImporterPatch.h; sourceTree = ""; }; + B57798FC1C94DB5000B1BDC9 /* DHJSONImporterPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHJSONImporterPatch.m; sourceTree = ""; }; + B57798FD1C94DB5000B1BDC9 /* DHJSONImporterPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHJSONImporterPatch.xml; sourceTree = ""; }; + B57799021C94DD7700B1BDC9 /* SBJson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJson.h; sourceTree = ""; }; + B57799031C94DD7700B1BDC9 /* SBJsonParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonParser.h; sourceTree = ""; }; + B57799041C94DD7700B1BDC9 /* SBJsonParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonParser.m; sourceTree = ""; }; + B57799051C94DD7700B1BDC9 /* SBJsonStreamParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParser.h; sourceTree = ""; }; + B57799061C94DD7700B1BDC9 /* SBJsonStreamParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParser.m; sourceTree = ""; }; + B57799071C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserAccumulator.h; sourceTree = ""; }; + B57799081C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserAccumulator.m; sourceTree = ""; }; + B57799091C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserAdapter.h; sourceTree = ""; }; + B577990A1C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserAdapter.m; sourceTree = ""; }; + B577990B1C94DD7700B1BDC9 /* SBJsonStreamParserState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamParserState.h; sourceTree = ""; }; + B577990C1C94DD7700B1BDC9 /* SBJsonStreamParserState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamParserState.m; sourceTree = ""; }; + B577990D1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamTokeniser.h; sourceTree = ""; }; + B577990E1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamTokeniser.m; sourceTree = ""; }; + B577990F1C94DD7700B1BDC9 /* SBJsonStreamWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamWriter.h; sourceTree = ""; }; + B57799101C94DD7700B1BDC9 /* SBJsonStreamWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamWriter.m; sourceTree = ""; }; + B57799111C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamWriterAccumulator.h; sourceTree = ""; }; + B57799121C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamWriterAccumulator.m; sourceTree = ""; }; + B57799131C94DD7700B1BDC9 /* SBJsonStreamWriterState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonStreamWriterState.h; sourceTree = ""; }; + B57799141C94DD7700B1BDC9 /* SBJsonStreamWriterState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonStreamWriterState.m; sourceTree = ""; }; + B57799151C94DD7700B1BDC9 /* SBJsonWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBJsonWriter.h; sourceTree = ""; }; + B57799161C94DD7700B1BDC9 /* SBJsonWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SBJsonWriter.m; sourceTree = ""; }; + B57799171C94DD7700B1BDC9 /* SocketIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketIO.h; sourceTree = ""; }; + B57799181C94DD7700B1BDC9 /* SocketIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SocketIO.m; sourceTree = ""; }; + B57799191C94DD7700B1BDC9 /* SocketIOJSONSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketIOJSONSerialization.h; sourceTree = ""; }; + B577991A1C94DD7700B1BDC9 /* SocketIOJSONSerialization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SocketIOJSONSerialization.m; sourceTree = ""; }; + B577991B1C94DD7700B1BDC9 /* SocketIOPacket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketIOPacket.h; sourceTree = ""; }; + B577991C1C94DD7700B1BDC9 /* SocketIOPacket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SocketIOPacket.m; sourceTree = ""; }; + B577991D1C94DD7700B1BDC9 /* SocketIOTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketIOTransport.h; sourceTree = ""; }; + B577991E1C94DD7700B1BDC9 /* SocketIOTransportWebsocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketIOTransportWebsocket.h; sourceTree = ""; }; + B577991F1C94DD7700B1BDC9 /* SocketIOTransportWebsocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SocketIOTransportWebsocket.m; sourceTree = ""; }; + B57799201C94DD7700B1BDC9 /* SocketIOTransportXHR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketIOTransportXHR.h; sourceTree = ""; }; + B57799211C94DD7700B1BDC9 /* SocketIOTransportXHR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SocketIOTransportXHR.m; sourceTree = ""; }; + B57799231C94DD7700B1BDC9 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = ""; }; + B57799241C94DD7700B1BDC9 /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; + B57799251C94DD7700B1BDC9 /* NSData+SRB64Additions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SRB64Additions.h"; sourceTree = ""; }; + B57799261C94DD7700B1BDC9 /* NSData+SRB64Additions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+SRB64Additions.m"; sourceTree = ""; }; + B57799271C94DD7700B1BDC9 /* SocketRocket-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SocketRocket-Prefix.pch"; sourceTree = ""; }; + B57799281C94DD7700B1BDC9 /* SRWebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRWebSocket.h; sourceTree = ""; }; + B57799291C94DD7700B1BDC9 /* SRWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRWebSocket.m; sourceTree = ""; }; + B577993C1C94DDB300B1BDC9 /* NSURL+FBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+FBAdditions.h"; sourceTree = ""; }; + B577993D1C94DDB300B1BDC9 /* NSURL+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+FBAdditions.m"; sourceTree = ""; }; + B577993F1C94DF1000B1BDC9 /* NSString+RelativePath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+RelativePath.h"; sourceTree = ""; }; + B57799401C94DF1000B1BDC9 /* NSString+RelativePath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+RelativePath.m"; sourceTree = ""; }; + B57799421C94EB1400B1BDC9 /* DHAppleScriptPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHAppleScriptPatch.h; sourceTree = ""; }; + B57799431C94EB1400B1BDC9 /* DHAppleScriptPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHAppleScriptPatch.m; sourceTree = ""; }; + B57799441C94EB1400B1BDC9 /* DHAppleScriptPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHAppleScriptPatch.xml; sourceTree = ""; }; + B57799471C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAppleEventDescriptor+FBAdditions.h"; sourceTree = ""; }; + B57799481C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAppleEventDescriptor+FBAdditions.m"; sourceTree = ""; }; + B57799491C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAppleScript+FBAdditions.h"; sourceTree = ""; }; + B577994A1C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAppleScript+FBAdditions.m"; sourceTree = ""; }; + B577994B1C94EBB600B1BDC9 /* NSDate+FBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+FBAdditions.h"; sourceTree = ""; }; + B577994C1C94EBB600B1BDC9 /* NSDate+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+FBAdditions.m"; sourceTree = ""; }; + B57799561C94F03800B1BDC9 /* DHImageAtURLPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHImageAtURLPatch.h; sourceTree = ""; }; + B57799571C94F03800B1BDC9 /* DHImageAtURLPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHImageAtURLPatch.m; sourceTree = ""; }; + B57799581C94F03800B1BDC9 /* DHImageAtURLPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHImageAtURLPatch.xml; sourceTree = ""; }; + B577995B1C94F29200B1BDC9 /* NSArray+FBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+FBAdditions.h"; sourceTree = ""; }; + B577995C1C94F29200B1BDC9 /* NSArray+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+FBAdditions.m"; sourceTree = ""; }; + B577995E1C94F65D00B1BDC9 /* DHRESTRequestPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHRESTRequestPatch.h; sourceTree = ""; }; + B577995F1C94F65D00B1BDC9 /* DHRESTRequestPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHRESTRequestPatch.m; sourceTree = ""; }; + B57799601C94F65D00B1BDC9 /* DHRESTRequestPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHRESTRequestPatch.xml; sourceTree = ""; }; + B57799631C94F72000B1BDC9 /* DHRESTRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHRESTRequest.h; sourceTree = ""; }; + B57799641C94F72000B1BDC9 /* DHRESTRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHRESTRequest.m; sourceTree = ""; }; + B57799691C95029300B1BDC9 /* FBFileUpdated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFileUpdated.h; sourceTree = ""; }; + B577996A1C95029300B1BDC9 /* FBFileUpdated.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBFileUpdated.m; sourceTree = ""; }; + B577996B1C95029300B1BDC9 /* FBFileUpdated.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = FBFileUpdated.xml; sourceTree = ""; }; + B577996C1C95029300B1BDC9 /* FBDescriptionPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBDescriptionPatch.h; sourceTree = ""; }; + B577996D1C95029300B1BDC9 /* FBDescriptionPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBDescriptionPatch.m; sourceTree = ""; }; + B577996E1C95029300B1BDC9 /* FBDescriptionPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = FBDescriptionPatch.xml; sourceTree = ""; }; + B577996F1C95029300B1BDC9 /* FBStructureShuffle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBStructureShuffle.h; sourceTree = ""; }; + B57799701C95029300B1BDC9 /* FBStructureShuffle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBStructureShuffle.m; sourceTree = ""; }; + B57799711C95029300B1BDC9 /* FBStructureShuffle.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = FBStructureShuffle.xml; sourceTree = ""; }; + B57799721C95029300B1BDC9 /* FBLogToConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBLogToConsole.h; sourceTree = ""; }; + B57799731C95029300B1BDC9 /* FBLogToConsole.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBLogToConsole.m; sourceTree = ""; }; + B57799741C95029300B1BDC9 /* FBLogToConsole.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = FBLogToConsole.xml; sourceTree = ""; }; + B57799751C95029300B1BDC9 /* FBFPS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBFPS.h; sourceTree = ""; }; + B57799761C95029300B1BDC9 /* FBFPS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBFPS.m; sourceTree = ""; }; + B57799771C95029300B1BDC9 /* FBFPS.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = FBFPS.xml; sourceTree = ""; }; + B57799821C954C0F00B1BDC9 /* DHStringFormatterPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHStringFormatterPatch.h; sourceTree = ""; }; + B57799831C954C0F00B1BDC9 /* DHStringFormatterPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHStringFormatterPatch.m; sourceTree = ""; }; + B57799841C954C0F00B1BDC9 /* DHStringFormatterPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHStringFormatterPatch.xml; sourceTree = ""; }; + B57799871C954C1700B1BDC9 /* DHImageWithFormattedStrings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHImageWithFormattedStrings.h; sourceTree = ""; }; + B57799881C954C1700B1BDC9 /* DHImageWithFormattedStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHImageWithFormattedStrings.m; sourceTree = ""; }; + B57799891C954C1700B1BDC9 /* DHImageWithFormattedStrings.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHImageWithFormattedStrings.xml; sourceTree = ""; }; + B577998C1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSColor+HTMLExtensions.h"; sourceTree = ""; }; + B577998D1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSColor+HTMLExtensions.m"; sourceTree = ""; }; + B577998F1C963A0500B1BDC9 /* DHStringAtURLPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHStringAtURLPatch.xml; sourceTree = ""; }; + B57799901C963A0500B1BDC9 /* DHStringAtURLPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHStringAtURLPatch.h; sourceTree = ""; }; + B57799911C963A0500B1BDC9 /* DHStringAtURLPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHStringAtURLPatch.m; sourceTree = ""; }; + B57799921C963A0500B1BDC9 /* DHSoundPlayerProPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHSoundPlayerProPatch.xml; sourceTree = ""; }; + B57799931C963A0500B1BDC9 /* DHSoundPlayerProPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHSoundPlayerProPatch.h; sourceTree = ""; }; + B57799941C963A0500B1BDC9 /* DHSoundPlayerProPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHSoundPlayerProPatch.m; sourceTree = ""; }; B57D530B1A2D301300B805D3 /* Code Export */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Code Export"; sourceTree = ""; }; B593F0A41A2737DF003C664A /* FBOrigamiAdditions+CodeExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FBOrigamiAdditions+CodeExport.h"; sourceTree = ""; }; B593F0A51A2737DF003C664A /* FBOrigamiAdditions+CodeExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "FBOrigamiAdditions+CodeExport.m"; sourceTree = ""; }; @@ -458,6 +613,20 @@ B5BB6FF21A1020DD005725C1 /* QCImage+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "QCImage+FBAdditions.m"; sourceTree = ""; }; B5BB6FF41A102146005725C1 /* FBHashValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBHashValue.h; sourceTree = ""; }; B5BB6FF51A102146005725C1 /* FBHashValue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBHashValue.m; sourceTree = ""; }; + B5C32BD41C98AB4A00E29020 /* DHStructureAllKeysPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHStructureAllKeysPatch.h; sourceTree = ""; }; + B5C32BD51C98AB4A00E29020 /* DHStructureAllKeysPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHStructureAllKeysPatch.m; sourceTree = ""; }; + B5C32BD61C98AB4A00E29020 /* DHStructureAllKeysPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHStructureAllKeysPatch.xml; sourceTree = ""; }; + B5C32BD71C98AB4A00E29020 /* DHStructureAllValuesPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHStructureAllValuesPatch.h; sourceTree = ""; }; + B5C32BD81C98AB4A00E29020 /* DHStructureAllValuesPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHStructureAllValuesPatch.m; sourceTree = ""; }; + B5C32BD91C98AB4A00E29020 /* DHStructureAllValuesPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHStructureAllValuesPatch.xml; sourceTree = ""; }; + B5C32BDA1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHStructureMultiplePathMembersPatch.h; sourceTree = ""; }; + B5C32BDB1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHStructureMultiplePathMembersPatch.m; sourceTree = ""; }; + B5C32BDC1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHStructureMultiplePathMembersPatch.xml; sourceTree = ""; }; + B5C32BDD1C98AB4A00E29020 /* DHStructurePathMemberPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DHStructurePathMemberPatch.h; sourceTree = ""; }; + B5C32BDE1C98AB4A00E29020 /* DHStructurePathMemberPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DHStructurePathMemberPatch.m; sourceTree = ""; }; + B5C32BDF1C98AB4A00E29020 /* DHStructurePathMemberPatch.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DHStructurePathMemberPatch.xml; sourceTree = ""; }; + B5C32BE81C98AEC500E29020 /* NSDictionary+FBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+FBAdditions.h"; sourceTree = ""; }; + B5C32BE91C98AEC500E29020 /* NSDictionary+FBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+FBAdditions.m"; sourceTree = ""; }; B5D7643D1A30FD9A0054F72F /* FBOStructureCreatorPatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBOStructureCreatorPatch.h; sourceTree = ""; }; B5D7643E1A30FD9A0054F72F /* FBOStructureCreatorPatch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBOStructureCreatorPatch.m; sourceTree = ""; }; B5D764401A30FEBF0054F72F /* FBOStructureCreatorPatchUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBOStructureCreatorPatchUI.h; sourceTree = ""; }; @@ -645,6 +814,61 @@ B596FE001A86D49500C1D132 /* FBViewerDimensionsPatch.h */, B596FE011A86D49500C1D132 /* FBViewerDimensionsPatch.m */, B596FE021A86D49500C1D132 /* FBViewerDimensionsPatch.xml */, + B57799691C95029300B1BDC9 /* FBFileUpdated.h */, + B577996A1C95029300B1BDC9 /* FBFileUpdated.m */, + B577996B1C95029300B1BDC9 /* FBFileUpdated.xml */, + B577996C1C95029300B1BDC9 /* FBDescriptionPatch.h */, + B577996D1C95029300B1BDC9 /* FBDescriptionPatch.m */, + B577996E1C95029300B1BDC9 /* FBDescriptionPatch.xml */, + B577996F1C95029300B1BDC9 /* FBStructureShuffle.h */, + B57799701C95029300B1BDC9 /* FBStructureShuffle.m */, + B57799711C95029300B1BDC9 /* FBStructureShuffle.xml */, + B57799721C95029300B1BDC9 /* FBLogToConsole.h */, + B57799731C95029300B1BDC9 /* FBLogToConsole.m */, + B57799741C95029300B1BDC9 /* FBLogToConsole.xml */, + B57799751C95029300B1BDC9 /* FBFPS.h */, + B57799761C95029300B1BDC9 /* FBFPS.m */, + B57799771C95029300B1BDC9 /* FBFPS.xml */, + B57798FB1C94DB5000B1BDC9 /* DHJSONImporterPatch.h */, + B57798FC1C94DB5000B1BDC9 /* DHJSONImporterPatch.m */, + B57798FD1C94DB5000B1BDC9 /* DHJSONImporterPatch.xml */, + B57799421C94EB1400B1BDC9 /* DHAppleScriptPatch.h */, + B57799431C94EB1400B1BDC9 /* DHAppleScriptPatch.m */, + B57799441C94EB1400B1BDC9 /* DHAppleScriptPatch.xml */, + B57799561C94F03800B1BDC9 /* DHImageAtURLPatch.h */, + B57799571C94F03800B1BDC9 /* DHImageAtURLPatch.m */, + B57799581C94F03800B1BDC9 /* DHImageAtURLPatch.xml */, + B577995E1C94F65D00B1BDC9 /* DHRESTRequestPatch.h */, + B577995F1C94F65D00B1BDC9 /* DHRESTRequestPatch.m */, + B57799601C94F65D00B1BDC9 /* DHRESTRequestPatch.xml */, + B57799631C94F72000B1BDC9 /* DHRESTRequest.h */, + B57799641C94F72000B1BDC9 /* DHRESTRequest.m */, + B57799821C954C0F00B1BDC9 /* DHStringFormatterPatch.h */, + B57799831C954C0F00B1BDC9 /* DHStringFormatterPatch.m */, + B57799841C954C0F00B1BDC9 /* DHStringFormatterPatch.xml */, + B57799871C954C1700B1BDC9 /* DHImageWithFormattedStrings.h */, + B57799881C954C1700B1BDC9 /* DHImageWithFormattedStrings.m */, + B57799891C954C1700B1BDC9 /* DHImageWithFormattedStrings.xml */, + B57799901C963A0500B1BDC9 /* DHStringAtURLPatch.h */, + B57799911C963A0500B1BDC9 /* DHStringAtURLPatch.m */, + B577998F1C963A0500B1BDC9 /* DHStringAtURLPatch.xml */, + B57799931C963A0500B1BDC9 /* DHSoundPlayerProPatch.h */, + B57799941C963A0500B1BDC9 /* DHSoundPlayerProPatch.m */, + B57799921C963A0500B1BDC9 /* DHSoundPlayerProPatch.xml */, + B5C32BD41C98AB4A00E29020 /* DHStructureAllKeysPatch.h */, + B5C32BD51C98AB4A00E29020 /* DHStructureAllKeysPatch.m */, + B5C32BD61C98AB4A00E29020 /* DHStructureAllKeysPatch.xml */, + B5C32BD71C98AB4A00E29020 /* DHStructureAllValuesPatch.h */, + B5C32BD81C98AB4A00E29020 /* DHStructureAllValuesPatch.m */, + B5C32BD91C98AB4A00E29020 /* DHStructureAllValuesPatch.xml */, + B5C32BDA1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.h */, + B5C32BDB1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.m */, + B5C32BDC1C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.xml */, + B5C32BDD1C98AB4A00E29020 /* DHStructurePathMemberPatch.h */, + B5C32BDE1C98AB4A00E29020 /* DHStructurePathMemberPatch.m */, + B5C32BDF1C98AB4A00E29020 /* DHStructurePathMemberPatch.xml */, + B577998C1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.h */, + B577998D1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.m */, ); name = "Other Patches"; sourceTree = ""; @@ -707,6 +931,10 @@ B5BB6FF21A1020DD005725C1 /* QCImage+FBAdditions.m */, B52C92621975D93000AB5A6D /* QCView+FBAdditions.h */, B52C92631975D93000AB5A6D /* QCView+FBAdditions.m */, + B577993C1C94DDB300B1BDC9 /* NSURL+FBAdditions.h */, + B577993D1C94DDB300B1BDC9 /* NSURL+FBAdditions.m */, + B577993F1C94DF1000B1BDC9 /* NSString+RelativePath.h */, + B57799401C94DF1000B1BDC9 /* NSString+RelativePath.m */, B574FDB918610DBF00668D86 /* NSObject+AssociatedObjects.h */, B574FDBA18610DBF00668D86 /* NSObject+AssociatedObjects.m */, B574FDBC18610E9B00668D86 /* NSDocument+FBAdditions.h */, @@ -715,6 +943,16 @@ 3AAF9DB818BAEBF9002B591F /* NSMenu+FBAdditions.m */, B50816BB1980F10E007AEDAF /* NSView+FBAdditions.h */, B50816BC1980F10E007AEDAF /* NSView+FBAdditions.m */, + B577995B1C94F29200B1BDC9 /* NSArray+FBAdditions.h */, + B577995C1C94F29200B1BDC9 /* NSArray+FBAdditions.m */, + B57799471C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.h */, + B57799481C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.m */, + B57799491C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.h */, + B577994A1C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.m */, + B577994B1C94EBB600B1BDC9 /* NSDate+FBAdditions.h */, + B577994C1C94EBB600B1BDC9 /* NSDate+FBAdditions.m */, + B5C32BE81C98AEC500E29020 /* NSDictionary+FBAdditions.h */, + B5C32BE91C98AEC500E29020 /* NSDictionary+FBAdditions.m */, B5BB6FEE1A1020AB005725C1 /* FBMutableOrderedDictionary.h */, B5BB6FEF1A1020AB005725C1 /* FBMutableOrderedDictionary.m */, 3AFA71081856776900A34E93 /* GRPHookMethod.h */, @@ -725,6 +963,7 @@ B59541B119E1F5F400B4FC3A /* FBDeviceViewController.h */, B59541B219E1F5F400B4FC3A /* FBDeviceViewController.m */, B59541B319E1F5F400B4FC3A /* FBDeviceViewController.xib */, + B57799001C94DD7700B1BDC9 /* SocketIO */, ); name = Other; sourceTree = ""; @@ -933,6 +1172,68 @@ name = "Pop Animation"; sourceTree = ""; }; + B57799001C94DD7700B1BDC9 /* SocketIO */ = { + isa = PBXGroup; + children = ( + B57799011C94DD7700B1BDC9 /* SBJson */, + B57799171C94DD7700B1BDC9 /* SocketIO.h */, + B57799181C94DD7700B1BDC9 /* SocketIO.m */, + B57799191C94DD7700B1BDC9 /* SocketIOJSONSerialization.h */, + B577991A1C94DD7700B1BDC9 /* SocketIOJSONSerialization.m */, + B577991B1C94DD7700B1BDC9 /* SocketIOPacket.h */, + B577991C1C94DD7700B1BDC9 /* SocketIOPacket.m */, + B577991D1C94DD7700B1BDC9 /* SocketIOTransport.h */, + B577991E1C94DD7700B1BDC9 /* SocketIOTransportWebsocket.h */, + B577991F1C94DD7700B1BDC9 /* SocketIOTransportWebsocket.m */, + B57799201C94DD7700B1BDC9 /* SocketIOTransportXHR.h */, + B57799211C94DD7700B1BDC9 /* SocketIOTransportXHR.m */, + B57799221C94DD7700B1BDC9 /* SocketRocket */, + ); + path = SocketIO; + sourceTree = ""; + }; + B57799011C94DD7700B1BDC9 /* SBJson */ = { + isa = PBXGroup; + children = ( + B57799021C94DD7700B1BDC9 /* SBJson.h */, + B57799031C94DD7700B1BDC9 /* SBJsonParser.h */, + B57799041C94DD7700B1BDC9 /* SBJsonParser.m */, + B57799051C94DD7700B1BDC9 /* SBJsonStreamParser.h */, + B57799061C94DD7700B1BDC9 /* SBJsonStreamParser.m */, + B57799071C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.h */, + B57799081C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.m */, + B57799091C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.h */, + B577990A1C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.m */, + B577990B1C94DD7700B1BDC9 /* SBJsonStreamParserState.h */, + B577990C1C94DD7700B1BDC9 /* SBJsonStreamParserState.m */, + B577990D1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.h */, + B577990E1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.m */, + B577990F1C94DD7700B1BDC9 /* SBJsonStreamWriter.h */, + B57799101C94DD7700B1BDC9 /* SBJsonStreamWriter.m */, + B57799111C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.h */, + B57799121C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.m */, + B57799131C94DD7700B1BDC9 /* SBJsonStreamWriterState.h */, + B57799141C94DD7700B1BDC9 /* SBJsonStreamWriterState.m */, + B57799151C94DD7700B1BDC9 /* SBJsonWriter.h */, + B57799161C94DD7700B1BDC9 /* SBJsonWriter.m */, + ); + path = SBJson; + sourceTree = ""; + }; + B57799221C94DD7700B1BDC9 /* SocketRocket */ = { + isa = PBXGroup; + children = ( + B57799231C94DD7700B1BDC9 /* base64.c */, + B57799241C94DD7700B1BDC9 /* base64.h */, + B57799251C94DD7700B1BDC9 /* NSData+SRB64Additions.h */, + B57799261C94DD7700B1BDC9 /* NSData+SRB64Additions.m */, + B57799271C94DD7700B1BDC9 /* SocketRocket-Prefix.pch */, + B57799281C94DD7700B1BDC9 /* SRWebSocket.h */, + B57799291C94DD7700B1BDC9 /* SRWebSocket.m */, + ); + path = SocketRocket; + sourceTree = ""; + }; B57D53081A2D280200B805D3 /* Code Export */ = { isa = PBXGroup; children = ( @@ -1064,14 +1365,18 @@ B5BB6FDB1A101FB6005725C1 /* FB3DOrientationPatch.xml in Resources */, B53EA60C18DD8B9500AD24A6 /* ShowSettings.tiff in Resources */, B53EA5E718DD888C00AD24A6 /* Stop@2x.tiff in Resources */, + B577998B1C954C1700B1BDC9 /* DHImageWithFormattedStrings.xml in Resources */, + B5C32BE31C98AB4A00E29020 /* DHStructureAllValuesPatch.xml in Resources */, B53EA5FF18DD8AE100AD24A6 /* Parent@2x.tiff in Resources */, B53EA5E318DD7DFF00AD24A6 /* SaveImage@2x.tiff in Resources */, B51CE8D91A9BC05D00E1C156 /* FBWirelessInPatch.xml in Resources */, B53EA61518DD8D2E00AD24A6 /* Patcherator@2x.tiff in Resources */, B5BB6FE11A101FB6005725C1 /* FBDeviceVibratePatch.xml in Resources */, + B577997F1C95029300B1BDC9 /* FBLogToConsole.xml in Resources */, B53EA63118DD9D3100AD24A6 /* DebugMode@2x.tiff in Resources */, B12469A11B0E84A30002AFEC /* coscript in Resources */, B51CE8DF1A9BC05D00E1C156 /* FBWirelessOutPatchUI.xib in Resources */, + B57799621C94F65D00B1BDC9 /* DHRESTRequestPatch.xml in Resources */, B53EA63218DD9D3100AD24A6 /* EditMode.tiff in Resources */, B5BB6FDD1A101FB6005725C1 /* FBDeviceInfoPatch.xml in Resources */, B53A9AC518F31B9A00B5E151 /* FBSafariToolbarController.xib in Resources */, @@ -1079,6 +1384,7 @@ B53EA60D18DD8B9500AD24A6 /* ShowSettings@2x.tiff in Resources */, B5B2E9F91A75B30300F93A75 /* FBOStructureCombinePatch.xml in Resources */, B53A9AD118F31E8600B5E151 /* FaviconGeneric.png in Resources */, + B57799981C963A0500B1BDC9 /* DHSoundPlayerProPatch.xml in Resources */, EC61DA18191C85B800E5443A /* POPDecayPatch.xml in Resources */, B53EA61018DD8BF100AD24A6 /* CreateVirtualMacro.tiff in Resources */, B53EA62618DD95CF00AD24A6 /* ZoomToFit@2x.tiff in Resources */, @@ -1090,23 +1396,29 @@ B53EA60918DD8B8400AD24A6 /* Viewer@2x.tiff in Resources */, B53A9ACF18F31E8600B5E151 /* FaviconFacebook.png in Resources */, B52C925E1975D7F100AB5A6D /* FBDragAndDropPatch.xml in Resources */, + B5C32BE11C98AB4A00E29020 /* DHStructureAllKeysPatch.xml in Resources */, + B57799461C94EB1400B1BDC9 /* DHAppleScriptPatch.xml in Resources */, B53A9AE018F32B6800B5E151 /* FBRGBToHex.xml in Resources */, B548D75C196CB12600A7C0B9 /* FBODelayPatch.xml in Resources */, B59692501A315E5D00FD991A /* FBOStructureCreatorPatch.xml in Resources */, B12469A31B0F92F50002AFEC /* Export for Origami.js in Resources */, B51CE8DB1A9BC05D00E1C156 /* FBWirelessInPatchUI.xib in Resources */, + B577995A1C94F03800B1BDC9 /* DHImageAtURLPatch.xml in Resources */, B53A9AD418F31E8600B5E151 /* RefreshIcon@2x.png in Resources */, D9DC9CEF19205EAE006B8241 /* FBOLiveFilePatchUI.xib in Resources */, B53EA62218DD95CF00AD24A6 /* ZoomOut@2x.tiff in Resources */, + B5C32BE71C98AB4A00E29020 /* DHStructurePathMemberPatch.xml in Resources */, B12469A51B0F9C560002AFEC /* Export for Origami.sketchplugin in Resources */, B5D88F9418E0A6F200CFBB7D /* FBOMultiSwitchPatch.xml in Resources */, B5EA16A118CE4F6800A561A4 /* FBOLayerGroup.xml in Resources */, B5BB6FDF1A101FB6005725C1 /* FBDeviceRendererPatch.xml in Resources */, B53EA63318DD9D3100AD24A6 /* EditMode@2x.tiff in Resources */, + B577997D1C95029300B1BDC9 /* FBStructureShuffle.xml in Resources */, B53EA5F218DD891000AD24A6 /* Present.tiff in Resources */, B54831E6186410B4000D2B2A /* OrigamiMenubar-normal.png in Resources */, B51CE8EA1A9BC0F600E1C156 /* Broadcast4x.png in Resources */, B53A9ADE18F32B6800B5E151 /* FBHexToRGB.xml in Resources */, + B57799791C95029300B1BDC9 /* FBFileUpdated.xml in Resources */, B53A9AC118F31B9A00B5E151 /* FBSafariTheme.xml in Resources */, B59541B519E1F5F400B4FC3A /* FBDeviceViewController.xib in Resources */, B53EA63518DD9D3100AD24A6 /* PerformanceMode@2x.tiff in Resources */, @@ -1118,6 +1430,7 @@ B53EA60418DD8B6E00AD24A6 /* Parameters.tiff in Resources */, B53EA62518DD95CF00AD24A6 /* ZoomToFit.tiff in Resources */, B54A5817196393F6004BB2D6 /* POPConverterPatch.xml in Resources */, + B57799861C954C0F00B1BDC9 /* DHStringFormatterPatch.xml in Resources */, B53EA5DC18DD7BC500AD24A6 /* InputParameters.tiff in Resources */, B53A9AD318F31E8600B5E151 /* RefreshIcon.png in Resources */, B53EA63718DD9D3100AD24A6 /* ProfileMode@2x.tiff in Resources */, @@ -1125,6 +1438,7 @@ D9DC9CF8192070EB006B8241 /* FBOLiveFilePatch.xml in Resources */, B53EA63018DD9D3100AD24A6 /* DebugMode.tiff in Resources */, B53EA5D418DD7AC600AD24A6 /* Run.tiff in Resources */, + B57799961C963A0500B1BDC9 /* DHStringAtURLPatch.xml in Resources */, B50F45AB18F9FB92005548DF /* FBCursorPatch.xml in Resources */, B59687C51BADEC72007682C7 /* FBLastValue.xml in Resources */, B545A8181ABA429B00D475A5 /* FBStopWatchPatch.xml in Resources */, @@ -1138,21 +1452,25 @@ B53EA63618DD9D3100AD24A6 /* ProfileMode.tiff in Resources */, B53EA63418DD9D3100AD24A6 /* PerformanceMode.tiff in Resources */, B53EA5FE18DD8AE100AD24A6 /* Parent.tiff in Resources */, + B57799811C95029300B1BDC9 /* FBFPS.xml in Resources */, 3A9D3DF718C1211C0021812A /* FBOInteractionPatch.xml in Resources */, B53EA61418DD8D2E00AD24A6 /* Patcherator.tiff in Resources */, B5B1D6C01A5C693D006E7A6A /* TabsIcon@2x.png in Resources */, B5B4A3F71919A96F0044C3ED /* FBOProgressPatch.xml in Resources */, B53EA60118DD8AE100AD24A6 /* Inspector@2x.tiff in Resources */, B53EA5EF18DD88F800AD24A6 /* Editor@2x.tiff in Resources */, + B5C32BE51C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.xml in Resources */, B53EA61118DD8BF100AD24A6 /* CreateVirtualMacro@2x.tiff in Resources */, B51CE8E91A9BC0F600E1C156 /* Broadcast1x.png in Resources */, 1A7A712918D7CBE80008D05A /* FBOMultiSwitchPatchUI.xib in Resources */, B596FE041A86D49500C1D132 /* FBViewerDimensionsPatch.xml in Resources */, B53EA5FD18DD8AE100AD24A6 /* Macro@2x.tiff in Resources */, + B577997B1C95029300B1BDC9 /* FBDescriptionPatch.xml in Resources */, B53EA5FC18DD8AE100AD24A6 /* Macro.tiff in Resources */, 3A9D3E0318C3FD8A0021812A /* Origami.icns in Resources */, B53EA61F18DD95CF00AD24A6 /* ZoomIn.tiff in Resources */, B53A9AD218F31E8600B5E151 /* FaviconGeneric@2x.png in Resources */, + B57798FF1C94DB5000B1BDC9 /* DHJSONImporterPatch.xml in Resources */, B53EA62118DD95CF00AD24A6 /* ZoomOut.tiff in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1183,6 +1501,7 @@ files = ( 3A63EDCF1A13BF0300298D20 /* FBOrigamiAdditions+StructureIterator.m in Sources */, B160AAEC1AAA0778004AB107 /* FBOrigamiAdditions+FileCreation.m in Sources */, + B577992E1C94DD7700B1BDC9 /* SBJsonStreamParserState.m in Sources */, B574FDBE18610E9B00668D86 /* NSDocument+FBAdditions.m in Sources */, B5741F271A8856CA00C621F7 /* FBOrigamiAdditions+Tooltips.m in Sources */, B51CE8D71A9BC05D00E1C156 /* FBWirelessController.m in Sources */, @@ -1190,26 +1509,35 @@ B5B2E9F61A75AF9400F93A75 /* FBOStructureCombinePatch.m in Sources */, 1A7A712618D7CBE80008D05A /* FBODynamicPortsPatch.m in Sources */, B51CE8E21A9BC07200E1C156 /* FBOrigamiAdditions+Wireless.m in Sources */, + B57799851C954C0F00B1BDC9 /* DHStringFormatterPatch.m in Sources */, + B577994F1C94EBB600B1BDC9 /* NSDate+FBAdditions.m in Sources */, 3AFA71041856768800A34E93 /* FBOrigamiAdditions+Mavericks.m in Sources */, F1481793089E5FE3006FD276 /* FBOrigamiPrincipal.m in Sources */, B5BB6FDE1A101FB6005725C1 /* FBDeviceRendererPatch.m in Sources */, + B57798FE1C94DB5000B1BDC9 /* DHJSONImporterPatch.m in Sources */, B53A9ABF18F31B9A00B5E151 /* FBSafariSegmentedCell.m in Sources */, B5BB6FDC1A101FB6005725C1 /* FBDeviceInfoPatch.m in Sources */, B50816B3198060F1007AEDAF /* FBOrigamiAdditions+InlineValues.m in Sources */, B5EE0034198A2FAB003DDFE6 /* FBOrigamiAdditions+WindowManagement.m in Sources */, + B577997E1C95029300B1BDC9 /* FBLogToConsole.m in Sources */, B574FDC11861130100668D86 /* FBOrigamiAdditions+DimDisabledConsumers.m in Sources */, + B57799451C94EB1400B1BDC9 /* DHAppleScriptPatch.m in Sources */, + B57799321C94DD7700B1BDC9 /* SBJsonStreamWriterState.m in Sources */, B54831DE186385DD000D2B2A /* FBOInteractionController.m in Sources */, B548D75A196C897900A7C0B9 /* FBODelayPatch.m in Sources */, B52C92611975D80000AB5A6D /* QCView+DragAndDrop.m in Sources */, 1A7A712818D7CBE80008D05A /* FBOMultiSwitchPatchUI.m in Sources */, B5FB9D0719E3DB060059028E /* FBDeviceToolbarButton.m in Sources */, + B577997A1C95029300B1BDC9 /* FBDescriptionPatch.m in Sources */, D9DC9D0219209DC0006B8241 /* CDEvent.m in Sources */, B516A2701A16A161009CD3E4 /* FBTextObject.m in Sources */, B53A9AC018F31B9A00B5E151 /* FBSafariTheme.m in Sources */, B54A58151963923F004BB2D6 /* POPConverterPatch.m in Sources */, + B57799611C94F65D00B1BDC9 /* DHRESTRequestPatch.m in Sources */, B5F6D89819624A450057BA6E /* FBOMouseScrollPatch.m in Sources */, B545A8171ABA429B00D475A5 /* FBStopWatchPatch.m in Sources */, EC61DA17191C85B800E5443A /* POPDecayPatch.mm in Sources */, + B57799301C94DD7700B1BDC9 /* SBJsonStreamWriter.m in Sources */, 3AF2FE3218C4210F00CF372C /* FBOrigamiAdditions+LinearPortConnections.m in Sources */, D9DC9CF51920618B006B8241 /* FBOLiveFilePatch.m in Sources */, B51CE8DC1A9BC05D00E1C156 /* FBWirelessOutPatch.m in Sources */, @@ -1218,12 +1546,15 @@ B50277531A7C93240023F1A2 /* FBOrigamiAdditions+KeyboardShortcuts.m in Sources */, B574FDB818610CFD00668D86 /* QCPatch+FBAdditions.m in Sources */, B56DC46718C10E8700BE6BB6 /* FBOrigamiAdditions+RenderInImage.m in Sources */, + B5C32BE01C98AB4A00E29020 /* DHStructureAllKeysPatch.m in Sources */, B5BB6FED1A102083005725C1 /* PTUSBHub.m in Sources */, 3A41C9CB1859100500141593 /* NSObject+FBAdditions.m in Sources */, B574FDC41861159D00668D86 /* FBOrigamiAdditions+TextFieldShortcuts.m in Sources */, 3AACD304185933AE0040742B /* NSString+FBAdditions.m in Sources */, B50816B4198060F1007AEDAF /* FBPatchView.m in Sources */, B5BB6FDA1A101FB6005725C1 /* FB3DOrientationPatch.m in Sources */, + B57799351C94DD7700B1BDC9 /* SocketIOJSONSerialization.m in Sources */, + B5C32BE41C98AB4A00E29020 /* DHStructureMultiplePathMembersPatch.m in Sources */, D9DC9D0119209DC0006B8241 /* CDEvents.m in Sources */, B50816BD1980F10E007AEDAF /* NSView+FBAdditions.m in Sources */, B5BB6FEC1A102083005725C1 /* PTProtocol.m in Sources */, @@ -1231,41 +1562,72 @@ B596FE031A86D49500C1D132 /* FBViewerDimensionsPatch.m in Sources */, B52C925D1975D7F100AB5A6D /* FBDragAndDropPatch.m in Sources */, B51CE8DE1A9BC05D00E1C156 /* FBWirelessOutPatchUI.m in Sources */, + B577992A1C94DD7700B1BDC9 /* SBJsonParser.m in Sources */, 3AFA71071856771A00A34E93 /* FBOrigamiAdditions.m in Sources */, B574FDB518610CEE00668D86 /* QCPatchView+FBAdditions.m in Sources */, + B57799311C94DD7700B1BDC9 /* SBJsonStreamWriterAccumulator.m in Sources */, + B577994E1C94EBB600B1BDC9 /* NSAppleScript+FBAdditions.m in Sources */, B53A9AC418F31B9A00B5E151 /* FBSafariToolbarController.m in Sources */, B53A9ADD18F32B6800B5E151 /* FBHexToRGB.m in Sources */, + B57799781C95029300B1BDC9 /* FBFileUpdated.m in Sources */, B574FDBB18610DBF00668D86 /* NSObject+AssociatedObjects.m in Sources */, 3AAF9DB918BAEBF9002B591F /* NSMenu+FBAdditions.m in Sources */, + B577995D1C94F29200B1BDC9 /* NSArray+FBAdditions.m in Sources */, + B5C32BEA1C98AEC500E29020 /* NSDictionary+FBAdditions.m in Sources */, + B577997C1C95029300B1BDC9 /* FBStructureShuffle.m in Sources */, B54831E11863898E000D2B2A /* NSObject+FBNoArcAdditons.m in Sources */, + B57799391C94DD7700B1BDC9 /* base64.c in Sources */, B593F0A61A2737DF003C664A /* FBOrigamiAdditions+CodeExport.m in Sources */, B574FDAF1861099400668D86 /* FBOrigamiAdditions+DragAndDrop.m in Sources */, + B577993E1C94DDB300B1BDC9 /* NSURL+FBAdditions.m in Sources */, B52C92641975D93000AB5A6D /* QCView+FBAdditions.m in Sources */, + B577998A1C954C1700B1BDC9 /* DHImageWithFormattedStrings.m in Sources */, + B57799801C95029300B1BDC9 /* FBFPS.m in Sources */, + B577992C1C94DD7700B1BDC9 /* SBJsonStreamParserAccumulator.m in Sources */, B51CE8D81A9BC05D00E1C156 /* FBWirelessInPatch.m in Sources */, + B577992F1C94DD7700B1BDC9 /* SBJsonStreamTokeniser.m in Sources */, B5BB6FEB1A102083005725C1 /* PTChannel.m in Sources */, + B577998E1C954DAF00B1BDC9 /* NSColor+HTMLExtensions.m in Sources */, + B57799361C94DD7700B1BDC9 /* SocketIOPacket.m in Sources */, + B57799341C94DD7700B1BDC9 /* SocketIO.m in Sources */, B5BB6FF61A102146005725C1 /* FBHashValue.m in Sources */, + B5C32BE21C98AB4A00E29020 /* DHStructureAllValuesPatch.m in Sources */, + B577994D1C94EBB600B1BDC9 /* NSAppleEventDescriptor+FBAdditions.m in Sources */, B53A9ADF18F32B6800B5E151 /* FBRGBToHex.m in Sources */, + B57799651C94F72000B1BDC9 /* DHRESTRequest.m in Sources */, + B577993B1C94DD7700B1BDC9 /* SRWebSocket.m in Sources */, B5BB6FD91A101FB6005725C1 /* BWDeviceInfoReceiver.m in Sources */, 1A7A712718D7CBE80008D05A /* FBOMultiSwitchPatch.m in Sources */, + B57799411C94DF1000B1BDC9 /* NSString+RelativePath.m in Sources */, B54831BB18627462000D2B2A /* FBOrigamiAdditions+Retina.m in Sources */, B5D764421A30FEBF0054F72F /* FBOStructureCreatorPatchUI.m in Sources */, + B577992D1C94DD7700B1BDC9 /* SBJsonStreamParserAdapter.m in Sources */, EC9AA2E0191B0C2C00F2209F /* POPBouncyPatch.mm in Sources */, + B577993A1C94DD7700B1BDC9 /* NSData+SRB64Additions.m in Sources */, B5D7643F1A30FD9A0054F72F /* FBOStructureCreatorPatch.m in Sources */, + B5C32BE61C98AB4A00E29020 /* DHStructurePathMemberPatch.m in Sources */, + B57799591C94F03800B1BDC9 /* DHImageAtURLPatch.m in Sources */, 3A9D3E0118C134E20021812A /* FBOrigamiAdditions+Preferences.m in Sources */, D9DC9CF219205ED7006B8241 /* FBOLiveFilePatchUI.m in Sources */, B59541B419E1F5F400B4FC3A /* FBDeviceViewController.m in Sources */, B5BB6FE01A101FB6005725C1 /* FBDeviceVibratePatch.m in Sources */, B59687C41BADEC72007682C7 /* FBLastValue.m in Sources */, B5EA169E18CE4E8200A561A4 /* FBOLayerGroup.m in Sources */, + B57799991C963A0500B1BDC9 /* DHSoundPlayerProPatch.m in Sources */, + B57799371C94DD7700B1BDC9 /* SocketIOTransportWebsocket.m in Sources */, + B57799331C94DD7700B1BDC9 /* SBJsonWriter.m in Sources */, B53A9AC218F31B9A00B5E151 /* FBSafariThemeButtons.m in Sources */, B5BB6FF31A1020DD005725C1 /* QCImage+FBAdditions.m in Sources */, B574FDA91861080400668D86 /* FBOrigamiAdditions+PluginLoading.m in Sources */, B50F45AA18F9FB92005548DF /* FBCursorPatch.m in Sources */, + B57799971C963A0500B1BDC9 /* DHStringAtURLPatch.m in Sources */, B5BB6FF01A1020AB005725C1 /* FBMutableOrderedDictionary.m in Sources */, B50277561A7C9F6B0023F1A2 /* FBOrigamiAdditions+PatchMenu.m in Sources */, B574FDB218610AD200668D86 /* QCPort+FBAdditions.m in Sources */, B574FDCA1861289E00668D86 /* FBOrigamiAdditions+WindowMods.m in Sources */, B54831D9186384B5000D2B2A /* FBOInteractionPatch.m in Sources */, + B577992B1C94DD7700B1BDC9 /* SBJsonStreamParser.m in Sources */, + B57799381C94DD7700B1BDC9 /* SocketIOTransportXHR.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Origami Plugin/FBOrigamiAdditions+Stats.h b/Origami Plugin/FBOrigamiAdditions+Stats.h new file mode 100644 index 0000000..c6aefbe --- /dev/null +++ b/Origami Plugin/FBOrigamiAdditions+Stats.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +// An anonymous count of the amount of people who use Origami every day. + +#import "FBOrigamiAdditions.h" + +@interface FBOrigamiAdditions (Stats) + +- (void)origamiDidLoad; + +@end diff --git a/Origami Plugin/FBOrigamiAdditions+Stats.m b/Origami Plugin/FBOrigamiAdditions+Stats.m new file mode 100644 index 0000000..9a6bf0f --- /dev/null +++ b/Origami Plugin/FBOrigamiAdditions+Stats.m @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +// An anonymous count of the amount of people who use Origami every day. + +#import "FBOrigamiAdditions+Stats.h" + +#define ORIGAMI_STATS_ENDPOINT @"https://origami-stats.herokuapp.com/track/origami" +#define ORIGAMI_STATS_API_KEY @"nveJ23MnX/1xo+ZcVyVBAOv0pqq2x+FJq5BzFaN3gmY=" +#define ORIGAMI_IID_KEY @"OrigamiIID" + +@implementation FBOrigamiAdditions (Stats) + +- (void)origamiDidLoad { + [self trackEvent:@"dau"]; +} + +- (void)trackEvent:(NSString *)eventName { + NSString *ep = [ORIGAMI_STATS_ENDPOINT stringByAppendingPathComponent:eventName]; + NSURL *url = [NSURL URLWithString:ep]; + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + + request.HTTPMethod = @"POST"; + NSDictionary *data = @{ @"iid": [self installationID], + @"os_version": [[NSProcessInfo processInfo] operatingSystemVersionString], + @"qc_version": [self qcVersionNumber], + @"origami_version": [[FBOrigamiAdditions origamiBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"] + }; + NSError *error; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error]; + if (jsonData){ + [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [request setValue:ORIGAMI_STATS_API_KEY forHTTPHeaderField:@"X-OGS-API-KEY"]; + + [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[jsonData length]] forHTTPHeaderField:@"Content-Length"]; + [request setHTTPBody: jsonData]; + [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:nil]; + } +} + +- (NSString *)installationID { + NSString *iid = [[NSUserDefaults standardUserDefaults] objectForKey:ORIGAMI_IID_KEY]; + if (!iid) { + iid = [[NSUUID UUID] UUIDString]; + [[NSUserDefaults standardUserDefaults] setObject:iid forKey:ORIGAMI_IID_KEY]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } + return iid; +} + +@end diff --git a/Origami Plugin/FBOrigamiPrincipal.m b/Origami Plugin/FBOrigamiPrincipal.m index 80adee7..14487ba 100755 --- a/Origami Plugin/FBOrigamiPrincipal.m +++ b/Origami Plugin/FBOrigamiPrincipal.m @@ -38,6 +38,23 @@ #import "FBWirelessOutPatch.h" #import "FBStopWatchPatch.h" #import "FBLastValue.h" +#import "DHJSONImporterPatch.h" +#import "DHAppleScriptPatch.h" +#import "DHImageAtURLPatch.h" +#import "DHRestRequestPatch.h" +#import "FBFileUpdated.h" +#import "FBDescriptionPatch.h" +#import "FBStructureShuffle.h" +#import "FBLogToConsole.h" +#import "FBFPS.h" +#import "DHImageWithFormattedStrings.h" +#import "DHStringFormatterPatch.h" +#import "DHStringAtURLPatch.h" +#import "DHSoundPlayerProPatch.h" +#import "DHStructureAllKeysPatch.h" +#import "DHStructureAllValuesPatch.h" +#import "DHStructureMultiplePathMembersPatch.h" +#import "DHStructurePathMemberPatch.h" @implementation FBOrigamiPrincipal @@ -69,7 +86,23 @@ + (void)registerNodesWithManager:(QCNodeManager*)manager { KIRegisterPatch(FBWirelessOutPatch); KIRegisterPatch(FBStopWatchPatch); KIRegisterPatch(FBLastValue); - + KIRegisterPatch(DHJSONImporterPatch); + KIRegisterPatch(DHAppleScriptPatch); + KIRegisterPatch(DHImageAtURLPatch); + KIRegisterPatch(DHRESTRequestPatch); + KIRegisterPatch(FBFileUpdated); + KIRegisterPatch(FBDescriptionPatch); + KIRegisterPatch(FBStructureShuffle); + KIRegisterPatch(FBLogToConsole); + KIRegisterPatch(FBFPS); + KIRegisterPatch(DHImageWithFormattedStrings); + KIRegisterPatch(DHStringFormatterPatch); + KIRegisterPatch(DHStringAtURLPatch); + KIRegisterPatch(DHSoundPlayerProPatch); + KIRegisterPatch(DHStructureAllKeysPatch); + KIRegisterPatch(DHStructureAllValuesPatch); + KIRegisterPatch(DHStructureMultiplePathMembersPatch); + KIRegisterPatch(DHStructurePathMemberPatch); [[FBOrigamiAdditions sharedAdditions] initialSetup]; [[BWDeviceInfoReceiver sharedReceiver] initialSetup]; diff --git a/Origami Plugin/FBStructureShuffle.h b/Origami Plugin/FBStructureShuffle.h new file mode 100644 index 0000000..8a672c2 --- /dev/null +++ b/Origami Plugin/FBStructureShuffle.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface FBStructureShuffle : QCPatch { + QCStructurePort *inputStructure; + QCBooleanPort *inputShuffleSignal; + QCStructurePort *outputStructure; +} + +@end diff --git a/Origami Plugin/FBStructureShuffle.m b/Origami Plugin/FBStructureShuffle.m new file mode 100644 index 0000000..c88d607 --- /dev/null +++ b/Origami Plugin/FBStructureShuffle.m @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "FBStructureShuffle.h" + +@implementation FBStructureShuffle + ++ (BOOL)allowsSubpatchesWithIdentifier:(id)fp8 +{ + return NO; +} + ++ (QCPatchExecutionMode)executionModeWithIdentifier:(id)fp8 +{ + return kQCPatchExecutionModeProcessor; +} + ++ (QCPatchTimeMode)timeModeWithIdentifier:(id)fp8 +{ + return kQCPatchTimeModeNone; +} + +- (BOOL)execute:(QCOpenGLContext *)context time:(double)time arguments:(NSDictionary *)arguments { + BOOL leadingEdge = [inputShuffleSignal wasUpdated] && [inputShuffleSignal booleanValue] == YES; + + if (leadingEdge || [inputStructure wasUpdated]) { + NSArray *array = [[inputStructure structureValue] arrayRepresentation]; + + NSMutableArray *mutableArray = [array mutableCopy]; + + for (NSUInteger i = array.count; i > 1; i--) { + NSUInteger j = arc4random_uniform(i); + [mutableArray exchangeObjectAtIndex:i-1 withObjectAtIndex:j]; + } + + [outputStructure setStructureValue:[[QCStructure alloc] initWithArray:mutableArray]]; + } + + return YES; +} + +@end diff --git a/Origami Plugin/FBStructureShuffle.xml b/Origami Plugin/FBStructureShuffle.xml new file mode 100755 index 0000000..d85b47a --- /dev/null +++ b/Origami Plugin/FBStructureShuffle.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch takes an indexed structure and outputs a structure with each member in a random position. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + Structure Shuffle + + inputAttributes + + inputStructure + + description + Structure to be shuffled + name + Structure + + inputShuffleSignal + + description + Shuffles on the leading edge of the signal + name + Shuffle Signal + + + outputAttributes + + outputStructure + + description + Shuffled structure + name + Structure + + + + \ No newline at end of file diff --git a/Origami Plugin/Info.plist b/Origami Plugin/Info.plist index aac6ab9..b514587 100755 --- a/Origami Plugin/Info.plist +++ b/Origami Plugin/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.3 + 2.2 CFBundleVersion - 14 + 16 GFPlugIn NSHumanReadableCopyright diff --git a/Origami Plugin/NSAppleEventDescriptor+FBAdditions.h b/Origami Plugin/NSAppleEventDescriptor+FBAdditions.h new file mode 100644 index 0000000..6045f1f --- /dev/null +++ b/Origami Plugin/NSAppleEventDescriptor+FBAdditions.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface NSAppleEventDescriptor (FBAdditions) +- (id)objectValue; +@end diff --git a/Origami Plugin/NSAppleEventDescriptor+FBAdditions.m b/Origami Plugin/NSAppleEventDescriptor+FBAdditions.m new file mode 100644 index 0000000..cc41d17 --- /dev/null +++ b/Origami Plugin/NSAppleEventDescriptor+FBAdditions.m @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "NSAppleEventDescriptor+FBAdditions.h" +#import "NSDate+FBAdditions.h" +#import "NSURL+FBAdditions.h" + +static NSString *NSAppleScriptDescriptorTypeBooleanTrue = @"'true'"; +static NSString *NSAppleScriptDescriptorTypeBooleanFalse = @"'fals'"; +static NSString *NSAppleScriptDescriptorTypeString = @"'utxt'"; +static NSString *NSAppleScriptDescriptorTypeEnum = @"'enum'"; +static NSString *NSAppleScriptDescriptorTypeDouble = @"'doub'"; +static NSString *NSAppleScriptDescriptorTypeLong = @"'long'"; +static NSString *NSAppleScriptDescriptorTypeTIFFImage = @"'TIFF'"; +static NSString *NSAppleScriptDescriptorTypeJPEGImage = @"'JPEG'"; +static NSString *NSAppleScriptDescriptorTypeTDTAImage = @"'tdta'"; +static NSString *NSAppleScriptDescriptorTypeNull = @"'null'"; +static NSString *NSAppleScriptDescriptorTypeObject = @"'obj '"; +static NSString *NSAppleScriptDescriptorTypeList = @"'list'"; +static NSString *NSAppleScriptDescriptorTypeRecord = @"'reco'"; +static NSString *NSAppleScriptDescriptorTypeAlias = @"'alis'"; +static NSString *NSAppleScriptDescriptorTypeDate = @"'ldt '"; + +@implementation NSAppleEventDescriptor (DHTools) + +- (id)objectValue { + DescType descriptorTypeCode = [self descriptorType]; + + if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeNull)) { + return [NSNull null]; + + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeBooleanTrue)) { + return @(YES); + + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeBooleanFalse)) { + return @(NO); + + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeString) || + descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeEnum)) { + return self.stringValue; + + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeDouble) || + descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeLong)) { + return [NSNumber numberWithDouble:[self.stringValue doubleValue]]; + + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeTIFFImage) || + descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeTDTAImage) || + descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeJPEGImage)) { + NSData *imageData = self.data; + return imageData ? [[NSImage alloc] initWithData:self.data] : nil; + + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeObject)) { + return self.description; + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeAlias)) { + return [NSURL URLWithEventDescriptor:self]; + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeDate)) { + return [NSDate dateWithEventDescriptor:self]; + } else if (descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeList) || + descriptorTypeCode == NSHFSTypeCodeFromFileType(NSAppleScriptDescriptorTypeRecord)) { + NSUInteger numberOfItems = self.numberOfItems; + NSMutableArray *array = [NSMutableArray arrayWithCapacity:numberOfItems]; + NSUInteger index; + for (index = 1; index < (numberOfItems + 1); index++) { + id object = [self descriptorAtIndex:index].objectValue; + if (!object) { + object = [NSNull null]; + } + [array addObject:object]; + } + return array; + } else { + NSLog(@"Unkown Type: %@", NSFileTypeForHFSTypeCode(descriptorTypeCode)); + return self.stringValue; + } +} + +@end diff --git a/Origami Plugin/NSAppleScript+FBAdditions.h b/Origami Plugin/NSAppleScript+FBAdditions.h new file mode 100644 index 0000000..e351b58 --- /dev/null +++ b/Origami Plugin/NSAppleScript+FBAdditions.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface NSAppleScript (FBAdditions) ++ (NSString *)runScript:(NSString *)script; ++ (id)runScript:(NSString *)script error:(NSString **)error; ++ (NSString *)runScript:(NSString *)script inApplication:(NSString *)applicationName; +@end + +static id RunAppleScript(NSString *application, NSString *script) { + return [NSAppleScript runScript:script inApplication:application]; +} diff --git a/Origami Plugin/NSAppleScript+FBAdditions.m b/Origami Plugin/NSAppleScript+FBAdditions.m new file mode 100644 index 0000000..829ca37 --- /dev/null +++ b/Origami Plugin/NSAppleScript+FBAdditions.m @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "NSAppleScript+FBAdditions.h" +#import "NSAppleEventDescriptor+FBAdditions.h" + +@implementation NSAppleScript (FBAdditions) + ++ (id)runScript:(NSString *)script { + NSString *error = nil; + id result = [NSAppleScript runScript:script error:&error]; + if (error) { + NSLog(@"AppleScript Error: %@", error.description); + return nil; + } + return result; +} + ++ (id)runScript:(NSString *)script error:(NSString **)error { + NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script]; + + NSDictionary *errors = nil; + NSAppleEventDescriptor *eventDescriptor = [appleScript executeAndReturnError:&errors]; + + if (errors) { + if (error) { + *error = errors.description; + } + return nil; + } + + return eventDescriptor.objectValue; +} + ++ (id)runScript:(NSString *)script inApplication:(NSString *)applicationName { + return [NSAppleScript runScript:[NSString stringWithFormat:@"tell application \"%@\" to %@", applicationName, script]]; +} + +@end diff --git a/Origami Plugin/NSArray+FBAdditions.h b/Origami Plugin/NSArray+FBAdditions.h new file mode 100644 index 0000000..59261f4 --- /dev/null +++ b/Origami Plugin/NSArray+FBAdditions.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface NSArray (FBAdditions) + +- (NSArray *)sortedArrayUsingAlphabeticalSort; + +@end diff --git a/Origami Plugin/NSArray+FBAdditions.m b/Origami Plugin/NSArray+FBAdditions.m new file mode 100644 index 0000000..9abd434 --- /dev/null +++ b/Origami Plugin/NSArray+FBAdditions.m @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "NSArray+FBAdditions.h" + +@implementation NSArray (FBAdditions) + +id _bestRepresentationForKey(id key) { + if ([key isKindOfClass:[NSString class]]) { + NSNumber *numberRepresentation = [NSNumber numberWithInteger:[key integerValue]]; + if ([key isEqualToString:[numberRepresentation stringValue]]) { + return numberRepresentation; + } + } + + return key; +} + +- (NSArray *)sortedArrayUsingAlphabeticalSort { + return [self sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { + a = _bestRepresentationForKey(a); + b = _bestRepresentationForKey(b); + + if ([a isKindOfClass:[NSString class]] && [b isKindOfClass:[NSString class]]) { + return [a caseInsensitiveCompare:b]; + } else if ([a isKindOfClass:[NSNumber class]] && [b isKindOfClass:[NSNumber class]]) { + return [a compare:b]; + } else if ([a isKindOfClass:[NSNumber class]] && [b isKindOfClass:[NSString class]]) { + return [[a stringValue] caseInsensitiveCompare:b]; + } else if ([a isKindOfClass:[NSString class]] && [b isKindOfClass:[NSNumber class]]) { + return [a caseInsensitiveCompare:[b stringValue]]; + } else { + return NSOrderedSame; + } + }]; +} + +@end diff --git a/Origami Plugin/NSColor+HTMLExtensions.h b/Origami Plugin/NSColor+HTMLExtensions.h new file mode 100644 index 0000000..9f24c46 --- /dev/null +++ b/Origami Plugin/NSColor+HTMLExtensions.h @@ -0,0 +1,16 @@ +// +// NSColor+HTMLExtensions.h +// DHTools +// +// Created by Drew Hamlin on 4/18/13. +// +// + +#import + +@interface NSColor (HTMLExtensions) + ++ (NSColor *)colorWithHexString:(NSString *)hexString; +- (NSString *)hexStringRepresentation; + +@end diff --git a/Origami Plugin/NSColor+HTMLExtensions.m b/Origami Plugin/NSColor+HTMLExtensions.m new file mode 100644 index 0000000..b3f368c --- /dev/null +++ b/Origami Plugin/NSColor+HTMLExtensions.m @@ -0,0 +1,72 @@ +// +// NSColor+HTMLExtensions.m +// DHTools +// +// Created by Drew Hamlin on 4/18/13. +// +// + +#import "NSColor+HTMLExtensions.h" + +@implementation NSColor (HTMLExtensions) + ++ (NSColor *)colorWithHexString:(NSString *)hexString { + if ([hexString hasPrefix:@"#"]) { + hexString = [hexString substringFromIndex:1]; + } + + BOOL shorthand = (hexString.length == 3); + NSInteger charactersToRead = shorthand ? 1: 2; + + NSMutableArray *values = [NSMutableArray arrayWithCapacity:3]; + NSInteger start = 0; + while (start < hexString.length) { + NSString *substring = [hexString substringWithRange:NSMakeRange(start, charactersToRead)]; + if (shorthand) { + substring = [substring stringByAppendingString:substring]; + } + NSScanner *substringScanner = [NSScanner scannerWithString:substring]; + unsigned int result; + [substringScanner scanHexInt:&result]; + [values addObject:@(result)]; + start += charactersToRead; + } + + if (values.count < 3) { + return [NSColor whiteColor]; + } + + CGFloat red = [[values objectAtIndex:0] doubleValue], green = [[values objectAtIndex:1] doubleValue], blue = [[values objectAtIndex:2] doubleValue]; + NSColor *color = [NSColor colorWithCalibratedRed:(red / 255.0) green:(green / 255.0) blue:(blue / 255.0) alpha:1.0]; + return color; +} + +- (NSString *)hexStringRepresentation { + NSColor *rgb = [self colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + CGFloat redf, greenf, bluef, alphaf; + [rgb getRed:&redf green:&greenf blue:&bluef alpha:&alphaf]; + int red = (int)round(redf * 255), green = (int)round(greenf * 255), blue = (int)round(bluef * 255); + NSString *alpha = [NSString stringWithFormat:@"%.2f", alphaf]; + + if (NO && (1.0 - alphaf) >= 0.000001) { + return [NSString stringWithFormat:@"rgba(%d, %d, %d, %@)", red, green, blue, alpha]; + + } else { + NSArray *intValues = @[@(red), @(green), @(blue)]; + NSMutableArray *hexValues = [NSMutableArray arrayWithCapacity:3]; + NSMutableArray *shorthandHexValues = [NSMutableArray arrayWithCapacity:3]; + + BOOL shorthand = YES; + for (NSNumber *value in intValues) { + NSString *hex = [NSString stringWithFormat:@"%02x", [value intValue]]; + shorthand &= [hex characterAtIndex:0] == [hex characterAtIndex:1]; + [hexValues addObject:hex]; + [shorthandHexValues addObject:[hex substringToIndex:1]]; + } + + id values = !shorthand ? hexValues : shorthandHexValues; + return [NSString stringWithFormat:@"#%@%@%@", [values objectAtIndex:0], [values objectAtIndex:1], [values objectAtIndex:2]]; + } +} + +@end diff --git a/Origami Plugin/NSDate+FBAdditions.h b/Origami Plugin/NSDate+FBAdditions.h new file mode 100644 index 0000000..0c04d0e --- /dev/null +++ b/Origami Plugin/NSDate+FBAdditions.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface NSDate (FBAdditions) + ++ (NSDate *)dateWithEventDescriptor:(NSAppleEventDescriptor *)descriptor; +- (NSString *)formattedDate:(NSString *)dateFormat; + +@end diff --git a/Origami Plugin/NSDate+FBAdditions.m b/Origami Plugin/NSDate+FBAdditions.m new file mode 100644 index 0000000..7bc536f --- /dev/null +++ b/Origami Plugin/NSDate+FBAdditions.m @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "NSDate+FBAdditions.h" + +@implementation NSDate (FBAdditions) + ++ (NSDate *)dateWithEventDescriptor:(NSAppleEventDescriptor *)descriptor { + NSDate *date = nil; + + CFAbsoluteTime absoluteTime; + LongDateTime longDateTime; + + if ([descriptor descriptorType] == typeLongDateTime) { + [[descriptor data] getBytes:&longDateTime length:sizeof(longDateTime)]; + OSStatus status = UCConvertLongDateTimeToCFAbsoluteTime(longDateTime, &absoluteTime); + if (status == noErr) { + date = (NSDate *)CFBridgingRelease(CFDateCreate(NULL, absoluteTime)); + } + } + + return date; +} + +- (NSString *)formattedDate:(NSString *)dateFormat { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:dateFormat]; + return [dateFormatter stringFromDate:self]; +} + + +@end diff --git a/Origami Plugin/NSDictionary+FBAdditions.h b/Origami Plugin/NSDictionary+FBAdditions.h new file mode 100644 index 0000000..5949394 --- /dev/null +++ b/Origami Plugin/NSDictionary+FBAdditions.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@interface NSDictionary (FBAdditions) + +- (id)valueAtPath:(NSArray *)path; +- (id)valueAtPath:(NSArray *)path untraversedPaths:(NSArray **)untraversedPaths; +- (QCStructure *)arrayOrDictionaryStructureFromDictionary; + +@end diff --git a/Origami Plugin/NSDictionary+FBAdditions.m b/Origami Plugin/NSDictionary+FBAdditions.m new file mode 100644 index 0000000..5253437 --- /dev/null +++ b/Origami Plugin/NSDictionary+FBAdditions.m @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "NSDictionary+FBAdditions.h" +#import "NSArray+FBAdditions.h" +#import "NSString+FBAdditions.h" + +@implementation NSDictionary (FBAdditions) + +- (id)valueAtPath:(NSArray *)path { + return [self valueAtPath:path untraversedPaths:nil]; +} + +- (id)valueAtPath:(NSArray *)path untraversedPaths:(NSArray **)untraversedPaths { + NSDictionary *dictionary = self; + BOOL _debug = NO; + + if (_debug) NSLog(@"_objectAtPath:(%@) inDictionary:(%@)", path, dictionary); + if (![path count] || ![dictionary count]) { + return nil; + } + + // The function is recursive so we examine only the first component every time + NSMutableString *nextPathComponent = [[path objectAtIndex:0] mutableCopy]; + BOOL wildcard = ([nextPathComponent isEqualToString:@"*"]); + NSNumber *numericalKey = nil; + + // Check to see if the key value is an index number + NSUInteger numericalIndexForKey = NSNotFound; + if ([nextPathComponent isEqualToString:[[NSNumber numberWithInteger:[nextPathComponent integerValue]] stringValue]]) { + numericalIndexForKey = [nextPathComponent integerValue]; + } else if ([nextPathComponent hasPrefix:@"["] && [nextPathComponent hasSuffix:@"]"] && [nextPathComponent length] >= 3) { + numericalIndexForKey = [[nextPathComponent substringWithRange:NSMakeRange(1, nextPathComponent.length - 2)] integerValue]; + } + if (numericalIndexForKey != NSNotFound && numericalIndexForKey < [dictionary count]) { + NSArray *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(compare:)]; + + id keyForNumericalIndex = [sortedKeys objectAtIndex:numericalIndexForKey]; + if ([keyForNumericalIndex isKindOfClass:[NSString class]]) { + [nextPathComponent setString:keyForNumericalIndex]; + } else if ([keyForNumericalIndex isKindOfClass:[NSNumber class]]) { + nextPathComponent = nil; + numericalKey = keyForNumericalIndex; + } + } + + if (nextPathComponent) { + [nextPathComponent replaceOccurrencesOfString:@"\\[" withString:@"[" options:0 range:NSMakeRange(0, nextPathComponent.length)]; + [nextPathComponent replaceOccurrencesOfString:@"\\\\" withString:@"\\" options:0 range:NSMakeRange(0, nextPathComponent.length)]; + } + + NSArray *keysSpecified = [nextPathComponent componentsSeparatedByUnescapedDelimeter:@"+"]; + + // If there's a wildcard, investigate every branch; otherwise take the specified component key(s) + NSArray *keysToTraverse; + if (wildcard) { + keysToTraverse = [dictionary allKeys]; + } else if (nextPathComponent) { + keysToTraverse = keysSpecified; + } else if (numericalKey) { + keysToTraverse = @[numericalKey]; + } + if (_debug) NSLog(@"keysToTraverse = %@", keysToTraverse); + + // For index-based keys, sort them numerically + BOOL allKeysAreNumbers = YES; + for (id key in keysToTraverse) { + if (![key isKindOfClass:[NSNumber class]]) { + allKeysAreNumbers = NO; + break; + } + } + if (allKeysAreNumbers) { + keysToTraverse = [keysToTraverse sortedArrayUsingAlphabeticalSort]; + } + + NSMutableArray *results = [NSMutableArray arrayWithCapacity:[keysToTraverse count]]; + NSMutableArray *resultingUntraversedPaths = [NSMutableArray arrayWithCapacity:[path count]]; + + // For the non-wildcard case, this loop will only execute once + for (id key in keysToTraverse) { + id object = [dictionary objectForKey:key]; + + NSMutableArray *pathsToTraverseOnBranch = [path mutableCopy]; + if ([path count]) { + [pathsToTraverseOnBranch removeObjectAtIndex:0]; + } + + // Start with the assumption that we find a value + NSArray *untraversedPathsOnBranch = pathsToTraverseOnBranch; + + // If we encounter a dictionary result and still have work to do, invoke the recursive procuedure + if ([object isKindOfClass:[NSDictionary class]] && [pathsToTraverseOnBranch count]) { + object = [object valueAtPath:pathsToTraverseOnBranch untraversedPaths:&untraversedPathsOnBranch]; + } + + if (_debug) NSLog(@"object = %@ for key %@", object, key); + if (object) { + // Collect the result + [results addObject:object]; + } else { + // No value: revert the earlier assumption + [results addObject:[NSNull null]]; + untraversedPathsOnBranch = [[NSArray arrayWithObject:nextPathComponent] arrayByAddingObjectsFromArray:untraversedPathsOnBranch]; + } + + if (untraversedPathsOnBranch) { + // Collect the untraversed path result, which may have been replaced by the recursion + if (_debug) NSLog(@"Untraversed: %@", untraversedPathsOnBranch); + [resultingUntraversedPaths addObject:untraversedPathsOnBranch]; + } + } + + // Flatten results and paths if necessary + if ([results count] == 1) { + results = [results objectAtIndex:0]; + } + if ([resultingUntraversedPaths count] == 1) { + [resultingUntraversedPaths setArray:[resultingUntraversedPaths objectAtIndex:0]]; + } + + // Resolve a remaining untraversed index on the entire result set, if possible + if ([results isKindOfClass:[NSArray class]] && [results count] > 1 && [resultingUntraversedPaths count] == 1) { + id untraversedComponent = [resultingUntraversedPaths lastObject]; + NSUInteger untraversedIndex = [untraversedComponent integerValue]; + if ([untraversedComponent isEqualToString:[[NSNumber numberWithInteger:untraversedIndex] stringValue]]) { + [results setArray:[NSArray arrayWithObject:[results objectAtIndex:untraversedIndex]]]; + } + } + + // If all of our child paths are identical or empty, flatten the result as a single array + BOOL untraversedPathsAreAllIdentical = YES; + BOOL virginity = YES; + id candidateForTestingIdenticalness = nil; + for (id branchPath in resultingUntraversedPaths) { + if (virginity) { + candidateForTestingIdenticalness = branchPath; + virginity = NO; + continue; + } + if (![branchPath isEqual:candidateForTestingIdenticalness]) { + untraversedPathsAreAllIdentical = NO; + break; + } + } + if (untraversedPathsAreAllIdentical) { + if (candidateForTestingIdenticalness && ![candidateForTestingIdenticalness isKindOfClass:[NSArray class]]) { + candidateForTestingIdenticalness = @[candidateForTestingIdenticalness]; + } + if (candidateForTestingIdenticalness && [candidateForTestingIdenticalness count]) { + [resultingUntraversedPaths setArray:candidateForTestingIdenticalness]; + } else { + [resultingUntraversedPaths removeAllObjects]; + } + } + + // Pass discovered untraversed paths back to the calling function + if (_debug) NSLog(@"Resulting untraversed: %@", resultingUntraversedPaths); + if (untraversedPaths) { + *untraversedPaths = resultingUntraversedPaths; + } + + if ([keysToTraverse count] == 1) { + return results; + } + + if (allKeysAreNumbers) { + return results; + } + + // Maintain the original keys when possible + NSUInteger resultIndex; + NSMutableDictionary *dictionaryResult = [NSMutableDictionary dictionaryWithCapacity:results.count]; + for (resultIndex = 0; resultIndex < results.count; resultIndex++) { + [dictionaryResult setObject:results[resultIndex] forKey:keysToTraverse[resultIndex]]; + } + return dictionaryResult; +} + +- (QCStructure *)arrayOrDictionaryStructureFromDictionary { + NSDictionary *dictionary = self; + QCStructure *structureAsDictionary = [[QCStructure alloc] initWithDictionary:dictionary]; + + // Check to see if the structure should be an array: if it has linear numerical keys starting at 0 + NSArray *keys = [[dictionary allKeys] sortedArrayUsingAlphabeticalSort]; + NSInteger previousKey = -1; + NSMutableArray *values = [NSMutableArray arrayWithCapacity:keys.count]; + + for (id key in keys) { + if (!([key isKindOfClass:[NSNumber class]] && [key integerValue] == previousKey + 1)) { + return structureAsDictionary; + } else { + [values addObject:dictionary[key]]; + previousKey++; + } + } + + // Structure is array: just return the values + return [[QCStructure alloc] initWithArray:values]; +} + +@end diff --git a/Origami Plugin/NSString+FBAdditions.h b/Origami Plugin/NSString+FBAdditions.h index 744f460..1a5e3da 100644 --- a/Origami Plugin/NSString+FBAdditions.h +++ b/Origami Plugin/NSString+FBAdditions.h @@ -14,5 +14,8 @@ - (BOOL)fb_containsString:(NSString *)string; - (NSString *)fb_capitalizeFirstLetter; - (NSString *)relativePathFromBaseDirPath:(NSString *)baseDirPath; +- (NSArray *)componentsSeparatedByUnescapedDelimeter:(NSString *)delimeter; +- (NSArray *)componentsSeparatedByUnescapedDelimeters:(NSArray *)delimeters map:(NSArray **)delimeterMap; +- (NSString *)humanReadableString; @end diff --git a/Origami Plugin/NSString+FBAdditions.m b/Origami Plugin/NSString+FBAdditions.m index 47009fe..7b9a7fe 100644 --- a/Origami Plugin/NSString+FBAdditions.m +++ b/Origami Plugin/NSString+FBAdditions.m @@ -48,4 +48,138 @@ - (NSString *)relativePathFromBaseDirPath:(NSString *)baseDirPath { return [NSString pathWithComponents:pathComponents1]; } +- (NSArray *)componentsSeparatedByUnescapedDelimeter:(NSString *)delimeter { + return [self componentsSeparatedByUnescapedDelimeters:@[delimeter] map:NULL]; +} + +- (NSArray *)componentsSeparatedByUnescapedDelimeters:(NSArray *)delimeters map:(NSArray **)delimeterMap { + BOOL _debug = NO; + + if (!(delimeters && [delimeters count])) { + return nil; + } + + static NSString *escape = @"\\"; + static NSString *doubleEscape = @"\\\\"; + + NSMutableArray *components = [NSMutableArray array]; + NSMutableArray *map = [NSMutableArray array]; + + NSScanner *inputScanner = [NSScanner scannerWithString:self]; + [inputScanner setCharactersToBeSkipped:nil]; + + NSString *previousDelimeter = nil; + + while (![inputScanner isAtEnd]) { + NSString *candidate = nil, *previousCandidate = nil, *delimeter = nil; + + do { + previousCandidate = candidate; + + NSString *stringToBeScanned = [[inputScanner string] substringFromIndex:[inputScanner scanLocation]]; + if (_debug) NSLog(@"stringToBeScanned: %@", stringToBeScanned); + NSUInteger firstOccuringLocation = NSNotFound; + NSString *firstOccuringDelimeter = nil; + for (NSString *delimeterCandidate in delimeters) { + NSUInteger delimeterCandidateLocation = [stringToBeScanned rangeOfString:delimeterCandidate].location; + if ((delimeterCandidateLocation != NSNotFound) && + (firstOccuringLocation == NSNotFound || delimeterCandidateLocation < firstOccuringLocation)) { + firstOccuringLocation = delimeterCandidateLocation; + firstOccuringDelimeter = delimeterCandidate; + if (_debug) NSLog(@"firstOccuringDelimeter: %@", firstOccuringDelimeter); + break; + } + } + delimeter = firstOccuringDelimeter; + if (!delimeter) { + [inputScanner scanString:stringToBeScanned intoString:&candidate]; + delimeter = [delimeters objectAtIndex:0]; + break; + } + + [inputScanner scanUpToString:delimeter intoString:&candidate]; + [inputScanner scanString:delimeter intoString:NULL]; + + if (previousCandidate && candidate) { + candidate = [NSString stringWithFormat:@"%@%@%@", previousCandidate, delimeter, candidate]; + if (_debug) NSLog(@"candidate: %@", candidate); + } + } while ([candidate hasSuffix:escape] && ![candidate hasSuffix:doubleEscape] && ![inputScanner isAtEnd]); + + if (candidate) { + if (_debug) NSLog(@"done. canddiate: %@", candidate); + candidate = [candidate stringByReplacingOccurrencesOfString:doubleEscape withString:escape]; + for (NSString *delimeterToEscape in delimeters) { + NSString *delimeterEscape = [escape stringByAppendingString:delimeterToEscape]; + candidate = [candidate stringByReplacingOccurrencesOfString:delimeterEscape withString:delimeterToEscape]; + } + [components addObject:candidate]; + if (_debug) NSLog(@"components: %@", components); + } else { + [components addObject:@""]; + } + + if (delimeter && [delimeter isEqualToString:previousDelimeter]) { + [map addObject:delimeter]; + previousDelimeter = nil; + } else { + [map addObject:@""]; + previousDelimeter = delimeter; + } + } + + if (delimeterMap) { + *delimeterMap = map; + } + + return components; +} + +- (NSString *)humanReadableString { + NSMutableString *key = [[self capitalizedString] mutableCopy]; + [key replaceOccurrencesOfString:@"_" withString:@" " options:0 range:NSMakeRange(0, key.length)]; + + NSArray *exceptions = [NSArray arrayWithObjects: + @"an", @"and", @"at", @"but", @"for", @"from", @"in", @"of", @"or", @"to", @"with", @"within", @"without", + @"AT&T", @"HTTP", @"HTTPS", @"ID", @"IP", @"IDEO", @"UFO", @"URI", @"URL", @"URLs", @"VPN", @"WAN", + @"eBay", @"eMac", @"eMate", @"iCal", @"iMac", @"iPad", @"iPhone", @"iPod", @"iTunes", @"iWork", @"QuickTime", nil]; + + for (NSString *exception in exceptions) { + NSRange previousExceptionRange = NSMakeRange(NSNotFound, 0); + NSRange searchRange = NSMakeRange(0, [key length]); + + while (YES) { + NSRange foundExceptionRange = [key rangeOfString:exception options:NSCaseInsensitiveSearch range:searchRange]; + if (foundExceptionRange.location == NSNotFound || NSEqualRanges(foundExceptionRange, previousExceptionRange)) { + break; + } + + BOOL lowercasedException = ([exception isEqualToString:[exception lowercaseString]]); + + // Only perform the replacement for lowercased exceptions past the first character + if (!lowercasedException || foundExceptionRange.location > 0) { + NSInteger precedingCharacterIndex = (foundExceptionRange.location - 1); + NSInteger subsequentCharacterIndex = (foundExceptionRange.location + foundExceptionRange.length); + if ((precedingCharacterIndex < 0 || [key characterAtIndex:precedingCharacterIndex] == ' ') && + ([key length] <= subsequentCharacterIndex || [key characterAtIndex:subsequentCharacterIndex] == ' ')) { + [key replaceCharactersInRange:foundExceptionRange withString:exception]; + } + } + + previousExceptionRange = foundExceptionRange; + searchRange.location = foundExceptionRange.location + 1; + searchRange.length = [key length] - searchRange.location; + if (searchRange.location >= [key length]) { + break; + } + } + } + + if ([key hasPrefix:@"Http://"] || [key hasPrefix:@"Https://"]) { + [key setString:[key lowercaseString]]; + } + + return [key stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + @end diff --git a/Origami Plugin/NSString+RelativePath.h b/Origami Plugin/NSString+RelativePath.h new file mode 100755 index 0000000..fecb4b5 --- /dev/null +++ b/Origami Plugin/NSString+RelativePath.h @@ -0,0 +1,18 @@ +// +// NSString+RelativePath.h +// +// Created by numata on 2010/01/12. +// Copyright 2010 Satoshi Numata. All rights reserved. +// + +#import + + +@interface NSString (RelativePath) + +- (NSString *)absolutePathFromBaseDirPath:(NSString *)baseDirPath; +- (NSString *)relativePathFromBaseDirPath:(NSString *)baseDirPath; + +@end + + diff --git a/Origami Plugin/NSString+RelativePath.m b/Origami Plugin/NSString+RelativePath.m new file mode 100755 index 0000000..56d7739 --- /dev/null +++ b/Origami Plugin/NSString+RelativePath.m @@ -0,0 +1,78 @@ +// +// NSString+RelativePath.m +// +// Created by numata on 2010/01/12. +// Copyright 2010 Satoshi Numata. All rights reserved. +// + +#import "NSString+RelativePath.h" + + +@implementation NSString (RelativePath) + +- (NSString *)absolutePathFromBaseDirPath:(NSString *)baseDirPath +{ + if ([self hasPrefix:@"~"]) { + return [self stringByExpandingTildeInPath]; + } + + NSString *theBasePath = [baseDirPath stringByExpandingTildeInPath]; + + if (![self hasPrefix:@"."]) { + return [theBasePath stringByAppendingPathComponent:self]; + } + + NSMutableArray *pathComponents1 = [NSMutableArray arrayWithArray:[self pathComponents]]; + NSMutableArray *pathComponents2 = [NSMutableArray arrayWithArray:[theBasePath pathComponents]]; + + while ([pathComponents1 count] > 0) { + NSString *topComponent1 = [pathComponents1 objectAtIndex:0]; + [pathComponents1 removeObjectAtIndex:0]; + + if ([topComponent1 isEqualToString:@".."]) { + if ([pathComponents2 count] == 1) { + // Error + return nil; + } + [pathComponents2 removeLastObject]; + } else if ([topComponent1 isEqualToString:@"."]) { + // Do nothing + } else { + [pathComponents2 addObject:topComponent1]; + } + } + + return [NSString pathWithComponents:pathComponents2]; +} + +- (NSString *)relativePathFromBaseDirPath:(NSString *)baseDirPath +{ + NSString *thePath = [self stringByExpandingTildeInPath]; + NSString *theBasePath = [baseDirPath stringByExpandingTildeInPath]; + + NSMutableArray *pathComponents1 = [NSMutableArray arrayWithArray:[thePath pathComponents]]; + NSMutableArray *pathComponents2 = [NSMutableArray arrayWithArray:[theBasePath pathComponents]]; + + // Remove same path components + while ([pathComponents1 count] > 0 && [pathComponents2 count] > 0) { + NSString *topComponent1 = [pathComponents1 objectAtIndex:0]; + NSString *topComponent2 = [pathComponents2 objectAtIndex:0]; + if (![topComponent1 isEqualToString:topComponent2]) { + break; + } + [pathComponents1 removeObjectAtIndex:0]; + [pathComponents2 removeObjectAtIndex:0]; + } + + // Create result path + for (int i = 0; i < [pathComponents2 count]; i++) { + [pathComponents1 insertObject:@".." atIndex:0]; + } + if ([pathComponents1 count] == 0) { + return @"."; + } + return [NSString pathWithComponents:pathComponents1]; +} + +@end + diff --git a/Origami Plugin/NSURL+FBAdditions.h b/Origami Plugin/NSURL+FBAdditions.h new file mode 100644 index 0000000..1226b1b --- /dev/null +++ b/Origami Plugin/NSURL+FBAdditions.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@interface NSURL (FBAdditions) + ++ (NSURL *)URLWithQuartzComposerLocation:(NSString *)location relativeToDocument:(NSDocument *)document; ++ (NSURL *)URLWithEventDescriptor:(NSAppleEventDescriptor *)appleEventDescriptor; + +@end diff --git a/Origami Plugin/NSURL+FBAdditions.m b/Origami Plugin/NSURL+FBAdditions.m new file mode 100644 index 0000000..9a305e8 --- /dev/null +++ b/Origami Plugin/NSURL+FBAdditions.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import "NSURL+FBAdditions.h" +#import "NSString+RelativePath.h" + +@implementation NSURL (FBAdditions) + ++ (NSURL *)URLWithQuartzComposerLocation:(NSString *)location relativeToDocument:(NSDocument *)document { + NSURL *url = [NSURL URLWithString:location]; + + if (!url.scheme.length) { + location = [location stringByStandardizingPath]; + if (!location.isAbsolutePath && document.fileURL) { + NSString *baseDirPath = [document.fileURL.path stringByDeletingLastPathComponent]; + location = [location absolutePathFromBaseDirPath:baseDirPath]; + } + + url = [NSURL fileURLWithPath:location]; + } + + return url; +} + +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + ++ (NSURL *)URLWithEventDescriptor:(NSAppleEventDescriptor *)appleEventDescriptor { + unsigned int theSize = (unsigned int)AEGetDescDataSize([appleEventDescriptor aeDesc]); + Handle aliasHandle = NewHandle(theSize); + HLock(aliasHandle); + AEGetDescData([appleEventDescriptor aeDesc], *aliasHandle, theSize); + HUnlock(aliasHandle); + + NSURL *url = nil; + FSRef theTarget; + Boolean theWasChanged; + if (FSResolveAlias(NULL, (AliasHandle)aliasHandle, &theTarget, &theWasChanged) == noErr) { + url = (NSURL *)CFBridgingRelease(CFURLCreateFromFSRef(kCFAllocatorDefault, &theTarget)); + } + + DisposeHandle(aliasHandle); + return url; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJson.h b/Origami Plugin/SocketIO/SBJson/SBJson.h new file mode 100755 index 0000000..58b9acb --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJson.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2009-2011 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonParser.h" +#import "SBJsonWriter.h" +#import "SBJsonStreamParser.h" +#import "SBJsonStreamParserAdapter.h" +#import "SBJsonStreamWriter.h" +#import "SBJsonStreamTokeniser.h" + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonParser.h b/Origami Plugin/SocketIO/SBJson/SBJsonParser.h new file mode 100755 index 0000000..ddfff03 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonParser.h @@ -0,0 +1,86 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +/** + Parse JSON Strings and NSData objects + + This uses SBJsonStreamParser internally. + + */ + +@interface SBJsonParser : NSObject + +/** + The maximum recursing depth. + + Defaults to 32. If the input is nested deeper than this the input will be deemed to be + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can + turn off this security feature by setting the maxDepth value to 0. + */ +@property NSUInteger maxDepth; + +/** + Description of parse error + + This method returns the trace of the last method that failed. + You need to check the return value of the call you're making to figure out + if the call actually failed, before you know call this method. + + @return A string describing the error encountered, or nil if no error occured. + + */ +@property(copy) NSString *error; + +/** + Return the object represented by the given NSData object. + + The data *must* be UTF8 encoded. + + @param data An NSData containing UTF8 encoded data to parse. + @return The NSArray or NSDictionary represented by the object, or nil if an error occured. + + */ +- (id)objectWithData:(NSData*)data; + +/** + Parse string and return the represented dictionary or array. + + Calls objectWithData: internally. + + @param string An NSString containing JSON text. + + @return The NSArray or NSDictionary represented by the object, or nil if an error occured. + */ +- (id)objectWithString:(NSString *)string; + +@end + + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonParser.m b/Origami Plugin/SocketIO/SBJson/SBJsonParser.m new file mode 100755 index 0000000..15e2823 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonParser.m @@ -0,0 +1,91 @@ +/* + Copyright (C) 2009,2010 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonParser.h" +#import "SBJsonStreamParser.h" +#import "SBJsonStreamParserAdapter.h" +#import "SBJsonStreamParserAccumulator.h" + +@implementation SBJsonParser + +@synthesize maxDepth; +@synthesize error; + +- (id)init { + self = [super init]; + if (self) + self.maxDepth = 32u; + return self; +} + + +#pragma mark Methods + +- (id)objectWithData:(NSData *)data { + + if (!data) { + self.error = @"Input was 'nil'"; + return nil; + } + + SBJsonStreamParserAccumulator *accumulator = [[SBJsonStreamParserAccumulator alloc] init]; + + SBJsonStreamParserAdapter *adapter = [[SBJsonStreamParserAdapter alloc] init]; + adapter.delegate = accumulator; + + SBJsonStreamParser *parser = [[SBJsonStreamParser alloc] init]; + parser.maxDepth = self.maxDepth; + parser.delegate = adapter; + + switch ([parser parse:data]) { + case SBJsonStreamParserComplete: + return accumulator.value; + break; + + case SBJsonStreamParserWaitingForData: + self.error = @"Unexpected end of input"; + break; + + case SBJsonStreamParserError: + self.error = parser.error; + break; + } + + return nil; +} + +- (id)objectWithString:(NSString *)string { + return [self objectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]]; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParser.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParser.h new file mode 100755 index 0000000..930df49 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParser.h @@ -0,0 +1,174 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +@class SBJsonStreamParser; +@class SBJsonStreamParserState; + +typedef enum { + SBJsonStreamParserComplete, + SBJsonStreamParserWaitingForData, + SBJsonStreamParserError, +} SBJsonStreamParserStatus; + + +/** + Delegate for interacting directly with the stream parser + + You will most likely find it much more convenient to implement the + SBJsonStreamParserAdapterDelegate protocol instead. + */ +@protocol SBJsonStreamParserDelegate + +/// Called when object start is found +- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser; + +/// Called when object key is found +- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key; + +/// Called when object end is found +- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser; + +/// Called when array start is found +- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser; + +/// Called when array end is found +- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser; + +/// Called when a boolean value is found +- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x; + +/// Called when a null value is found +- (void)parserFoundNull:(SBJsonStreamParser*)parser; + +/// Called when a number is found +- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num; + +/// Called when a string is found +- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string; + +@end + + +/** + Parse a stream of JSON data. + + Using this class directly you can reduce the apparent latency for each + download/parse cycle of documents over a slow connection. You can start + parsing *and return chunks of the parsed document* before the entire + document is downloaded. + + Using this class is also useful to parse huge documents on disk + bit by bit so you don't have to keep them all in memory. + + JSON is mapped to Objective-C types in the following way: + + - null -> NSNull + - string -> NSString + - array -> NSMutableArray + - object -> NSMutableDictionary + - true -> NSNumber's -numberWithBool:YES + - false -> NSNumber's -numberWithBool:NO + - number -> NSNumber + + Since Objective-C doesn't have a dedicated class for boolean values, + these turns into NSNumber instances. However, since these are + initialised with the -initWithBool: method they round-trip back to JSON + properly. In other words, they won't silently suddenly become 0 or 1; + they'll be represented as 'true' and 'false' again. + + Integers are parsed into either a `long long` or `unsigned long long` + type if they fit, else a `double` is used. All real & exponential numbers + are represented using a `double`. Previous versions of this library used + an NSDecimalNumber in some cases, but this is no longer the case. + + See also SBJsonStreamParserAdapter for more information. + + */ +@interface SBJsonStreamParser : NSObject + +@property (nonatomic, unsafe_unretained) SBJsonStreamParserState *state; // Private +@property (nonatomic, readonly, strong) NSMutableArray *stateStack; // Private + +/** + Expect multiple documents separated by whitespace + + Normally the -parse: method returns SBJsonStreamParserComplete when it's found a complete JSON document. + Attempting to parse any more data at that point is considered an error. ("Garbage after JSON".) + + If you set this property to true the parser will never return SBJsonStreamParserComplete. Rather, + once an object is completed it will expect another object to immediately follow, separated + only by (optional) whitespace. + + */ +@property BOOL supportMultipleDocuments; + +/** + Delegate to receive messages + + The object set here receives a series of messages as the parser breaks down the JSON stream + into valid tokens. + + Usually this should be an instance of SBJsonStreamParserAdapter, but you can + substitute your own implementation of the SBJsonStreamParserDelegate protocol if you need to. + */ +@property (unsafe_unretained) id delegate; + +/** + The max parse depth + + If the input is nested deeper than this the parser will halt parsing and return an error. + + Defaults to 32. + */ +@property NSUInteger maxDepth; + +/// Holds the error after SBJsonStreamParserError was returned +@property (copy) NSString *error; + +/** + Parse some JSON + + The JSON is assumed to be UTF8 encoded. This can be a full JSON document, or a part of one. + + @param data An NSData object containing the next chunk of JSON + + @return + - SBJsonStreamParserComplete if a full document was found + - SBJsonStreamParserWaitingForData if a partial document was found and more data is required to complete it + - SBJsonStreamParserError if an error occured. (See the error property for details in this case.) + + */ +- (SBJsonStreamParserStatus)parse:(NSData*)data; + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParser.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParser.m new file mode 100755 index 0000000..ba2f748 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParser.m @@ -0,0 +1,338 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamParser.h" +#import "SBJsonStreamTokeniser.h" +#import "SBJsonStreamParserState.h" + +#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL)) + +@implementation SBJsonStreamParser { + SBJsonStreamTokeniser *tokeniser; +} + +@synthesize supportMultipleDocuments; +@synthesize error; +@synthesize delegate; +@synthesize maxDepth; +@synthesize state; +@synthesize stateStack; + +#pragma mark Housekeeping + +- (id)init { + self = [super init]; + if (self) { + maxDepth = 32u; + stateStack = [[NSMutableArray alloc] initWithCapacity:maxDepth]; + state = [SBJsonStreamParserStateStart sharedInstance]; + tokeniser = [[SBJsonStreamTokeniser alloc] init]; + } + return self; +} + + +#pragma mark Methods + +- (NSString*)tokenName:(sbjson_token_t)token { + switch (token) { + case sbjson_token_array_open: + return @"start of array"; + break; + + case sbjson_token_array_close: + return @"end of array"; + break; + + case sbjson_token_integer: + case sbjson_token_real: + return @"number"; + break; + + case sbjson_token_string: + case sbjson_token_encoded: + return @"string"; + break; + + case sbjson_token_bool: + return @"boolean"; + break; + + case sbjson_token_null: + return @"null"; + break; + + case sbjson_token_entry_sep: + return @"key-value separator"; + break; + + case sbjson_token_value_sep: + return @"value separator"; + break; + + case sbjson_token_object_open: + return @"start of object"; + break; + + case sbjson_token_object_close: + return @"end of object"; + break; + + case sbjson_token_eof: + case sbjson_token_error: + break; + } + NSAssert(NO, @"Should not get here"); + return @""; +} + +- (void)maxDepthError { + self.error = [NSString stringWithFormat:@"Input depth exceeds max depth of %lu", (unsigned long)maxDepth]; + self.state = [SBJsonStreamParserStateError sharedInstance]; +} + +- (void)handleObjectStart { + if (stateStack.count >= maxDepth) { + [self maxDepthError]; + return; + } + + [delegate parserFoundObjectStart:self]; + [stateStack addObject:state]; + self.state = [SBJsonStreamParserStateObjectStart sharedInstance]; +} + +- (void)handleObjectEnd: (sbjson_token_t) tok { + self.state = [stateStack lastObject]; + [stateStack removeLastObject]; + [state parser:self shouldTransitionTo:tok]; + [delegate parserFoundObjectEnd:self]; +} + +- (void)handleArrayStart { + if (stateStack.count >= maxDepth) { + [self maxDepthError]; + return; + } + + [delegate parserFoundArrayStart:self]; + [stateStack addObject:state]; + self.state = [SBJsonStreamParserStateArrayStart sharedInstance]; +} + +- (void)handleArrayEnd: (sbjson_token_t) tok { + self.state = [stateStack lastObject]; + [stateStack removeLastObject]; + [state parser:self shouldTransitionTo:tok]; + [delegate parserFoundArrayEnd:self]; +} + +- (void) handleTokenNotExpectedHere: (sbjson_token_t) tok { + NSString *tokenName = [self tokenName:tok]; + NSString *stateName = [state name]; + + self.error = [NSString stringWithFormat:@"Token '%@' not expected %@", tokenName, stateName]; + self.state = [SBJsonStreamParserStateError sharedInstance]; +} + +- (SBJsonStreamParserStatus)parse:(NSData *)data_ { + @autoreleasepool { + [tokeniser appendData:data_]; + + for (;;) { + + if ([state isError]) + return SBJsonStreamParserError; + + char *token; + NSUInteger token_len; + sbjson_token_t tok = [tokeniser getToken:&token length:&token_len]; + + switch (tok) { + case sbjson_token_eof: + return [state parserShouldReturn:self]; + break; + + case sbjson_token_error: + self.state = [SBJsonStreamParserStateError sharedInstance]; + self.error = tokeniser.error; + return SBJsonStreamParserError; + break; + + default: + + if (![state parser:self shouldAcceptToken:tok]) { + [self handleTokenNotExpectedHere: tok]; + return SBJsonStreamParserError; + } + + switch (tok) { + case sbjson_token_object_open: + [self handleObjectStart]; + break; + + case sbjson_token_object_close: + [self handleObjectEnd: tok]; + break; + + case sbjson_token_array_open: + [self handleArrayStart]; + break; + + case sbjson_token_array_close: + [self handleArrayEnd: tok]; + break; + + case sbjson_token_value_sep: + case sbjson_token_entry_sep: + [state parser:self shouldTransitionTo:tok]; + break; + + case sbjson_token_bool: + [delegate parser:self foundBoolean:token[0] == 't']; + [state parser:self shouldTransitionTo:tok]; + break; + + + case sbjson_token_null: + [delegate parserFoundNull:self]; + [state parser:self shouldTransitionTo:tok]; + break; + + case sbjson_token_integer: { + const int UNSIGNED_LONG_LONG_MAX_DIGITS = 20; + if (token_len <= UNSIGNED_LONG_LONG_MAX_DIGITS) { + if (*token == '-') + [delegate parser:self foundNumber: @(strtoll(token, NULL, 10))]; + else + [delegate parser:self foundNumber: @(strtoull(token, NULL, 10))]; + + [state parser:self shouldTransitionTo:tok]; + break; + } + } + // FALLTHROUGH + + case sbjson_token_real: { + [delegate parser:self foundNumber: @(strtod(token, NULL))]; + [state parser:self shouldTransitionTo:tok]; + break; + } + + case sbjson_token_string: { + NSString *string = [[NSString alloc] initWithBytes:token length:token_len encoding:NSUTF8StringEncoding]; + if ([state needKey]) + [delegate parser:self foundObjectKey:string]; + else + [delegate parser:self foundString:string]; + [state parser:self shouldTransitionTo:tok]; + break; + } + + case sbjson_token_encoded: { + NSString *string = [self decodeStringToken:token length:token_len]; + if ([state needKey]) + [delegate parser:self foundObjectKey:string]; + else + [delegate parser:self foundString:string]; + [state parser:self shouldTransitionTo:tok]; + break; + } + + default: + break; + } + break; + } + } + return SBJsonStreamParserComplete; + } +} + +- (unichar)decodeHexQuad:(char *)quad { + unichar ch = 0; + for (NSUInteger i = 0; i < 4; i++) { + int c = quad[i]; + ch *= 16; + switch (c) { + case '0' ... '9': ch += c - '0'; break; + case 'a' ... 'f': ch += 10 + c - 'a'; break; + case 'A' ... 'F': ch += 10 + c - 'A'; break; + default: @throw @"FUT FUT FUT"; + } + } + return ch; +} + +- (NSString*)decodeStringToken:(char*)bytes length:(NSUInteger)len { + NSMutableString *string = [NSMutableString stringWithCapacity:len]; + + for (NSUInteger i = 0; i < len;) { + switch (bytes[i]) { + case '\\': { + switch (bytes[++i]) { + case '"': [string appendString:@"\""]; i++; break; + case '/': [string appendString:@"/"]; i++; break; + case '\\': [string appendString:@"\\"]; i++; break; + case 'b': [string appendString:@"\b"]; i++; break; + case 'f': [string appendString:@"\f"]; i++; break; + case 'n': [string appendString:@"\n"]; i++; break; + case 'r': [string appendString:@"\r"]; i++; break; + case 't': [string appendString:@"\t"]; i++; break; + case 'u': { + unichar hi = [self decodeHexQuad:bytes + i + 1]; + i += 5; + if (SBStringIsSurrogateHighCharacter(hi)) { + // Skip past \u that we know is there.. + unichar lo = [self decodeHexQuad:bytes + i + 2]; + i += 6; + [string appendFormat:@"%C%C", hi, lo]; + } else { + [string appendFormat:@"%C", hi]; + } + break; + } + default: @throw @"FUT FUT FUT"; + } + break; + } + default: [string appendFormat:@"%c", bytes[i++]]; break; + } + } + return string; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAccumulator.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAccumulator.h new file mode 100755 index 0000000..141d6ee --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAccumulator.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2011 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "SBJsonStreamParserAdapter.h" + +@interface SBJsonStreamParserAccumulator : NSObject + +@property (copy) id value; + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAccumulator.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAccumulator.m new file mode 100755 index 0000000..82d8fe8 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAccumulator.m @@ -0,0 +1,51 @@ +/* + Copyright (C) 2011 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamParserAccumulator.h" + +@implementation SBJsonStreamParserAccumulator + +@synthesize value; + + +#pragma mark SBJsonStreamParserAdapterDelegate + +- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray *)array { + value = array; +} + +- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary *)dict { + value = dict; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAdapter.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAdapter.h new file mode 100755 index 0000000..8b4bc5d --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAdapter.h @@ -0,0 +1,142 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import "SBJsonStreamParser.h" + +typedef enum { + SBJsonStreamParserAdapterNone, + SBJsonStreamParserAdapterArray, + SBJsonStreamParserAdapterObject, +} SBJsonStreamParserAdapterType; + +/** + Delegate for getting objects & arrays from the stream parser adapter + + */ +@protocol SBJsonStreamParserAdapterDelegate + +/** + Called if a JSON array is found + + This method is called if a JSON array is found. + + */ +- (void)parser:(SBJsonStreamParser*)parser foundArray:(NSArray*)array; + +/** + Called when a JSON object is found + + This method is called if a JSON object is found. + */ +- (void)parser:(SBJsonStreamParser*)parser foundObject:(NSDictionary*)dict; + +@end + +/** + SBJsonStreamParserDelegate protocol adapter + + Rather than implementing the SBJsonStreamParserDelegate protocol yourself you will + most likely find it much more convenient to use an instance of this class and + implement the SBJsonStreamParserAdapterDelegate protocol instead. + + The default behaviour is that the delegate only receives one call from + either the -parser:foundArray: or -parser:foundObject: method when the + document is fully parsed. However, if your inputs contains multiple JSON + documents and you set the parser's -supportMultipleDocuments property to YES + you will get one call for each full method. + + SBJsonStreamParserAdapter *adapter = [[[SBJsonStreamParserAdapter alloc] init] autorelease]; + adapter.delegate = self; + + SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease]; + parser.delegate = adapter; + parser.supportMultipleDocuments = YES; + + // Note that this input contains multiple top-level JSON documents + NSData *json = [@"[]{}[]{}" dataWithEncoding:NSUTF8StringEncoding]; + [parser parse:data]; + + In the above example self will have the following sequence of methods called on it: + + - -parser:foundArray: + - -parser:foundObject: + - -parser:foundArray: + - -parser:foundObject: + + Often you won't have control over the input you're parsing, so can't make use of + this feature. But, all is not lost: this class will let you get the same effect by + allowing you to skip one or more of the outer enclosing objects. Thus, the next + example results in the same sequence of -parser:foundArray: / -parser:foundObject: + being called on your delegate. + + SBJsonStreamParserAdapter *adapter = [[[SBJsonStreamParserAdapter alloc] init] autorelease]; + adapter.delegate = self; + adapter.levelsToSkip = 1; + + SBJsonStreamParser *parser = [[[SBJsonStreamParser alloc] init] autorelease]; + parser.delegate = adapter; + + // Note that this input contains A SINGLE top-level document + NSData *json = [@"[[],{},[],{}]" dataWithEncoding:NSUTF8StringEncoding]; + [parser parse:data]; + +*/ +@interface SBJsonStreamParserAdapter : NSObject { +@private + NSUInteger depth; + NSMutableArray *array; + NSMutableDictionary *dict; + NSMutableArray *keyStack; + NSMutableArray *stack; + + SBJsonStreamParserAdapterType currentType; +} + +/** + How many levels to skip + + This is useful for parsing huge JSON documents, or documents coming in over a very slow link. + + If you set this to N it will skip the outer N levels and call the -parser:foundArray: + or -parser:foundObject: methods for each of the inner objects, as appropriate. + +*/ +@property NSUInteger levelsToSkip; + +/** + Your delegate object + Set this to the object you want to receive the SBJsonStreamParserAdapterDelegate messages. + */ +@property (unsafe_unretained) id delegate; + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAdapter.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAdapter.m new file mode 100755 index 0000000..7259a06 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserAdapter.m @@ -0,0 +1,168 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamParserAdapter.h" + +@interface SBJsonStreamParserAdapter () + +- (void)pop; +- (void)parser:(SBJsonStreamParser*)parser found:(id)obj; + +@end + + + +@implementation SBJsonStreamParserAdapter + +@synthesize delegate; +@synthesize levelsToSkip; + +#pragma mark Housekeeping + +- (id)init { + self = [super init]; + if (self) { + keyStack = [[NSMutableArray alloc] initWithCapacity:32]; + stack = [[NSMutableArray alloc] initWithCapacity:32]; + + currentType = SBJsonStreamParserAdapterNone; + } + return self; +} + + +#pragma mark Private methods + +- (void)pop { + [stack removeLastObject]; + array = nil; + dict = nil; + currentType = SBJsonStreamParserAdapterNone; + + id value = [stack lastObject]; + + if ([value isKindOfClass:[NSArray class]]) { + array = value; + currentType = SBJsonStreamParserAdapterArray; + } else if ([value isKindOfClass:[NSDictionary class]]) { + dict = value; + currentType = SBJsonStreamParserAdapterObject; + } +} + +- (void)parser:(SBJsonStreamParser*)parser found:(id)obj { + NSParameterAssert(obj); + + switch (currentType) { + case SBJsonStreamParserAdapterArray: + [array addObject:obj]; + break; + + case SBJsonStreamParserAdapterObject: + NSParameterAssert(keyStack.count); + [dict setObject:obj forKey:[keyStack lastObject]]; + [keyStack removeLastObject]; + break; + + case SBJsonStreamParserAdapterNone: + if ([obj isKindOfClass:[NSArray class]]) { + [delegate parser:parser foundArray:obj]; + } else { + [delegate parser:parser foundObject:obj]; + } + break; + + default: + break; + } +} + + +#pragma mark Delegate methods + +- (void)parserFoundObjectStart:(SBJsonStreamParser*)parser { + if (++depth > self.levelsToSkip) { + dict = [NSMutableDictionary new]; + [stack addObject:dict]; + currentType = SBJsonStreamParserAdapterObject; + } +} + +- (void)parser:(SBJsonStreamParser*)parser foundObjectKey:(NSString*)key_ { + [keyStack addObject:key_]; +} + +- (void)parserFoundObjectEnd:(SBJsonStreamParser*)parser { + if (depth-- > self.levelsToSkip) { + id value = dict; + [self pop]; + [self parser:parser found:value]; + } +} + +- (void)parserFoundArrayStart:(SBJsonStreamParser*)parser { + if (++depth > self.levelsToSkip) { + array = [NSMutableArray new]; + [stack addObject:array]; + currentType = SBJsonStreamParserAdapterArray; + } +} + +- (void)parserFoundArrayEnd:(SBJsonStreamParser*)parser { + if (depth-- > self.levelsToSkip) { + id value = array; + [self pop]; + [self parser:parser found:value]; + } +} + +- (void)parser:(SBJsonStreamParser*)parser foundBoolean:(BOOL)x { + [self parser:parser found:[NSNumber numberWithBool:x]]; +} + +- (void)parserFoundNull:(SBJsonStreamParser*)parser { + [self parser:parser found:[NSNull null]]; +} + +- (void)parser:(SBJsonStreamParser*)parser foundNumber:(NSNumber*)num { + [self parser:parser found:num]; +} + +- (void)parser:(SBJsonStreamParser*)parser foundString:(NSString*)string { + [self parser:parser found:string]; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserState.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserState.h new file mode 100755 index 0000000..35b3f24 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserState.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +#import "SBJsonStreamTokeniser.h" +#import "SBJsonStreamParser.h" + +@interface SBJsonStreamParserState : NSObject ++ (id)sharedInstance; + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token; +- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser; +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok; +- (BOOL)needKey; +- (BOOL)isError; + +- (NSString*)name; + +@end + +@interface SBJsonStreamParserStateStart : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateComplete : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateError : SBJsonStreamParserState +@end + + +@interface SBJsonStreamParserStateObjectStart : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateObjectGotKey : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateObjectSeparator : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateObjectGotValue : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateObjectNeedKey : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateArrayStart : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateArrayGotValue : SBJsonStreamParserState +@end + +@interface SBJsonStreamParserStateArrayNeedValue : SBJsonStreamParserState +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserState.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserState.m new file mode 100755 index 0000000..9481f97 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamParserState.m @@ -0,0 +1,364 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamParserState.h" + +#define SINGLETON \ ++ (id)sharedInstance { \ + static id state = nil; \ + if (!state) { \ + @synchronized(self) { \ + if (!state) state = [[self alloc] init]; \ + } \ + } \ + return state; \ +} + +@implementation SBJsonStreamParserState + ++ (id)sharedInstance { return nil; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + return NO; +} + +- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser { + return SBJsonStreamParserWaitingForData; +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok {} + +- (BOOL)needKey { + return NO; +} + +- (NSString*)name { + return @""; +} + +- (BOOL)isError { + return NO; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateStart + +SINGLETON + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + return token == sbjson_token_array_open || token == sbjson_token_object_open; +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + + SBJsonStreamParserState *state = nil; + switch (tok) { + case sbjson_token_array_open: + state = [SBJsonStreamParserStateArrayStart sharedInstance]; + break; + + case sbjson_token_object_open: + state = [SBJsonStreamParserStateObjectStart sharedInstance]; + break; + + case sbjson_token_array_close: + case sbjson_token_object_close: + if (parser.supportMultipleDocuments) + state = parser.state; + else + state = [SBJsonStreamParserStateComplete sharedInstance]; + break; + + case sbjson_token_eof: + return; + + default: + state = [SBJsonStreamParserStateError sharedInstance]; + break; + } + + + parser.state = state; +} + +- (NSString*)name { return @"before outer-most array or object"; } + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateComplete + +SINGLETON + +- (NSString*)name { return @"after outer-most array or object"; } + +- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser { + return SBJsonStreamParserComplete; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateError + +SINGLETON + +- (NSString*)name { return @"in error"; } + +- (SBJsonStreamParserStatus)parserShouldReturn:(SBJsonStreamParser*)parser { + return SBJsonStreamParserError; +} + +- (BOOL)isError { + return YES; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateObjectStart + +SINGLETON + +- (NSString*)name { return @"at beginning of object"; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + switch (token) { + case sbjson_token_object_close: + case sbjson_token_string: + case sbjson_token_encoded: + return YES; + break; + default: + return NO; + break; + } +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateObjectGotKey sharedInstance]; +} + +- (BOOL)needKey { + return YES; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateObjectGotKey + +SINGLETON + +- (NSString*)name { return @"after object key"; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + return token == sbjson_token_entry_sep; +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateObjectSeparator sharedInstance]; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateObjectSeparator + +SINGLETON + +- (NSString*)name { return @"as object value"; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + switch (token) { + case sbjson_token_object_open: + case sbjson_token_array_open: + case sbjson_token_bool: + case sbjson_token_null: + case sbjson_token_integer: + case sbjson_token_real: + case sbjson_token_string: + case sbjson_token_encoded: + return YES; + break; + + default: + return NO; + break; + } +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateObjectGotValue sharedInstance]; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateObjectGotValue + +SINGLETON + +- (NSString*)name { return @"after object value"; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + switch (token) { + case sbjson_token_object_close: + case sbjson_token_value_sep: + return YES; + break; + default: + return NO; + break; + } +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateObjectNeedKey sharedInstance]; +} + + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateObjectNeedKey + +SINGLETON + +- (NSString*)name { return @"in place of object key"; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + return sbjson_token_string == token || sbjson_token_encoded == token; +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateObjectGotKey sharedInstance]; +} + +- (BOOL)needKey { + return YES; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateArrayStart + +SINGLETON + +- (NSString*)name { return @"at array start"; } + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + switch (token) { + case sbjson_token_object_close: + case sbjson_token_entry_sep: + case sbjson_token_value_sep: + return NO; + break; + + default: + return YES; + break; + } +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateArrayGotValue sharedInstance]; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateArrayGotValue + +SINGLETON + +- (NSString*)name { return @"after array value"; } + + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + return token == sbjson_token_array_close || token == sbjson_token_value_sep; +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + if (tok == sbjson_token_value_sep) + parser.state = [SBJsonStreamParserStateArrayNeedValue sharedInstance]; +} + +@end + +#pragma mark - + +@implementation SBJsonStreamParserStateArrayNeedValue + +SINGLETON + +- (NSString*)name { return @"as array value"; } + + +- (BOOL)parser:(SBJsonStreamParser*)parser shouldAcceptToken:(sbjson_token_t)token { + switch (token) { + case sbjson_token_array_close: + case sbjson_token_entry_sep: + case sbjson_token_object_close: + case sbjson_token_value_sep: + return NO; + break; + + default: + return YES; + break; + } +} + +- (void)parser:(SBJsonStreamParser*)parser shouldTransitionTo:(sbjson_token_t)tok { + parser.state = [SBJsonStreamParserStateArrayGotValue sharedInstance]; +} + +@end + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamTokeniser.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamTokeniser.h new file mode 100755 index 0000000..b24b4ff --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamTokeniser.h @@ -0,0 +1,40 @@ +// +// Created by SuperPappi on 09/01/2013. +// +// To change the template use AppCode | Preferences | File Templates. +// + +#import + +typedef enum { + sbjson_token_error = -1, + sbjson_token_eof, + + sbjson_token_array_open, + sbjson_token_array_close, + sbjson_token_value_sep, + + sbjson_token_object_open, + sbjson_token_object_close, + sbjson_token_entry_sep, + + sbjson_token_bool, + sbjson_token_null, + + sbjson_token_integer, + sbjson_token_real, + + sbjson_token_string, + sbjson_token_encoded, +} sbjson_token_t; + + +@interface SBJsonStreamTokeniser : NSObject + +@property (nonatomic, readonly, copy) NSString *error; + +- (void)appendData:(NSData*)data_; +- (sbjson_token_t)getToken:(char**)tok length:(NSUInteger*)len; + +@end + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamTokeniser.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamTokeniser.m new file mode 100755 index 0000000..c0e9b29 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamTokeniser.m @@ -0,0 +1,397 @@ +// +// Created by SuperPappi on 09/01/2013. +// +// To change the template use AppCode | Preferences | File Templates. +// + + +#import "SBJsonStreamTokeniser.h" + +#define SBStringIsIllegalSurrogateHighCharacter(character) (((character) >= 0xD800UL) && ((character) <= 0xDFFFUL)) +#define SBStringIsSurrogateLowCharacter(character) ((character >= 0xDC00UL) && (character <= 0xDFFFUL)) +#define SBStringIsSurrogateHighCharacter(character) ((character >= 0xD800UL) && (character <= 0xDBFFUL)) + +@implementation SBJsonStreamTokeniser { + NSMutableData *data; + const char *bytes; + NSUInteger index; + NSUInteger offset; +} + +- (void)setError:(NSString *)error { + _error = [NSString stringWithFormat:@"%@ at index %lu", error, (unsigned long)(offset + index)]; +} + +- (void)appendData:(NSData *)data_ { + if (!data) { + data = [data_ mutableCopy]; + + } else if (index) { + // Discard data we've already parsed + [data replaceBytesInRange:NSMakeRange(0, index) withBytes:"" length:0]; + [data appendData:data_]; + + // Add to the offset for reporting + offset += index; + + // Reset index to point to current position + index = 0u; + + } + else { + [data appendData:data_]; + } + + bytes = [data bytes]; +} + +- (void)skipWhitespace { + while (index < data.length) { + switch (bytes[index]) { + case ' ': + case '\t': + case '\r': + case '\n': + index++; + break; + default: + return; + } + } +} + +- (BOOL)getUnichar:(unichar *)ch { + if ([self haveRemainingCharacters:1]) { + *ch = (unichar) bytes[index]; + return YES; + } + return NO; +} + +- (BOOL)haveOneMoreCharacter { + return [self haveRemainingCharacters:1]; +} + +- (BOOL)haveRemainingCharacters:(NSUInteger)length { + return data.length - index >= length; +} + +- (sbjson_token_t)match:(char *)str retval:(sbjson_token_t)tok token:(char **)token length:(NSUInteger *)length { + NSUInteger len = strlen(str); + if ([self haveRemainingCharacters:len]) { + if (!memcmp(bytes + index, str, len)) { + *token = str; + *length = len; + index += len; + return tok; + } + [self setError: [NSString stringWithFormat:@"Expected '%s' after initial '%.1s'", str, str]]; + return sbjson_token_error; + } + + return sbjson_token_eof; +} + +- (BOOL)decodeHexQuad:(unichar*)quad { + unichar tmp = 0; + + for (int i = 0; i < 4; i++, index++) { + unichar c = bytes[index]; + tmp *= 16; + switch (c) { + case '0' ... '9': + tmp += c - '0'; + break; + + case 'a' ... 'f': + tmp += 10 + c - 'a'; + break; + + case 'A' ... 'F': + tmp += 10 + c - 'A'; + break; + + default: + return NO; + } + } + *quad = tmp; + return YES; +} + +- (sbjson_token_t)getStringToken:(char **)token length:(NSUInteger *)length { + + // Skip initial " + index++; + + NSUInteger string_start = index; + sbjson_token_t tok = sbjson_token_string; + + for (;;) { + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + + switch (bytes[index]) { + case 0 ... 0x1F: + [self setError:[NSString stringWithFormat:@"Unescaped control character [0x%0.2X] in string", bytes[index]]]; + return sbjson_token_error; + + case '"': + *token = (char *)(bytes + string_start); + *length = index - string_start; + index++; + return tok; + + case '\\': + tok = sbjson_token_encoded; + index++; + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + + if (bytes[index] == 'u') { + index++; + if (![self haveRemainingCharacters:4]) + return sbjson_token_eof; + + unichar hi; + if (![self decodeHexQuad:&hi]) { + [self setError:@"Invalid hex quad"]; + return sbjson_token_error; + } + + if (SBStringIsSurrogateHighCharacter(hi)) { + if (![self haveRemainingCharacters:6]) + return sbjson_token_eof; + + unichar lo; + if (bytes[index++] != '\\' || bytes[index++] != 'u' || ![self decodeHexQuad:&lo]) { + [self setError:@"Missing low character in surrogate pair"]; + return sbjson_token_error; + } + + if (!SBStringIsSurrogateLowCharacter(lo)) { + [self setError:@"Invalid low character in surrogate pair"]; + return sbjson_token_error; + } + + } else if (SBStringIsIllegalSurrogateHighCharacter(hi)) { + [self setError:@"Invalid high character in surrogate pair"]; + return sbjson_token_error; + + } + + + } else { + switch (bytes[index]) { + case '\\': + case '/': + case '"': + case 'b': + case 'n': + case 'r': + case 't': + case 'f': + index++; + break; + + default: + [self setError:[NSString stringWithFormat:@"Illegal escape character [%x]", bytes[index]]]; + return sbjson_token_error; + } + } + + break; + + default: + index++; + break; + } + } + + @throw @"FUT FUT FUT"; +} + +- (sbjson_token_t)getNumberToken:(char **)token length:(NSUInteger *)length { + NSUInteger num_start = index; + if (bytes[index] == '-') { + index++; + + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + } + + sbjson_token_t tok = sbjson_token_integer; + if (bytes[index] == '0') { + index++; + + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + + if (isdigit(bytes[index])) { + [self setError:@"Leading zero is illegal in number"]; + return sbjson_token_error; + } + } + + while (isdigit(bytes[index])) { + index++; + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + } + + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + + + if (bytes[index] == '.') { + index++; + tok = sbjson_token_real; + + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + + NSUInteger frac_start = index; + while (isdigit(bytes[index])) { + index++; + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + } + + if (frac_start == index) { + [self setError:@"No digits after decimal point"]; + return sbjson_token_error; + } + } + + if (bytes[index] == 'e' || bytes[index] == 'E') { + index++; + tok = sbjson_token_real; + + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + + if (bytes[index] == '-' || bytes[index] == '+') { + index++; + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + } + + NSUInteger exp_start = index; + while (isdigit(bytes[index])) { + index++; + if (![self haveOneMoreCharacter]) + return sbjson_token_eof; + } + + if (exp_start == index) { + [self setError:@"No digits in exponent"]; + return sbjson_token_error; + } + + } + + if (num_start + 1 == index && bytes[num_start] == '-') { + [self setError:@"No digits after initial minus"]; + return sbjson_token_error; + } + + *token = (char *)(bytes + num_start); + *length = index - num_start; + return tok; +} + + +- (sbjson_token_t)getToken:(char **)token length:(NSUInteger *)length { + [self skipWhitespace]; + NSUInteger copyOfIndex = index; + + unichar ch; + if (![self getUnichar:&ch]) + return sbjson_token_eof; + + sbjson_token_t tok; + switch (ch) { + case '{': { + index++; + tok = sbjson_token_object_open; + break; + } + case '}': { + index++; + tok = sbjson_token_object_close; + break; + + } + case '[': { + index++; + tok = sbjson_token_array_open; + break; + + } + case ']': { + index++; + tok = sbjson_token_array_close; + break; + + } + case 't': { + tok = [self match:"true" retval:sbjson_token_bool token:token length:length]; + break; + + } + case 'f': { + tok = [self match:"false" retval:sbjson_token_bool token:token length:length]; + break; + + } + case 'n': { + tok = [self match:"null" retval:sbjson_token_null token:token length:length]; + break; + + } + case ',': { + index++; + tok = sbjson_token_value_sep; + break; + + } + case ':': { + index++; + tok = sbjson_token_entry_sep; + break; + + } + case '"': { + tok = [self getStringToken:token length:length]; + break; + + } + case '-': + case '0' ... '9': { + tok = [self getNumberToken:token length:length]; + break; + + } + case '+': { + self.error = @"Leading + is illegal in number"; + tok = sbjson_token_error; + break; + + } + default: { + self.error = [NSString stringWithFormat:@"Illegal start of token [%c]", ch]; + tok = sbjson_token_error; + break; + } + } + + if (tok == sbjson_token_eof) { + // We ran out of bytes before we could finish parsing the current token. + // Back up to the start & wait for more data. + index = copyOfIndex; + } + + return tok; +} + +@end \ No newline at end of file diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriter.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriter.h new file mode 100755 index 0000000..7794a14 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriter.h @@ -0,0 +1,210 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +/// Enable JSON writing for non-native objects +@interface NSObject (SBProxyForJson) + +/** + Allows generation of JSON for otherwise unsupported classes. + + If you have a custom class that you want to create a JSON representation + for you can implement this method in your class. It should return a + representation of your object defined in terms of objects that can be + translated into JSON. For example, a Person object might implement it like this: + + - (id)proxyForJson { + return [NSDictionary dictionaryWithObjectsAndKeys: + name, @"name", + phone, @"phone", + email, @"email", + nil]; + } + + */ +- (id)proxyForJson; + +@end + +@class SBJsonStreamWriter; + +@protocol SBJsonStreamWriterDelegate + +- (void)writer:(SBJsonStreamWriter*)writer appendBytes:(const void *)bytes length:(NSUInteger)length; + +@end + +@class SBJsonStreamWriterState; + +/** + The Stream Writer class. + + Accepts a stream of messages and writes JSON of these to its delegate object. + + This class provides a range of high-, mid- and low-level methods. You can mix + and match calls to these. For example, you may want to call -writeArrayOpen + to start an array and then repeatedly call -writeObject: with various objects + before finishing off with a -writeArrayClose call. + + Objective-C types are mapped to JSON types in the following way: + + - NSNull -> null + - NSString -> string + - NSArray -> array + - NSDictionary -> object + - NSNumber's -initWithBool:YES -> true + - NSNumber's -initWithBool:NO -> false + - NSNumber -> number + + NSNumber instances created with the -numberWithBool: method are + converted into the JSON boolean "true" and "false" values, and vice + versa. Any other NSNumber instances are converted to a JSON number the + way you would expect. + + @warning: In JSON the keys of an object must be strings. NSDictionary + keys need not be, but attempting to convert an NSDictionary with + non-string keys into JSON will throw an exception.* + + */ + +@interface SBJsonStreamWriter : NSObject { + NSMutableDictionary *cache; +} + +@property (nonatomic, unsafe_unretained) SBJsonStreamWriterState *state; // Internal +@property (nonatomic, readonly, strong) NSMutableArray *stateStack; // Internal + +/** + delegate to receive JSON output + Delegate that will receive messages with output. + */ +@property (unsafe_unretained) id delegate; + +/** + The maximum recursing depth. + + Defaults to 512. If the input is nested deeper than this the input will be deemed to be + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can + turn off this security feature by setting the maxDepth value to 0. + */ +@property NSUInteger maxDepth; + +/** + Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace between tokens. If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + An optional comparator to be used if sortKeys is YES. + + If this is nil, sorting will be done via @selector(compare:). + */ +@property (copy) NSComparator sortKeysComparator; + +/// Contains the error description after an error has occured. +@property (copy) NSString *error; + +/** + Write an NSDictionary to the JSON stream. + @return YES if successful, or NO on failure + */ +- (BOOL)writeObject:(NSDictionary*)dict; + +/** + Write an NSArray to the JSON stream. + @return YES if successful, or NO on failure + */ +- (BOOL)writeArray:(NSArray *)array; + +/** + Start writing an Object to the stream + @return YES if successful, or NO on failure +*/ +- (BOOL)writeObjectOpen; + +/** + Close the current object being written + @return YES if successful, or NO on failure +*/ +- (BOOL)writeObjectClose; + +/** Start writing an Array to the stream + @return YES if successful, or NO on failure +*/ +- (BOOL)writeArrayOpen; + +/** Close the current Array being written + @return YES if successful, or NO on failure +*/ +- (BOOL)writeArrayClose; + +/** Write a null to the stream + @return YES if successful, or NO on failure +*/ +- (BOOL)writeNull; + +/** Write a boolean to the stream + @return YES if successful, or NO on failure +*/ +- (BOOL)writeBool:(BOOL)x; + +/** Write a Number to the stream + @return YES if successful, or NO on failure +*/ +- (BOOL)writeNumber:(NSNumber*)n; + +/** Write a String to the stream + @return YES if successful, or NO on failure +*/ +- (BOOL)writeString:(NSString*)s; + +@end + +@interface SBJsonStreamWriter (Private) +- (BOOL)writeValue:(id)v; +- (void)appendBytes:(const void *)bytes length:(NSUInteger)length; +@end + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriter.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriter.m new file mode 100755 index 0000000..61cd58e --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriter.m @@ -0,0 +1,367 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamWriter.h" +#import "SBJsonStreamWriterState.h" + +static NSNumber *kTrue; +static NSNumber *kFalse; +static NSNumber *kPositiveInfinity; +static NSNumber *kNegativeInfinity; + + +@implementation SBJsonStreamWriter + +@synthesize error; +@synthesize maxDepth; +@synthesize state; +@synthesize stateStack; +@synthesize humanReadable; +@synthesize sortKeys; +@synthesize sortKeysComparator; + ++ (void)initialize { + kPositiveInfinity = [NSNumber numberWithDouble:+HUGE_VAL]; + kNegativeInfinity = [NSNumber numberWithDouble:-HUGE_VAL]; + kTrue = [NSNumber numberWithBool:YES]; + kFalse = [NSNumber numberWithBool:NO]; +} + +#pragma mark Housekeeping + +@synthesize delegate; + +- (id)init { + self = [super init]; + if (self) { + maxDepth = 32u; + stateStack = [[NSMutableArray alloc] initWithCapacity:maxDepth]; + state = [SBJsonStreamWriterStateStart sharedInstance]; + cache = [[NSMutableDictionary alloc] initWithCapacity:32]; + } + return self; +} + +#pragma mark Methods + +- (void)appendBytes:(const void *)bytes length:(NSUInteger)length { + [delegate writer:self appendBytes:bytes length:length]; +} + +- (BOOL)writeObject:(NSDictionary *)dict { + if (![self writeObjectOpen]) + return NO; + + NSArray *keys = [dict allKeys]; + + if (sortKeys) { + if (sortKeysComparator) { + keys = [keys sortedArrayWithOptions:NSSortStable usingComparator:sortKeysComparator]; + } + else{ + keys = [keys sortedArrayUsingSelector:@selector(compare:)]; + } + } + + for (id k in keys) { + if (![k isKindOfClass:[NSString class]]) { + self.error = [NSString stringWithFormat:@"JSON object key must be string: %@", k]; + return NO; + } + + if (![self writeString:k]) + return NO; + if (![self writeValue:[dict objectForKey:k]]) + return NO; + } + + return [self writeObjectClose]; +} + +- (BOOL)writeArray:(NSArray*)array { + if (![self writeArrayOpen]) + return NO; + for (id v in array) + if (![self writeValue:v]) + return NO; + return [self writeArrayClose]; +} + + +- (BOOL)writeObjectOpen { + if ([state isInvalidState:self]) return NO; + if ([state expectingKey:self]) return NO; + [state appendSeparator:self]; + if (humanReadable && stateStack.count) [state appendWhitespace:self]; + + [stateStack addObject:state]; + self.state = [SBJsonStreamWriterStateObjectStart sharedInstance]; + + if (maxDepth && stateStack.count > maxDepth) { + self.error = @"Nested too deep"; + return NO; + } + + [delegate writer:self appendBytes:"{" length:1]; + return YES; +} + +- (BOOL)writeObjectClose { + if ([state isInvalidState:self]) return NO; + + SBJsonStreamWriterState *prev = state; + + self.state = [stateStack lastObject]; + [stateStack removeLastObject]; + + if (humanReadable) [prev appendWhitespace:self]; + [delegate writer:self appendBytes:"}" length:1]; + + [state transitionState:self]; + return YES; +} + +- (BOOL)writeArrayOpen { + if ([state isInvalidState:self]) return NO; + if ([state expectingKey:self]) return NO; + [state appendSeparator:self]; + if (humanReadable && stateStack.count) [state appendWhitespace:self]; + + [stateStack addObject:state]; + self.state = [SBJsonStreamWriterStateArrayStart sharedInstance]; + + if (maxDepth && stateStack.count > maxDepth) { + self.error = @"Nested too deep"; + return NO; + } + + [delegate writer:self appendBytes:"[" length:1]; + return YES; +} + +- (BOOL)writeArrayClose { + if ([state isInvalidState:self]) return NO; + if ([state expectingKey:self]) return NO; + + SBJsonStreamWriterState *prev = state; + + self.state = [stateStack lastObject]; + [stateStack removeLastObject]; + + if (humanReadable) [prev appendWhitespace:self]; + [delegate writer:self appendBytes:"]" length:1]; + + [state transitionState:self]; + return YES; +} + +- (BOOL)writeNull { + if ([state isInvalidState:self]) return NO; + if ([state expectingKey:self]) return NO; + [state appendSeparator:self]; + if (humanReadable) [state appendWhitespace:self]; + + [delegate writer:self appendBytes:"null" length:4]; + [state transitionState:self]; + return YES; +} + +- (BOOL)writeBool:(BOOL)x { + if ([state isInvalidState:self]) return NO; + if ([state expectingKey:self]) return NO; + [state appendSeparator:self]; + if (humanReadable) [state appendWhitespace:self]; + + if (x) + [delegate writer:self appendBytes:"true" length:4]; + else + [delegate writer:self appendBytes:"false" length:5]; + [state transitionState:self]; + return YES; +} + + +- (BOOL)writeValue:(id)o { + if ([o isKindOfClass:[NSDictionary class]]) { + return [self writeObject:o]; + + } else if ([o isKindOfClass:[NSArray class]]) { + return [self writeArray:o]; + + } else if ([o isKindOfClass:[NSString class]]) { + [self writeString:o]; + return YES; + + } else if ([o isKindOfClass:[NSNumber class]]) { + return [self writeNumber:o]; + + } else if ([o isKindOfClass:[NSNull class]]) { + return [self writeNull]; + + } else if ([o respondsToSelector:@selector(proxyForJson)]) { + return [self writeValue:[o proxyForJson]]; + + } + + self.error = [NSString stringWithFormat:@"JSON serialisation not supported for %@", [o class]]; + return NO; +} + +static const char *strForChar(int c) { + switch (c) { + case 0: return "\\u0000"; break; + case 1: return "\\u0001"; break; + case 2: return "\\u0002"; break; + case 3: return "\\u0003"; break; + case 4: return "\\u0004"; break; + case 5: return "\\u0005"; break; + case 6: return "\\u0006"; break; + case 7: return "\\u0007"; break; + case 8: return "\\b"; break; + case 9: return "\\t"; break; + case 10: return "\\n"; break; + case 11: return "\\u000b"; break; + case 12: return "\\f"; break; + case 13: return "\\r"; break; + case 14: return "\\u000e"; break; + case 15: return "\\u000f"; break; + case 16: return "\\u0010"; break; + case 17: return "\\u0011"; break; + case 18: return "\\u0012"; break; + case 19: return "\\u0013"; break; + case 20: return "\\u0014"; break; + case 21: return "\\u0015"; break; + case 22: return "\\u0016"; break; + case 23: return "\\u0017"; break; + case 24: return "\\u0018"; break; + case 25: return "\\u0019"; break; + case 26: return "\\u001a"; break; + case 27: return "\\u001b"; break; + case 28: return "\\u001c"; break; + case 29: return "\\u001d"; break; + case 30: return "\\u001e"; break; + case 31: return "\\u001f"; break; + case 34: return "\\\""; break; + case 92: return "\\\\"; break; + } + NSLog(@"FUTFUTFUT: -->'%c'<---", c); + return "FUTFUTFUT"; +} + +- (BOOL)writeString:(NSString*)string { + if ([state isInvalidState:self]) return NO; + [state appendSeparator:self]; + if (humanReadable) [state appendWhitespace:self]; + + NSMutableData *buf = [cache objectForKey:string]; + if (!buf) { + + NSUInteger len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + const char *utf8 = [string UTF8String]; + NSUInteger written = 0, i = 0; + + buf = [NSMutableData dataWithCapacity:(NSUInteger)(len * 1.1f)]; + [buf appendBytes:"\"" length:1]; + + for (i = 0; i < len; i++) { + int c = utf8[i]; + BOOL isControlChar = c >= 0 && c < 32; + if (isControlChar || c == '"' || c == '\\') { + if (i - written) + [buf appendBytes:utf8 + written length:i - written]; + written = i + 1; + + const char *t = strForChar(c); + [buf appendBytes:t length:strlen(t)]; + } + } + + if (i - written) + [buf appendBytes:utf8 + written length:i - written]; + + [buf appendBytes:"\"" length:1]; + [cache setObject:buf forKey:string]; + } + + [delegate writer:self appendBytes:[buf bytes] length:[buf length]]; + [state transitionState:self]; + return YES; +} + +- (BOOL)writeNumber:(NSNumber*)number { + if (number == kTrue || number == kFalse) + return [self writeBool:[number boolValue]]; + + if ([state isInvalidState:self]) return NO; + if ([state expectingKey:self]) return NO; + [state appendSeparator:self]; + if (humanReadable) [state appendWhitespace:self]; + + if ([kPositiveInfinity isEqualToNumber:number]) { + self.error = @"+Infinity is not a valid number in JSON"; + return NO; + + } else if ([kNegativeInfinity isEqualToNumber:number]) { + self.error = @"-Infinity is not a valid number in JSON"; + return NO; + + } else if (isnan([number doubleValue])) { + self.error = @"NaN is not a valid number in JSON"; + return NO; + } + + const char *objcType = [number objCType]; + char num[128]; + size_t len; + + switch (objcType[0]) { + case 'c': case 'i': case 's': case 'l': case 'q': + len = snprintf(num, sizeof num, "%lld", [number longLongValue]); + break; + case 'C': case 'I': case 'S': case 'L': case 'Q': + len = snprintf(num, sizeof num, "%llu", [number unsignedLongLongValue]); + break; + case 'f': case 'd': default: { + len = snprintf(num, sizeof num, "%.17g", [number doubleValue]); + break; + } + } + [delegate writer:self appendBytes:num length: len]; + [state transitionState:self]; + return YES; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterAccumulator.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterAccumulator.h new file mode 100755 index 0000000..b12d0d5 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterAccumulator.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2011 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "SBJsonStreamWriter.h" + +@interface SBJsonStreamWriterAccumulator : NSObject + +@property (readonly, copy) NSMutableData* data; + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterAccumulator.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterAccumulator.m new file mode 100755 index 0000000..d78c317 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterAccumulator.m @@ -0,0 +1,56 @@ +/* + Copyright (C) 2011 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamWriterAccumulator.h" + + +@implementation SBJsonStreamWriterAccumulator + +@synthesize data; + +- (id)init { + self = [super init]; + if (self) { + data = [[NSMutableData alloc] initWithCapacity:8096u]; + } + return self; +} + + +#pragma mark SBJsonStreamWriterDelegate + +- (void)writer:(SBJsonStreamWriter *)writer appendBytes:(const void *)bytes length:(NSUInteger)length { + [data appendBytes:bytes length:length]; +} + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterState.h b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterState.h new file mode 100755 index 0000000..90d442a --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterState.h @@ -0,0 +1,69 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +@class SBJsonStreamWriter; + +@interface SBJsonStreamWriterState : NSObject ++ (id)sharedInstance; +- (BOOL)isInvalidState:(SBJsonStreamWriter*)writer; +- (void)appendSeparator:(SBJsonStreamWriter*)writer; +- (BOOL)expectingKey:(SBJsonStreamWriter*)writer; +- (void)transitionState:(SBJsonStreamWriter*)writer; +- (void)appendWhitespace:(SBJsonStreamWriter*)writer; +@end + +@interface SBJsonStreamWriterStateObjectStart : SBJsonStreamWriterState +@end + +@interface SBJsonStreamWriterStateObjectKey : SBJsonStreamWriterStateObjectStart +@end + +@interface SBJsonStreamWriterStateObjectValue : SBJsonStreamWriterState +@end + +@interface SBJsonStreamWriterStateArrayStart : SBJsonStreamWriterState +@end + +@interface SBJsonStreamWriterStateArrayValue : SBJsonStreamWriterState +@end + +@interface SBJsonStreamWriterStateStart : SBJsonStreamWriterState +@end + +@interface SBJsonStreamWriterStateComplete : SBJsonStreamWriterState +@end + +@interface SBJsonStreamWriterStateError : SBJsonStreamWriterState +@end + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterState.m b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterState.m new file mode 100755 index 0000000..a87b447 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonStreamWriterState.m @@ -0,0 +1,147 @@ +/* + Copyright (c) 2010, Stig Brautaset. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Neither the name of the the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonStreamWriterState.h" +#import "SBJsonStreamWriter.h" + +#define SINGLETON \ ++ (id)sharedInstance { \ + static id state = nil; \ + if (!state) { \ + @synchronized(self) { \ + if (!state) state = [[self alloc] init]; \ + } \ + } \ + return state; \ +} + + +@implementation SBJsonStreamWriterState ++ (id)sharedInstance { return nil; } +- (BOOL)isInvalidState:(SBJsonStreamWriter*)writer { return NO; } +- (void)appendSeparator:(SBJsonStreamWriter*)writer {} +- (BOOL)expectingKey:(SBJsonStreamWriter*)writer { return NO; } +- (void)transitionState:(SBJsonStreamWriter *)writer {} +- (void)appendWhitespace:(SBJsonStreamWriter*)writer { + [writer appendBytes:"\n" length:1]; + for (NSUInteger i = 0; i < writer.stateStack.count; i++) + [writer appendBytes:" " length:2]; +} +@end + +@implementation SBJsonStreamWriterStateObjectStart + +SINGLETON + +- (void)transitionState:(SBJsonStreamWriter *)writer { + writer.state = [SBJsonStreamWriterStateObjectValue sharedInstance]; +} +- (BOOL)expectingKey:(SBJsonStreamWriter *)writer { + writer.error = @"JSON object key must be string"; + return YES; +} +@end + +@implementation SBJsonStreamWriterStateObjectKey + +SINGLETON + +- (void)appendSeparator:(SBJsonStreamWriter *)writer { + [writer appendBytes:"," length:1]; +} +@end + +@implementation SBJsonStreamWriterStateObjectValue + +SINGLETON + +- (void)appendSeparator:(SBJsonStreamWriter *)writer { + [writer appendBytes:":" length:1]; +} +- (void)transitionState:(SBJsonStreamWriter *)writer { + writer.state = [SBJsonStreamWriterStateObjectKey sharedInstance]; +} +- (void)appendWhitespace:(SBJsonStreamWriter *)writer { + [writer appendBytes:" " length:1]; +} +@end + +@implementation SBJsonStreamWriterStateArrayStart + +SINGLETON + +- (void)transitionState:(SBJsonStreamWriter *)writer { + writer.state = [SBJsonStreamWriterStateArrayValue sharedInstance]; +} +@end + +@implementation SBJsonStreamWriterStateArrayValue + +SINGLETON + +- (void)appendSeparator:(SBJsonStreamWriter *)writer { + [writer appendBytes:"," length:1]; +} +@end + +@implementation SBJsonStreamWriterStateStart + +SINGLETON + + +- (void)transitionState:(SBJsonStreamWriter *)writer { + writer.state = [SBJsonStreamWriterStateComplete sharedInstance]; +} +- (void)appendSeparator:(SBJsonStreamWriter *)writer { +} +@end + +@implementation SBJsonStreamWriterStateComplete + +SINGLETON + +- (BOOL)isInvalidState:(SBJsonStreamWriter*)writer { + writer.error = @"Stream is closed"; + return YES; +} +@end + +@implementation SBJsonStreamWriterStateError + +SINGLETON + +@end + diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonWriter.h b/Origami Plugin/SocketIO/SBJson/SBJsonWriter.h new file mode 100755 index 0000000..aaf6b0b --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonWriter.h @@ -0,0 +1,103 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +/** + The JSON writer class. + + This uses SBJsonStreamWriter internally. + + */ + +@interface SBJsonWriter : NSObject + +/** + The maximum recursing depth. + + Defaults to 32. If the input is nested deeper than this the input will be deemed to be + malicious and the parser returns nil, signalling an error. ("Nested too deep".) You can + turn off this security feature by setting the maxDepth value to 0. + */ +@property NSUInteger maxDepth; + +/** + Return an error trace, or nil if there was no errors. + + Note that this method returns the trace of the last method that failed. + You need to check the return value of the call you're making to figure out + if the call actually failed, before you know call this method. + */ +@property (readonly, copy) NSString *error; + +/** + Whether we are generating human-readable (multiline) JSON. + + Set whether or not to generate human-readable JSON. The default is NO, which produces + JSON without any whitespace. (Except inside strings.) If set to YES, generates human-readable + JSON with linebreaks after each array value and dictionary key/value pair, indented two + spaces per nesting level. + */ +@property BOOL humanReadable; + +/** + Whether or not to sort the dictionary keys in the output. + + If this is set to YES, the dictionary keys in the JSON output will be in sorted order. + (This is useful if you need to compare two structures, for example.) The default is NO. + */ +@property BOOL sortKeys; + +/** + An optional comparator to be used if sortKeys is YES. + + If this is nil, sorting will be done via @selector(compare:). + */ +@property (copy) NSComparator sortKeysComparator; + +/** + Generates string with JSON representation for the given object. + + Returns a string containing JSON representation of the passed in value, or nil on error. + If nil is returned and error is not NULL, *error can be interrogated to find the cause of the error. + + @param value any instance that can be represented as JSON text. + */ +- (NSString*)stringWithObject:(id)value; + +/** + Generates JSON representation for the given object. + + Returns an NSData object containing JSON represented as UTF8 text, or nil on error. + + @param value any instance that can be represented as JSON text. + */ +- (NSData*)dataWithObject:(id)value; + +@end diff --git a/Origami Plugin/SocketIO/SBJson/SBJsonWriter.m b/Origami Plugin/SocketIO/SBJson/SBJsonWriter.m new file mode 100755 index 0000000..34e4db4 --- /dev/null +++ b/Origami Plugin/SocketIO/SBJson/SBJsonWriter.m @@ -0,0 +1,103 @@ +/* + Copyright (C) 2009 Stig Brautaset. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !__has_feature(objc_arc) +#error "This source file must be compiled with ARC enabled!" +#endif + +#import "SBJsonWriter.h" +#import "SBJsonStreamWriter.h" +#import "SBJsonStreamWriterAccumulator.h" + + +@interface SBJsonWriter () +@property (copy) NSString *error; +@end + +@implementation SBJsonWriter + +@synthesize sortKeys; +@synthesize humanReadable; + +@synthesize error; +@synthesize maxDepth; + +@synthesize sortKeysComparator; + +- (id)init { + self = [super init]; + if (self) { + self.maxDepth = 32u; + } + return self; +} + + +- (NSString*)stringWithObject:(id)value { + NSData *data = [self dataWithObject:value]; + if (data) + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return nil; +} + +- (NSData*)dataWithObject:(id)object { + self.error = nil; + + SBJsonStreamWriterAccumulator *accumulator = [[SBJsonStreamWriterAccumulator alloc] init]; + + SBJsonStreamWriter *streamWriter = [[SBJsonStreamWriter alloc] init]; + streamWriter.sortKeys = self.sortKeys; + streamWriter.maxDepth = self.maxDepth; + streamWriter.sortKeysComparator = self.sortKeysComparator; + streamWriter.humanReadable = self.humanReadable; + streamWriter.delegate = accumulator; + + BOOL ok = NO; + if ([object isKindOfClass:[NSDictionary class]]) + ok = [streamWriter writeObject:object]; + + else if ([object isKindOfClass:[NSArray class]]) + ok = [streamWriter writeArray:object]; + + else if ([object respondsToSelector:@selector(proxyForJson)]) + return [self dataWithObject:[object proxyForJson]]; + else { + self.error = @"Not valid type for JSON"; + return nil; + } + + if (ok) + return accumulator.data; + + self.error = streamWriter.error; + return nil; +} + + +@end diff --git a/Origami Plugin/SocketIO/SocketIO.h b/Origami Plugin/SocketIO/SocketIO.h new file mode 100755 index 0000000..5becec2 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIO.h @@ -0,0 +1,125 @@ +// +// SocketIO.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import + +#import "SocketIOTransport.h" + +@class SocketIO; +@class SocketIOPacket; + +typedef void(^SocketIOCallback)(id argsData); + +extern NSString* const SocketIOError; + +typedef enum { + SocketIOServerRespondedWithInvalidConnectionData = -1, + SocketIOServerRespondedWithDisconnect = -2, + SocketIOHeartbeatTimeout = -3, + SocketIOWebSocketClosed = -4, + SocketIOTransportsNotSupported = -5, + SocketIOHandshakeFailed = -6, + SocketIODataCouldNotBeSend = -7 +} SocketIOErrorCodes; + + +@protocol SocketIODelegate +@optional +- (void) socketIODidConnect:(SocketIO *)socket; +- (void) socketIODidDisconnect:(SocketIO *)socket disconnectedWithError:(NSError *)error; +- (void) socketIO:(SocketIO *)socket didReceiveMessage:(SocketIOPacket *)packet; +- (void) socketIO:(SocketIO *)socket didReceiveJSON:(SocketIOPacket *)packet; +- (void) socketIO:(SocketIO *)socket didReceiveEvent:(SocketIOPacket *)packet; +- (void) socketIO:(SocketIO *)socket didSendMessage:(SocketIOPacket *)packet; +- (void) socketIO:(SocketIO *)socket onError:(NSError *)error; + +// TODO: deprecated -> to be removed +- (void) socketIO:(SocketIO *)socket failedToConnectWithError:(NSError *)error __attribute__((deprecated)); +- (void) socketIOHandshakeFailed:(SocketIO *)socket __attribute__((deprecated)); +@end + + +@interface SocketIO : NSObject +{ + NSString *_host; + NSInteger _port; + NSString *_sid; + NSString *_endpoint; + NSDictionary *_params; + + __unsafe_unretained id _delegate; + + NSObject *_transport; + + BOOL _isConnected; + BOOL _isConnecting; + BOOL _useSecure; + + NSURLConnection *_handshake; + + // heartbeat + NSTimeInterval _heartbeatTimeout; + dispatch_source_t _timeout; + + NSMutableArray *_queue; + + // acknowledge + NSMutableDictionary *_acks; + NSInteger _ackCount; + + // http request + NSMutableData *_httpRequestData; + + // get all arguments from ack? (https://github.com/pkyeck/socket.IO-objc/pull/85) + BOOL _returnAllDataFromAck; +} + +@property (nonatomic, readonly) NSString *host; +@property (nonatomic, readonly) NSInteger port; +@property (nonatomic, readonly) NSString *sid; +@property (nonatomic, readonly) NSTimeInterval heartbeatTimeout; +@property (nonatomic) BOOL useSecure; +@property (nonatomic, readonly) BOOL isConnected, isConnecting; +@property (nonatomic, unsafe_unretained) id delegate; +@property (nonatomic) BOOL returnAllDataFromAck; + +- (id) initWithDelegate:(id)delegate; +- (void) connectToHost:(NSString *)host onPort:(NSInteger)port; +- (void) connectToHost:(NSString *)host onPort:(NSInteger)port withParams:(NSDictionary *)params; +- (void) connectToHost:(NSString *)host onPort:(NSInteger)port withParams:(NSDictionary *)params withNamespace:(NSString *)endpoint; +- (void) connectToHost:(NSString *)host onPort:(NSInteger)port withParams:(NSDictionary *)params withNamespace:(NSString *)endpoint withConnectionTimeout: (NSTimeInterval) connectionTimeout; + +- (void) disconnect; +- (void) disconnectForced; + +- (void) sendMessage:(NSString *)data; +- (void) sendMessage:(NSString *)data withAcknowledge:(SocketIOCallback)function; +- (void) sendJSON:(NSDictionary *)data; +- (void) sendJSON:(NSDictionary *)data withAcknowledge:(SocketIOCallback)function; +- (void) sendEvent:(NSString *)eventName withData:(id)data; +- (void) sendEvent:(NSString *)eventName withData:(id)data andAcknowledge:(SocketIOCallback)function; +- (void) sendAcknowledgement:(NSString*)pId withArgs:(NSArray *)data; + +- (void) setResourceName:(NSString *)name; + +@end \ No newline at end of file diff --git a/Origami Plugin/SocketIO/SocketIO.m b/Origami Plugin/SocketIO/SocketIO.m new file mode 100755 index 0000000..be56a0a --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIO.m @@ -0,0 +1,839 @@ +// +// SocketIO.m +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import "SocketIO.h" +#import "SocketIOPacket.h" +#import "SocketIOJSONSerialization.h" + +#import "SocketIOTransportWebsocket.h" +#import "SocketIOTransportXHR.h" + +#define DEBUG_LOGS 0 +#define DEBUG_CERTIFICATE 1 + +#if DEBUG_LOGS +#define DEBUGLOG(...) NSLog(__VA_ARGS__) +#else +#define DEBUGLOG(...) +#endif + +static NSString* kResourceName = @"socket.io"; +static NSString* kHandshakeURL = @"%@://%@%@/%@/1/?t=%d%@"; +static NSString* kForceDisconnectURL = @"%@://%@%@/%@/1/xhr-polling/%@?disconnect"; + +float const defaultConnectionTimeout = 10.0f; + +NSString* const SocketIOError = @"SocketIOError"; +NSString* const SocketIOException = @"SocketIOException"; + +# pragma mark - +# pragma mark SocketIO's private interface + +@interface SocketIO (Private) + +- (NSArray*) arrayOfCaptureComponentsMatchedByRegex:(NSString*)regex; + +- (void) setTimeout; +- (void) onTimeout; + +- (void) onConnect:(SocketIOPacket *)packet; +- (void) onDisconnect:(NSError *)error; + +- (void) sendDisconnect; +- (void) sendHearbeat; +- (void) send:(SocketIOPacket *)packet; + +- (NSString *) addAcknowledge:(SocketIOCallback)function; +- (void) removeAcknowledgeForKey:(NSString *)key; +- (NSMutableArray*) getMatchesFrom:(NSString*)data with:(NSString*)regex; + +@end + +# pragma mark - +# pragma mark SocketIO implementation + +@implementation SocketIO + +@synthesize isConnected = _isConnected, + isConnecting = _isConnecting, + useSecure = _useSecure, + delegate = _delegate, + heartbeatTimeout = _heartbeatTimeout, + returnAllDataFromAck = _returnAllDataFromAck; + +- (id) initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) { + _delegate = delegate; + _queue = [[NSMutableArray alloc] init]; + _ackCount = 0; + _acks = [[NSMutableDictionary alloc] init]; + _returnAllDataFromAck = NO; + } + return self; +} + +- (void) connectToHost:(NSString *)host onPort:(NSInteger)port +{ + [self connectToHost:host onPort:port withParams:nil withNamespace:@"" withConnectionTimeout:defaultConnectionTimeout]; +} + +- (void) connectToHost:(NSString *)host onPort:(NSInteger)port withParams:(NSDictionary *)params +{ + [self connectToHost:host onPort:port withParams:params withNamespace:@"" withConnectionTimeout:defaultConnectionTimeout]; +} + +- (void) connectToHost:(NSString *)host + onPort:(NSInteger)port + withParams:(NSDictionary *)params + withNamespace:(NSString *)endpoint +{ + [self connectToHost:host onPort:port withParams:params withNamespace:@"" withConnectionTimeout:defaultConnectionTimeout]; +} + +- (void) connectToHost:(NSString *)host + onPort:(NSInteger)port + withParams:(NSDictionary *)params + withNamespace:(NSString *)endpoint + withConnectionTimeout:(NSTimeInterval)connectionTimeout +{ + if (!_isConnected && !_isConnecting) { + _isConnecting = YES; + + _host = host; + _port = port; + _params = params; + _endpoint = [endpoint copy]; + + // create a query parameters string + NSMutableString *query = [[NSMutableString alloc] initWithString:@""]; + [params enumerateKeysAndObjectsUsingBlock: ^(id key, id value, BOOL *stop) { + [query appendFormat:@"&%@=%@", key, value]; + }]; + + // do handshake via HTTP request + NSString *protocol = _useSecure ? @"https" : @"http"; + NSString *port = _port ? [NSString stringWithFormat:@":%ld", (long)_port] : @""; + NSString *handshakeUrl = [NSString stringWithFormat:kHandshakeURL, protocol, _host, port, kResourceName, rand(), query]; + + DEBUGLOG(@"Connecting to socket with URL: %@", handshakeUrl); + query = nil; + + // make a request + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:handshakeUrl] + cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData + timeoutInterval:connectionTimeout]; + + _handshake = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; + [_handshake scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + [_handshake start]; + + if (_handshake) { + _httpRequestData = [NSMutableData data]; + } + else { + // connection failed + [self connection:_handshake didFailWithError:nil]; + } + } +} + +- (void) disconnect +{ + if (_isConnected) { + [self sendDisconnect]; + } + else if (_isConnecting) { + [_handshake cancel]; + [self onDisconnect: nil]; + } +} + +- (void) disconnectForced +{ + NSString *protocol = [self useSecure] ? @"https" : @"http"; + NSString *port = _port ? [NSString stringWithFormat:@":%ld", (long)_port] : @""; + NSString *urlString = [NSString stringWithFormat:kForceDisconnectURL, protocol, _host, port, kResourceName, _sid]; + NSURL *url = [NSURL URLWithString:urlString]; + DEBUGLOG(@"Force disconnect at: %@", urlString); + + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + NSError *error = nil; + NSHTTPURLResponse *response = nil; + + [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; + + if (error || [response statusCode] != 200) { + DEBUGLOG(@"Error during disconnect: %@", error); + } + + [self onDisconnect:error]; +} + +- (void) sendMessage:(NSString *)data +{ + [self sendMessage:data withAcknowledge:nil]; +} + +- (void) sendMessage:(NSString *)data withAcknowledge:(SocketIOCallback)function +{ + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"message"]; + packet.data = data; + packet.pId = [self addAcknowledge:function]; + [self send:packet]; +} + +- (void) sendJSON:(NSDictionary *)data +{ + [self sendJSON:data withAcknowledge:nil]; +} + +- (void) sendJSON:(NSDictionary *)data withAcknowledge:(SocketIOCallback)function +{ + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"json"]; + packet.data = [SocketIOJSONSerialization JSONStringFromObject:data error:nil]; + packet.pId = [self addAcknowledge:function]; + [self send:packet]; +} + +- (void) sendEvent:(NSString *)eventName withData:(id)data +{ + [self sendEvent:eventName withData:data andAcknowledge:nil]; +} + +- (void) sendEvent:(NSString *)eventName withData:(id)data andAcknowledge:(SocketIOCallback)function +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:eventName forKey:@"name"]; + + // do not require arguments + if (data != nil) { + [dict setObject:[NSArray arrayWithObject:data] forKey:@"args"]; + } + + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"event"]; + packet.data = [SocketIOJSONSerialization JSONStringFromObject:dict error:nil]; + packet.pId = [self addAcknowledge:function]; + if (function) { + packet.ack = @"data"; + } + [self send:packet]; +} + +- (void) sendAcknowledgement:(NSString *)pId withArgs:(NSArray *)data +{ + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"ack"]; + packet.data = [SocketIOJSONSerialization JSONStringFromObject:data error:nil]; + packet.pId = pId; + packet.ack = @"data"; + + [self send:packet]; +} + +- (void) setResourceName:(NSString *)name +{ + kResourceName = [name copy]; +} + +# pragma mark - +# pragma mark private methods + +- (void) sendDisconnect +{ + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"disconnect"]; + [self send:packet]; + [self onDisconnect:nil]; +} + +- (void) sendConnect +{ + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"connect"]; + [self send:packet]; +} + +- (void) sendHeartbeat +{ + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:@"heartbeat"]; + [self send:packet]; +} + +- (void) send:(SocketIOPacket *)packet +{ + if (![self isConnected] && ![self isConnecting]) { + DEBUGLOG(@"Already disconnected!"); + return; + } + DEBUGLOG(@"send()"); + NSNumber *type = [packet typeAsNumber]; + NSMutableArray *encoded = [NSMutableArray arrayWithObject:type]; + + NSString *pId = packet.pId != nil ? packet.pId : @""; + if ([packet.ack isEqualToString:@"data"]) { + pId = [pId stringByAppendingString:@"+"]; + } + + // Do not write pid for acknowledgements + if ([type intValue] != 6) { + [encoded addObject:pId]; + } + + // Add the end point for the namespace to be used, as long as it is not + // an ACK, heartbeat, or disconnect packet + if ([type intValue] != 6 && [type intValue] != 2 && [type intValue] != 0) { + [encoded addObject:_endpoint]; + } + else { + [encoded addObject:@""]; + } + + if (packet.data != nil) { + NSString *ackpId = @""; + // This is an acknowledgement packet, so, prepend the ack pid to the data + if ([type intValue] == 6) { + ackpId = [NSString stringWithFormat:@":%@%@", packet.pId, @"+"]; + } + [encoded addObject:[NSString stringWithFormat:@"%@%@", ackpId, packet.data]]; + } + + NSString *req = [encoded componentsJoinedByString:@":"]; + if (![_transport isReady]) { + DEBUGLOG(@"queue >>> %@", req); + [_queue addObject:packet]; + } + else { + DEBUGLOG(@"send() >>> %@", req); + [_transport send:req]; + + if ([_delegate respondsToSelector:@selector(socketIO:didSendMessage:)]) { + [_delegate socketIO:self didSendMessage:packet]; + } + } +} + +- (void) doQueue +{ + DEBUGLOG(@"doQueue() >> %lu", (unsigned long)[_queue count]); + + // TODO send all packets at once ... not as seperate packets + while ([_queue count] > 0) { + SocketIOPacket *packet = [_queue objectAtIndex:0]; + [self send:packet]; + [_queue removeObject:packet]; + } +} + +- (void) onConnect:(SocketIOPacket *)packet +{ + DEBUGLOG(@"onConnect()"); + + _isConnected = YES; + + // Send the connected packet so the server knows what it's dealing with. + // Only required when endpoint/namespace is present + if ([_endpoint length] > 0) { + // Make sure the packet we received has an endpoint, otherwise send it again + if (![packet.endpoint isEqualToString:_endpoint]) { + DEBUGLOG(@"onConnect() >> End points do not match, resending connect packet"); + [self sendConnect]; + return; + } + } + + _isConnecting = NO; + + if ([_delegate respondsToSelector:@selector(socketIODidConnect:)]) { + [_delegate socketIODidConnect:self]; + } + + // send any queued packets + [self doQueue]; + + [self setTimeout]; +} + +# pragma mark - +# pragma mark Acknowledge methods + +- (NSString *) addAcknowledge:(SocketIOCallback)function +{ + if (function) { + ++_ackCount; + NSString *ac = [NSString stringWithFormat:@"%ld", (long)_ackCount]; + [_acks setObject:[function copy] forKey:ac]; + return ac; + } + return nil; +} + +- (void) removeAcknowledgeForKey:(NSString *)key +{ + [_acks removeObjectForKey:key]; +} + +# pragma mark - +# pragma mark Heartbeat methods + +- (void) onTimeout +{ + if (_timeout) { + dispatch_source_cancel(_timeout); + _timeout = NULL; + } + + DEBUGLOG(@"Timed out waiting for heartbeat."); + [self onDisconnect:[NSError errorWithDomain:SocketIOError + code:SocketIOHeartbeatTimeout + userInfo:nil]]; +} + +- (void) setTimeout +{ + DEBUGLOG(@"start/reset timeout"); + if (_timeout) { + dispatch_source_cancel(_timeout); + _timeout = NULL; + } + + _timeout = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, + 0, + 0, + dispatch_get_main_queue()); + + dispatch_source_set_timer(_timeout, + dispatch_time(DISPATCH_TIME_NOW, _heartbeatTimeout * NSEC_PER_SEC), + 0, + 0); + + __weak SocketIO *weakSelf = self; + + dispatch_source_set_event_handler(_timeout, ^{ + [weakSelf onTimeout]; + }); + + dispatch_resume(_timeout); + +} + + +# pragma mark - +# pragma mark Regex helper method +- (NSMutableArray*) getMatchesFrom:(NSString*)data with:(NSString*)regex +{ + NSRegularExpression *nsregexTest = [NSRegularExpression regularExpressionWithPattern:regex options:0 error:nil]; + NSArray *nsmatchesTest = [nsregexTest matchesInString:data options:0 range:NSMakeRange(0, [data length])]; + NSMutableArray *arr = [NSMutableArray array]; + + for (NSTextCheckingResult *nsmatchTest in nsmatchesTest) { + NSMutableArray *localMatch = [NSMutableArray array]; + for (NSUInteger i = 0, l = [nsmatchTest numberOfRanges]; i < l; i++) { + NSRange range = [nsmatchTest rangeAtIndex:i]; + NSString *nsmatchStr = nil; + if (range.location != NSNotFound && NSMaxRange(range) <= [data length]) { + nsmatchStr = [data substringWithRange:[nsmatchTest rangeAtIndex:i]]; + } + else { + nsmatchStr = @""; + } + [localMatch addObject:nsmatchStr]; + } + [arr addObject:localMatch]; + } + + return arr; +} + + +#pragma mark - +#pragma mark SocketIOTransport callbacks + +- (void) onData:(NSString *)data +{ + DEBUGLOG(@"onData %@", data); + + // data arrived -> reset timeout + [self setTimeout]; + + // check if data is valid (from socket.io.js) + NSString *regex = @"^([^:]+):([0-9]+)?(\\+)?:([^:]+)?:?(.*)?$"; + NSString *regexPieces = @"^([0-9]+)(\\+)?(.*)"; + + // create regex result + NSMutableArray *test = [self getMatchesFrom:data with:regex]; + + // valid data-string arrived + if ([test count] > 0) { + NSArray *result = [test objectAtIndex:0]; + + int idx = [[result objectAtIndex:1] intValue]; + SocketIOPacket *packet = [[SocketIOPacket alloc] initWithTypeIndex:idx]; + + packet.pId = [result objectAtIndex:2]; + + packet.ack = [result objectAtIndex:3]; + packet.endpoint = [result objectAtIndex:4]; + packet.data = [result objectAtIndex:5]; + + // + switch (idx) { + case 0: { + DEBUGLOG(@"disconnect"); + [self onDisconnect:[NSError errorWithDomain:SocketIOError + code:SocketIOServerRespondedWithDisconnect + userInfo:nil]]; + break; + } + case 1: { + DEBUGLOG(@"connected"); + // from socket.io.js ... not sure when data will contain sth?! + // packet.qs = data || ''; + [self onConnect:packet]; + break; + } + case 2: { + DEBUGLOG(@"heartbeat"); + [self sendHeartbeat]; + break; + } + case 3: { + DEBUGLOG(@"message"); + if (packet.data && ![packet.data isEqualToString:@""]) { + if ([_delegate respondsToSelector:@selector(socketIO:didReceiveMessage:)]) { + [_delegate socketIO:self didReceiveMessage:packet]; + } + } + break; + } + case 4: { + DEBUGLOG(@"json"); + if (packet.data && ![packet.data isEqualToString:@""]) { + if ([_delegate respondsToSelector:@selector(socketIO:didReceiveJSON:)]) { + [_delegate socketIO:self didReceiveJSON:packet]; + } + } + break; + } + case 5: { + DEBUGLOG(@"event"); + if (packet.data && ![packet.data isEqualToString:@""]) { + NSDictionary *json = [packet dataAsJSON]; + packet.name = [json objectForKey:@"name"]; + packet.args = [json objectForKey:@"args"]; + if ([_delegate respondsToSelector:@selector(socketIO:didReceiveEvent:)]) { + [_delegate socketIO:self didReceiveEvent:packet]; + } + } + break; + } + case 6: { + DEBUGLOG(@"ack"); + + // create regex result + NSMutableArray *pieces = [self getMatchesFrom:packet.data with:regexPieces]; + + if ([pieces count] > 0) { + NSArray *piece = [pieces objectAtIndex:0]; + int ackId = [[piece objectAtIndex:1] intValue]; + DEBUGLOG(@"ack id found: %d", ackId); + + NSString *argsStr = [piece objectAtIndex:3]; + id argsData = nil; + if (argsStr && ![argsStr isEqualToString:@""]) { + argsData = [SocketIOJSONSerialization objectFromJSONData:[argsStr dataUsingEncoding:NSUTF8StringEncoding] error:nil]; + // either send complete response or only the first arg to callback + if (!_returnAllDataFromAck && [argsData count] > 0) { + argsData = [argsData objectAtIndex:0]; + } + } + + // get selector for ackId + NSString *key = [NSString stringWithFormat:@"%d", ackId]; + SocketIOCallback callbackFunction = [_acks objectForKey:key]; + if (callbackFunction != nil) { + callbackFunction(argsData); + [self removeAcknowledgeForKey:key]; + } + } + + break; + } + case 7: { + DEBUGLOG(@"error"); + break; + } + case 8: { + DEBUGLOG(@"noop"); + break; + } + default: { + DEBUGLOG(@"command not found or not yet supported"); + break; + } + } + + packet = nil; + } + else { + DEBUGLOG(@"ERROR: data that has arrived wasn't valid"); + } +} + +- (void) onDisconnect:(NSError *)error +{ + DEBUGLOG(@"onDisconnect()"); + BOOL wasConnected = _isConnected; + BOOL wasConnecting = _isConnecting; + + _isConnected = NO; + _isConnecting = NO; + _sid = nil; + + [_queue removeAllObjects]; + + // Kill the heartbeat timer + if (_timeout) { + dispatch_source_cancel(_timeout); + _timeout = NULL; + } + + // Disconnect the websocket, just in case + if (_transport != nil) { + // clear websocket's delegate - otherwise crashes + _transport.delegate = nil; + [_transport close]; + } + + if ((wasConnected || wasConnecting)) { + if ([_delegate respondsToSelector:@selector(socketIODidDisconnect:disconnectedWithError:)]) { + [_delegate socketIODidDisconnect:self disconnectedWithError:error]; + } + } +} + +- (void) onError:(NSError *)error +{ + if ([_delegate respondsToSelector:@selector(socketIO:onError:)]) { + [_delegate socketIO:self onError:error]; + } +} + + +# pragma mark - +# pragma mark Handshake callbacks (NSURLConnectionDataDelegate) +- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + // check for server status code (http://gigliwood.com/weblog/Cocoa/Q__When_is_an_conne.html) + if ([response respondsToSelector:@selector(statusCode)]) { + NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode]; + DEBUGLOG(@"didReceiveResponse() %i", statusCode); + + if (statusCode >= 400) { + // stop connecting; no more delegate messages + [connection cancel]; + + NSString *error = [NSString stringWithFormat:NSLocalizedString(@"Server returned status code %d", @""), statusCode]; + NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:error forKey:NSLocalizedDescriptionKey]; + NSError *statusError = [NSError errorWithDomain:SocketIOError + code:statusCode + userInfo:errorInfo]; + // call error callback manually + [self connection:connection didFailWithError:statusError]; + } + } + + [_httpRequestData setLength:0]; +} + +- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + [_httpRequestData appendData:data]; +} + +- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + NSLog(@"ERROR: handshake failed ... %@", [error localizedDescription]); + + _isConnected = NO; + _isConnecting = NO; + + if ([_delegate respondsToSelector:@selector(socketIO:onError:)]) { + NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:error forKey:NSLocalizedDescriptionKey]; + + NSError *err = [NSError errorWithDomain:SocketIOError + code:SocketIOHandshakeFailed + userInfo:errorInfo]; + + [_delegate socketIO:self onError:err]; + } + // TODO: deprecated - to be removed + else if ([_delegate respondsToSelector:@selector(socketIOHandshakeFailed:)]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_delegate socketIOHandshakeFailed:self]; +#pragma clang diagnostic pop + } +} + +- (void) connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSString *responseString = [[NSString alloc] initWithData:_httpRequestData encoding:NSASCIIStringEncoding]; + + DEBUGLOG(@"connectionDidFinishLoading() %@", responseString); + NSArray *data = [responseString componentsSeparatedByString:@":"]; + // should be SID : heartbeat timeout : connection timeout : supported transports + + // check each returned value (thanks for the input https://github.com/taiyangc) + BOOL connectionFailed = false; + NSError* error; + + _sid = [data objectAtIndex:0]; + if ([_sid length] < 1 || [data count] < 4) { + // did not receive valid data, possibly missing a useSecure? + connectionFailed = true; + } + else { + // check SID + DEBUGLOG(@"sid: %@", _sid); + NSString *regex = @"[^0-9]"; + NSPredicate *regexTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex]; + if ([_sid rangeOfString:@"error"].location != NSNotFound || [regexTest evaluateWithObject:_sid]) { + [self connectToHost:_host onPort:_port withParams:_params withNamespace:_endpoint]; + return; + } + + // check heartbeat timeout + _heartbeatTimeout = [[data objectAtIndex:1] floatValue]; + if (_heartbeatTimeout == 0.0) { + // couldn't find float value -> fail + connectionFailed = true; + } + else { + // add small buffer of 7sec (magic xD) otherwise heartbeat will be too late and connection is closed + _heartbeatTimeout += 7.0; + } + DEBUGLOG(@"heartbeatTimeout: %f", _heartbeatTimeout); + + // index 2 => connection timeout + + // get transports + NSString *t = [data objectAtIndex:3]; + NSArray *transports = [t componentsSeparatedByString:@","]; + DEBUGLOG(@"transports: %@", transports); + + if ([transports indexOfObject:@"websocket"] != NSNotFound) { + DEBUGLOG(@"websocket supported -> using it now"); + _transport = [[SocketIOTransportWebsocket alloc] initWithDelegate:self]; + } + else if ([transports indexOfObject:@"xhr-polling"] != NSNotFound) { + DEBUGLOG(@"xhr polling supported -> using it now"); + _transport = [[SocketIOTransportXHR alloc] initWithDelegate:self]; + } + else { + DEBUGLOG(@"no transport found that is supported :( -> fail"); + connectionFailed = true; + error = [NSError errorWithDomain:SocketIOError + code:SocketIOTransportsNotSupported + userInfo:nil]; + } + } + + // if connection didn't return the values we need -> fail + if (connectionFailed) { + // error already set!? + if (error == nil) { + error = [NSError errorWithDomain:SocketIOError + code:SocketIOServerRespondedWithInvalidConnectionData + userInfo:nil]; + } + + if ([_delegate respondsToSelector:@selector(socketIO:onError:)]) { + [_delegate socketIO:self onError:error]; + } + // TODO: deprecated - to be removed + else if ([_delegate respondsToSelector:@selector(socketIO:failedToConnectWithError:)]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [_delegate socketIO:self failedToConnectWithError:error]; +#pragma clang diagnostic pop + } + + // make sure to do call all cleanup code + [self onDisconnect:error]; + + return; + } + + [_transport open]; +} + +#if DEBUG_CERTIFICATE + +// to deal with self-signed certificates +- (BOOL) connection:(NSURLConnection *)connection +canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace +{ + return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; +} + +- (void) connection:(NSURLConnection *)connection +didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge +{ + if ([challenge.protectionSpace.authenticationMethod + isEqualToString:NSURLAuthenticationMethodServerTrust]) { + // we only trust our own domain + if ([challenge.protectionSpace.host isEqualToString:_host]) { + SecTrustRef trust = challenge.protectionSpace.serverTrust; + NSURLCredential *credential = [NSURLCredential credentialForTrust:trust]; + [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; + } + } + + [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; +} +#endif + + +# pragma mark - + +- (void) dealloc +{ + [_handshake cancel]; + _handshake = nil; + + _host = nil; + _sid = nil; + _endpoint = nil; + + _transport.delegate = nil; + _transport = nil; + + if (_timeout) { + dispatch_source_cancel(_timeout); + _timeout = NULL; + } + + _queue = nil; + _acks = nil; +} + + +@end diff --git a/Origami Plugin/SocketIO/SocketIOJSONSerialization.h b/Origami Plugin/SocketIO/SocketIOJSONSerialization.h new file mode 100755 index 0000000..8360a06 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOJSONSerialization.h @@ -0,0 +1,31 @@ +// +// SocketIOJSONSerialization.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import + +@interface SocketIOJSONSerialization : NSObject + ++ (id) objectFromJSONData:(NSData *)data error:(NSError **)error; ++ (NSString *) JSONStringFromObject:(id)object error:(NSError **)error; + +@end diff --git a/Origami Plugin/SocketIO/SocketIOJSONSerialization.m b/Origami Plugin/SocketIO/SocketIOJSONSerialization.m new file mode 100755 index 0000000..5fbb677 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOJSONSerialization.m @@ -0,0 +1,115 @@ +// +// SocketIOJSONSerialization.m +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import "SocketIOJSONSerialization.h" + +extern NSString * const SocketIOException; + +// covers the methods in SBJson and JSONKit +@interface NSObject (SocketIOJSONSerialization) + +// used by both JSONKit and SBJson +- (id) objectWithData:(NSData *)data; + +// Use by JSONKit serialization +- (NSString *) JSONString; +- (id) decoder; + +// Used by SBJsonWriter +- (NSString *) stringWithObject:(id)object; + +@end + +@implementation SocketIOJSONSerialization + ++ (id) objectFromJSONData:(NSData *)data error:(NSError **)error { + Class serializer; + + // try SBJson first + serializer = NSClassFromString(@"SBJsonParser"); + if (serializer) { + id parser; + id object; + + parser = [[serializer alloc] init]; + object = [parser objectWithData:data]; + + return object; + } + + // try Foundation's JSON coder, available in OS X 10.7/iOS 5.0 + serializer = NSClassFromString(@"NSJSONSerialization"); + if (serializer) { + return [serializer JSONObjectWithData:data options:0 error:error]; + } + + // lastly, try JSONKit + serializer = NSClassFromString(@"JSONDecoder"); + if (serializer) { + return [[serializer decoder] objectWithData:data]; + } + + // unable to find a suitable JSON deseralizer + [NSException raise:SocketIOException format:@"socket.IO-objc requires SBJson, JSONKit or an OS that has NSJSONSerialization."]; + + return nil; +} + ++ (NSString *) JSONStringFromObject:(id)object error:(NSError **)error { + Class serializer; + NSString *jsonString; + + jsonString = nil; + serializer = NSClassFromString(@"SBJsonWriter"); + if (serializer) { + id writer; + + writer = [[serializer alloc] init]; + jsonString = [writer stringWithObject:object]; + + return jsonString; + } + + serializer = NSClassFromString(@"NSJSONSerialization"); + if (serializer) { + NSData *data; + + data = [serializer dataWithJSONObject:object options:0 error:error]; + + jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + return jsonString; + } + + // lastly, try JSONKit + if ([object respondsToSelector:@selector(JSONString)]) { + return [object JSONString]; + } + + // unable to find a suitable JSON seralizer + [NSException raise:SocketIOException format:@"socket.IO-objc requires SBJson, JSONKit or an OS that has NSJSONSerialization."]; + + return nil; +} + +@end diff --git a/Origami Plugin/SocketIO/SocketIOPacket.h b/Origami Plugin/SocketIO/SocketIOPacket.h new file mode 100755 index 0000000..fad6089 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOPacket.h @@ -0,0 +1,52 @@ +// +// SocketIOPacket.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import + +@interface SocketIOPacket : NSObject +{ + NSString *type; + NSString *pId; + NSString *ack; + NSString *name; + NSString *data; + NSArray *args; + NSString *endpoint; + NSArray *_types; +} + +@property (nonatomic, copy) NSString *type; +@property (nonatomic, copy) NSString *pId; +@property (nonatomic, copy) NSString *ack; +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *data; +@property (nonatomic, copy) NSString *endpoint; +@property (nonatomic, copy) NSArray *args; + +- (id) initWithType:(NSString *)packetType; +- (id) initWithTypeIndex:(int)index; +- (id) dataAsJSON; +- (NSNumber *) typeAsNumber; +- (NSString *) typeForIndex:(int)index; + +@end diff --git a/Origami Plugin/SocketIO/SocketIOPacket.m b/Origami Plugin/SocketIO/SocketIOPacket.m new file mode 100755 index 0000000..eeb9f31 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOPacket.m @@ -0,0 +1,104 @@ +// +// SocketIOPacket.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import "SocketIOPacket.h" +#import "SocketIOJSONSerialization.h" + +@implementation SocketIOPacket + +@synthesize type, pId, name, ack, data, args, endpoint; + +- (id) init +{ + self = [super init]; + if (self) { + _types = [NSArray arrayWithObjects: @"disconnect", + @"connect", + @"heartbeat", + @"message", + @"json", + @"event", + @"ack", + @"error", + @"noop", + nil]; + } + return self; +} + +- (id) initWithType:(NSString *)packetType +{ + self = [self init]; + if (self) { + self.type = packetType; + } + return self; +} + +- (id) initWithTypeIndex:(int)index +{ + self = [self init]; + if (self) { + self.type = [self typeForIndex:index]; + } + return self; +} + +- (id) dataAsJSON +{ + if (self.data) { + NSData *utf8Data = [self.data dataUsingEncoding:NSUTF8StringEncoding]; + return [SocketIOJSONSerialization objectFromJSONData:utf8Data error:nil]; + } + else { + return nil; + } +} + +- (NSNumber *) typeAsNumber +{ + NSUInteger index = [_types indexOfObject:self.type]; + NSNumber *num = [NSNumber numberWithUnsignedInteger:index]; + return num; +} + +- (NSString *) typeForIndex:(int)index +{ + return [_types objectAtIndex:index]; +} + +- (void) dealloc +{ + _types = nil; + + type = nil; + pId = nil; + name = nil; + ack = nil; + data = nil; + args = nil; + endpoint = nil; +} + +@end + diff --git a/Origami Plugin/SocketIO/SocketIOTransport.h b/Origami Plugin/SocketIO/SocketIOTransport.h new file mode 100755 index 0000000..c06eec5 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOTransport.h @@ -0,0 +1,50 @@ +// +// SocketIOTransport.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import + +@protocol SocketIOTransportDelegate + +- (void) onData:(id)message; +- (void) onDisconnect:(NSError*)error; +- (void) onError:(NSError*)error; + +@property (nonatomic, readonly) NSString *host; +@property (nonatomic, readonly) NSInteger port; +@property (nonatomic, readonly) NSString *sid; +@property (nonatomic, readonly) NSTimeInterval heartbeatTimeout; +@property (nonatomic) BOOL useSecure; + +@end + +@protocol SocketIOTransport + +- (id) initWithDelegate:(id )delegate; +- (void) open; +- (void) close; +- (BOOL) isReady; +- (void) send:(NSString *)request; + +@property (nonatomic, unsafe_unretained) id delegate; + +@end diff --git a/Origami Plugin/SocketIO/SocketIOTransportWebsocket.h b/Origami Plugin/SocketIO/SocketIOTransportWebsocket.h new file mode 100755 index 0000000..d3d23b3 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOTransportWebsocket.h @@ -0,0 +1,36 @@ +// +// SocketIOTransportWebsocket.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import + +#import "SRWebSocket.h" +#import "SocketIOTransport.h" + +@interface SocketIOTransportWebsocket : NSObject +{ + SRWebSocket *_webSocket; +} + +@property (nonatomic, unsafe_unretained) id delegate; + +@end diff --git a/Origami Plugin/SocketIO/SocketIOTransportWebsocket.m b/Origami Plugin/SocketIO/SocketIOTransportWebsocket.m new file mode 100755 index 0000000..7d7db10 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOTransportWebsocket.m @@ -0,0 +1,128 @@ +// +// SocketIOTransportWebsocket.m +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import "SocketIOTransportWebsocket.h" +#import "SocketIO.h" + +#define DEBUG_LOGS 0 + +#if DEBUG_LOGS +#define DEBUGLOG(...) NSLog(__VA_ARGS__) +#else +#define DEBUGLOG(...) +#endif + +static NSString* kInsecureSocketURL = @"ws://%@/socket.io/1/websocket/%@"; +static NSString* kSecureSocketURL = @"wss://%@/socket.io/1/websocket/%@"; +static NSString* kInsecureSocketPortURL = @"ws://%@:%d/socket.io/1/websocket/%@"; +static NSString* kSecureSocketPortURL = @"wss://%@:%d/socket.io/1/websocket/%@"; + +@implementation SocketIOTransportWebsocket + +@synthesize delegate; + +- (id) initWithDelegate:(id)delegate_ +{ + self = [super init]; + if (self) { + self.delegate = delegate_; + } + return self; +} + +- (BOOL) isReady +{ + return _webSocket.readyState == SR_OPEN; +} + +- (void) open +{ + NSString *urlStr; + NSString *format; + if (delegate.port) { + format = delegate.useSecure ? kSecureSocketPortURL : kInsecureSocketPortURL; + urlStr = [NSString stringWithFormat:format, delegate.host, delegate.port, delegate.sid]; + } + else { + format = delegate.useSecure ? kSecureSocketURL : kInsecureSocketURL; + urlStr = [NSString stringWithFormat:format, delegate.host, delegate.sid]; + } + NSURL *url = [NSURL URLWithString:urlStr]; + + _webSocket = nil; + + _webSocket = [[SRWebSocket alloc] initWithURL:url]; + _webSocket.delegate = self; + DEBUGLOG(@"Opening %@", url); + [_webSocket open]; +} + +- (void) dealloc +{ + [_webSocket setDelegate:nil]; +} + +- (void) close +{ + [_webSocket close]; +} + +- (void) send:(NSString*)request +{ + [_webSocket send:request]; +} + + + +# pragma mark - +# pragma mark WebSocket Delegate Methods + +- (void) webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message +{ + [delegate onData:message]; +} + +- (void) webSocketDidOpen:(SRWebSocket *)webSocket +{ + DEBUGLOG(@"Socket opened."); +} + +- (void) webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error +{ + DEBUGLOG(@"Socket failed with error ... %@", [error localizedDescription]); + // Assuming this resulted in a disconnect + [delegate onDisconnect:error]; +} + +- (void) webSocket:(SRWebSocket *)webSocket + didCloseWithCode:(NSInteger)code + reason:(NSString *)reason + wasClean:(BOOL)wasClean +{ + DEBUGLOG(@"Socket closed. %@", reason); + [delegate onDisconnect:[NSError errorWithDomain:SocketIOError + code:SocketIOWebSocketClosed + userInfo:nil]]; +} + +@end diff --git a/Origami Plugin/SocketIO/SocketIOTransportXHR.h b/Origami Plugin/SocketIO/SocketIOTransportXHR.h new file mode 100755 index 0000000..8e650c5 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOTransportXHR.h @@ -0,0 +1,37 @@ +// +// SocketIOTransportXHR.h +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import + +#import "SocketIOTransport.h" + +@interface SocketIOTransportXHR : NSObject +{ + NSString *_url; + NSMutableData *_data; + NSMutableDictionary *_polls; +} + +@property (nonatomic, unsafe_unretained) id delegate; + +@end diff --git a/Origami Plugin/SocketIO/SocketIOTransportXHR.m b/Origami Plugin/SocketIO/SocketIOTransportXHR.m new file mode 100755 index 0000000..6ac871e --- /dev/null +++ b/Origami Plugin/SocketIO/SocketIOTransportXHR.m @@ -0,0 +1,255 @@ +// +// SocketIOTransportXHR.m +// v0.4 ARC +// +// based on +// socketio-cocoa https://github.com/fpotter/socketio-cocoa +// by Fred Potter +// +// using +// https://github.com/square/SocketRocket +// https://github.com/stig/json-framework/ +// +// reusing some parts of +// /socket.io/socket.io.js +// +// Created by Philipp Kyeck http://beta-interactive.de +// +// Updated by +// samlown https://github.com/samlown +// kayleg https://github.com/kayleg +// taiyangc https://github.com/taiyangc +// + +#import "SocketIOTransportXHR.h" +#import "SocketIO.h" + +#define DEBUG_LOGS 0 + +#if DEBUG_LOGS +#define DEBUGLOG(...) NSLog(__VA_ARGS__) +#else +#define DEBUGLOG(...) +#endif + +static NSString* kInsecureXHRURL = @"http://%@/socket.io/1/xhr-polling/%@"; +static NSString* kSecureXHRURL = @"https://%@/socket.io/1/xhr-polling/%@"; +static NSString* kInsecureXHRPortURL = @"http://%@:%d/socket.io/1/xhr-polling/%@"; +static NSString* kSecureXHRPortURL = @"https://%@:%d/socket.io/1/xhr-polling/%@"; + +@interface SocketIOTransportXHR (Private) +- (void) checkAndStartPoll; +- (void) poll:(NSString *)data; +- (void) poll:(NSString *)data retryNumber:(int)retry; +@end + +@implementation SocketIOTransportXHR + +@synthesize delegate; + +- (id) initWithDelegate:(id)delegate_ +{ + self = [super init]; + if (self) { + self.delegate = delegate_; + _data = [[NSMutableData alloc] init]; + _polls = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void) open +{ + NSString *format; + if (delegate.port) { + format = delegate.useSecure ? kSecureXHRPortURL : kInsecureXHRPortURL; + _url = [NSString stringWithFormat:format, delegate.host, delegate.port, delegate.sid]; + } + else { + format = delegate.useSecure ? kSecureXHRURL : kInsecureXHRURL; + _url = [NSString stringWithFormat:format, delegate.host, delegate.sid]; + } + DEBUGLOG(@"Opening XHR @ %@", _url); + [self poll:nil]; +} + +- (void) close +{ + NSMutableDictionary *pollData; + NSURLConnection *conn; + for (NSString *key in _polls) { + pollData = [_polls objectForKey:key]; + conn = [pollData objectForKey:@"connection"]; + [conn cancel]; + } + [_polls removeAllObjects]; +} + +- (BOOL) isReady +{ + return YES; +} + +- (void) send:(NSString *)request +{ + [self poll:request]; +} + + +#pragma mark - +#pragma mark private methods + +- (void) checkAndStartPoll +{ + BOOL restart = NO; + // no polls currently running -> start one + if ([_polls count] == 0) { + restart = YES; + } + else { + restart = YES; + // look for polls w/o data -> if there, no need to restart + for (NSString *key in _polls) { + NSMutableDictionary *pollData = [_polls objectForKey:key]; + if ([pollData objectForKey:@"data"] == nil) { + restart = NO; + break; + } + } + } + + if (restart) { + [self poll:nil]; + } +} + +- (void) poll:(NSString *)data +{ + [self poll:data retryNumber:0]; +} + +- (void) poll:(NSString *)data retryNumber:(int)retry +{ + NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970]; + double unix = timeStamp * 1000; + NSString *url = [_url stringByAppendingString:[NSString stringWithFormat:@"?t=%.0f", unix]]; + + DEBUGLOG(@"---------------------------------------------------------------------------------------"); + DEBUGLOG(@"poll() %@", url); + + NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] + cachePolicy:NSURLRequestUseProtocolCachePolicy + timeoutInterval:[delegate heartbeatTimeout]]; + if (data != nil) { + DEBUGLOG(@"poll() %@", data); + [req setHTTPMethod:@"POST"]; + [req setValue:@"text/plain; charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; + [req setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]]; + } + [req setValue:@"Keep-Alive" forHTTPHeaderField:@"Connection"]; + + NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self]; + + // add pollData to polls dictionary + NSMutableDictionary *pollData = [[NSMutableDictionary alloc] init]; + [pollData setObject:[NSNumber numberWithInt:retry] forKey:@"retries"]; + [pollData setObject:conn forKey:@"connection"]; + [pollData setValue:data forKey:@"data"]; + [_polls setObject:pollData forKey:conn.description]; + + [conn start]; +} + + +#pragma mark - +#pragma mark NSURLConnection delegate methods + + +- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response +{ + [_data setLength:0]; +} + +- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data +{ + DEBUGLOG(@"didReceiveData(): %@", data); + [_data appendData:data]; +} + +- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error +{ + DEBUGLOG(@"didFailWithError: %@", [error localizedDescription]); + + // retry 3 times or throw error + NSMutableDictionary *pollData = [_polls objectForKey:connection.description]; + NSString *data = [pollData objectForKey:@"data"]; + [_polls removeObjectForKey:connection.description]; + + NSNumber *retries = [pollData objectForKey:@"retries"]; + if ([retries intValue] < 2) { + [self poll:data retryNumber:[retries intValue] + 1]; + } + else { + NSMutableDictionary *errorInfo = [[NSMutableDictionary alloc] init]; + [errorInfo setValue:[error localizedDescription] forKey:@"reason"]; + [errorInfo setValue:data forKey:@"data"]; + + if ([delegate respondsToSelector:@selector(onError:)]) { + [delegate onError:[NSError errorWithDomain:SocketIOError + code:SocketIODataCouldNotBeSend + userInfo:errorInfo]]; + } + } +} + +// Sometimes Socket.IO "batches" up messages in one packet, +// so we have to split them. +// +- (NSArray *)packetsFromPayload:(NSString *)payload +{ + // "Batched" format is: + // �[packet_0 length]�[packet_0]�[packet_1 length]�[packet_1]�[packet_n length]�[packet_n] + + if([payload hasPrefix:@"\ufffd"]) { + // Payload has multiple packets, split based on the '�' character + // Skip the first character, then split + NSArray *split = [[payload substringFromIndex:1] componentsSeparatedByString:@"\ufffd"]; + + // Init array with [split count] / 2 because we only need the odd-numbered + NSMutableArray *packets = [NSMutableArray arrayWithCapacity:[split count]/2]; + + // Now all of the odd-numbered indices are the packets (1, 3, 5, etc.) + [split enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if(idx % 2 != 0) { + [packets addObject:obj]; + } + }]; + + NSLog(@"Parsed a payload!"); + return packets; + } else { + // Regular single-packet payload + return @[payload]; + } +} + +- (void) connectionDidFinishLoading:(NSURLConnection *)connection +{ + NSString *message = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding]; + DEBUGLOG(@"response: __%@__", message); + + if (![message isEqualToString:@"1"]) { + NSArray *messages = [self packetsFromPayload:message]; + [messages enumerateObjectsUsingBlock:^(NSString *message, NSUInteger idx, BOOL *stop) { + [delegate onData:message]; + }]; + } + + // remove current connection from pool + [_polls removeObjectForKey:connection.description]; + + [self checkAndStartPoll]; +} + + +@end diff --git a/Origami Plugin/SocketIO/SocketRocket/NSData+SRB64Additions.h b/Origami Plugin/SocketIO/SocketRocket/NSData+SRB64Additions.h new file mode 100755 index 0000000..7d61063 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/NSData+SRB64Additions.h @@ -0,0 +1,24 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + + +@interface NSData (SRB64Additions) + +- (NSString *)SR_stringByBase64Encoding; + +@end diff --git a/Origami Plugin/SocketIO/SocketRocket/NSData+SRB64Additions.m b/Origami Plugin/SocketIO/SocketRocket/NSData+SRB64Additions.m new file mode 100755 index 0000000..5874a18 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/NSData+SRB64Additions.m @@ -0,0 +1,39 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "NSData+SRB64Additions.h" +#import "base64.h" + + +@implementation NSData (SRB64Additions) + +- (NSString *)SR_stringByBase64Encoding; +{ + size_t buffer_size = (([self length] * 3 + 2) / 2); + + char *buffer = (char *)malloc(buffer_size); + + int len = b64_ntop([self bytes], [self length], buffer, buffer_size); + + if (len == -1) { + free(buffer); + return nil; + } else{ + return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSUTF8StringEncoding freeWhenDone:YES]; + } +} + +@end diff --git a/Origami Plugin/SocketIO/SocketRocket/SRWebSocket.h b/Origami Plugin/SocketIO/SocketRocket/SRWebSocket.h new file mode 100755 index 0000000..2d40bb1 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/SRWebSocket.h @@ -0,0 +1,114 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +typedef enum { + SR_CONNECTING = 0, + SR_OPEN = 1, + SR_CLOSING = 2, + SR_CLOSED = 3, +} SRReadyState; + +@class SRWebSocket; + +extern NSString *const SRWebSocketErrorDomain; + +#pragma mark - SRWebSocketDelegate + +@protocol SRWebSocketDelegate; + +#pragma mark - SRWebSocket + +@interface SRWebSocket : NSObject + +@property (nonatomic, assign) id delegate; + +@property (nonatomic, readonly) SRReadyState readyState; +@property (nonatomic, readonly, retain) NSURL *url; + +// This returns the negotiated protocol. +// It will be nil until after the handshake completes. +@property (nonatomic, readonly, copy) NSString *protocol; + +// Protocols should be an array of strings that turn into Sec-WebSocket-Protocol. +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +- (id)initWithURLRequest:(NSURLRequest *)request; + +// Some helper constructors. +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +- (id)initWithURL:(NSURL *)url; + +// Delegate queue will be dispatch_main_queue by default. +// You cannot set both OperationQueue and dispatch_queue. +- (void)setDelegateOperationQueue:(NSOperationQueue*) queue; +- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue; + +// By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes. +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; + +// SRWebSockets are intended for one-time-use only. Open should be called once and only once. +- (void)open; + +- (void)close; +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; + +// Send a UTF8 String or Data. +- (void)send:(id)data; + +@end + +#pragma mark - SRWebSocketDelegate + +@protocol SRWebSocketDelegate + +// message will either be an NSString if the server is using text +// or NSData if the server is using binary. +- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message; + +@optional + +- (void)webSocketDidOpen:(SRWebSocket *)webSocket; +- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error; +- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean; + +@end + +#pragma mark - NSURLRequest (CertificateAdditions) + +@interface NSURLRequest (CertificateAdditions) + +@property (nonatomic, retain, readonly) NSArray *SR_SSLPinnedCertificates; + +@end + +#pragma mark - NSMutableURLRequest (CertificateAdditions) + +@interface NSMutableURLRequest (CertificateAdditions) + +@property (nonatomic, retain) NSArray *SR_SSLPinnedCertificates; + +@end + +#pragma mark - NSRunLoop (SRWebSocket) + +@interface NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop; + +@end diff --git a/Origami Plugin/SocketIO/SocketRocket/SRWebSocket.m b/Origami Plugin/SocketIO/SocketRocket/SRWebSocket.m new file mode 100755 index 0000000..db5039a --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/SRWebSocket.m @@ -0,0 +1,1757 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#import "SRWebSocket.h" + +#if TARGET_OS_IPHONE +#define HAS_ICU +#endif + +#ifdef HAS_ICU +#import +#endif + +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import +#import + +#import "base64.h" +#import "NSData+SRB64Additions.h" + +#if OS_OBJECT_USE_OBJC_RETAIN_RELEASE +#define sr_dispatch_retain(x) +#define sr_dispatch_release(x) +#define maybe_bridge(x) ((__bridge void *) x) +#else +#define sr_dispatch_retain(x) dispatch_retain(x) +#define sr_dispatch_release(x) dispatch_release(x) +#define maybe_bridge(x) (x) +#endif + +#if !__has_feature(objc_arc) +#error SocketRocket muust be compiled with ARC enabled +#endif + + +typedef enum { + SROpCodeTextFrame = 0x1, + SROpCodeBinaryFrame = 0x2, + // 3-7 reserved. + SROpCodeConnectionClose = 0x8, + SROpCodePing = 0x9, + SROpCodePong = 0xA, + // B-F reserved. +} SROpCode; + +typedef enum { + SRStatusCodeNormal = 1000, + SRStatusCodeGoingAway = 1001, + SRStatusCodeProtocolError = 1002, + SRStatusCodeUnhandledType = 1003, + // 1004 reserved. + SRStatusNoStatusReceived = 1005, + // 1004-1006 reserved. + SRStatusCodeInvalidUTF8 = 1007, + SRStatusCodePolicyViolated = 1008, + SRStatusCodeMessageTooBig = 1009, +} SRStatusCode; + +typedef struct { + BOOL fin; +// BOOL rsv1; +// BOOL rsv2; +// BOOL rsv3; + uint8_t opcode; + BOOL masked; + uint64_t payload_length; +} frame_header; + +static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +static inline int32_t validate_dispatch_data_partial_string(NSData *data); +static inline dispatch_queue_t log_queue(); +static inline void SRFastLog(NSString *format, ...); + +@interface NSData (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSString (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; + +@end + + +@interface NSURL (SRWebSocket) + +// The origin isn't really applicable for a native application. +// So instead, just map ws -> http and wss -> https. +- (NSString *)SR_origin; + +@end + + +@interface _SRRunLoopThread : NSThread + +@property (nonatomic, readonly) NSRunLoop *runLoop; + +@end + + +static NSString *newSHA1String(const char *bytes, size_t length) { + uint8_t md[CC_SHA1_DIGEST_LENGTH]; + + CC_SHA1(bytes, length, md); + + size_t buffer_size = ((sizeof(md) * 3 + 2) / 2); + + char *buffer = (char *)malloc(buffer_size); + + int len = b64_ntop(md, CC_SHA1_DIGEST_LENGTH, buffer, buffer_size); + if (len == -1) { + free(buffer); + return nil; + } else{ + return [[NSString alloc] initWithBytesNoCopy:buffer length:len encoding:NSASCIIStringEncoding freeWhenDone:YES]; + } +} + +@implementation NSData (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.bytes, self.length); +} + +@end + + +@implementation NSString (SRWebSocket) + +- (NSString *)stringBySHA1ThenBase64Encoding; +{ + return newSHA1String(self.UTF8String, self.length); +} + +@end + +NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; + +// Returns number of bytes consumed. Returning 0 means you didn't match. +// Sends bytes to callback handler; +typedef size_t (^stream_scanner)(NSData *collected_data); + +typedef void (^data_callback)(SRWebSocket *webSocket, NSData *data); + +@interface SRIOConsumer : NSObject { + stream_scanner _scanner; + data_callback _handler; + size_t _bytesNeeded; + BOOL _readToCurrentFrame; + BOOL _unmaskBytes; +} +@property (nonatomic, copy, readonly) stream_scanner consumer; +@property (nonatomic, copy, readonly) data_callback handler; +@property (nonatomic, assign) size_t bytesNeeded; +@property (nonatomic, assign, readonly) BOOL readToCurrentFrame; +@property (nonatomic, assign, readonly) BOOL unmaskBytes; + +@end + +// This class is not thread-safe, and is expected to always be run on the same queue. +@interface SRIOConsumerPool : NSObject + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)returnConsumer:(SRIOConsumer *)consumer; + +@end + +@interface SRWebSocket () + +- (void)_writeData:(NSData *)data; +- (void)_closeWithProtocolError:(NSString *)message; +- (void)_failWithError:(NSError *)error; + +- (void)_disconnect; + +- (void)_readFrameNew; +- (void)_readFrameContinue; + +- (void)_pumpScanner; + +- (void)_pumpWriting; + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; + +- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +- (void)_SR_commonInit; + +- (void)_initializeStreams; +- (void)_connect; + +@property (nonatomic) SRReadyState readyState; + +@property (nonatomic) NSOperationQueue *delegateOperationQueue; +@property (nonatomic) dispatch_queue_t delegateDispatchQueue; + +@end + + +@implementation SRWebSocket { + NSInteger _webSocketVersion; + + NSOperationQueue *_delegateOperationQueue; + dispatch_queue_t _delegateDispatchQueue; + + dispatch_queue_t _workQueue; + NSMutableArray *_consumers; + + NSInputStream *_inputStream; + NSOutputStream *_outputStream; + + NSMutableData *_readBuffer; + NSUInteger _readBufferOffset; + + NSMutableData *_outputBuffer; + NSUInteger _outputBufferOffset; + + uint8_t _currentFrameOpcode; + size_t _currentFrameCount; + size_t _readOpCount; + uint32_t _currentStringScanPosition; + NSMutableData *_currentFrameData; + + NSString *_closeReason; + + NSString *_secKey; + + BOOL _pinnedCertFound; + + uint8_t _currentReadMaskKey[4]; + size_t _currentReadMaskOffset; + + BOOL _consumerStopped; + + BOOL _closeWhenFinishedWriting; + BOOL _failed; + + BOOL _secure; + NSURLRequest *_urlRequest; + + CFHTTPMessageRef _receivedHTTPHeaders; + + BOOL _sentClose; + BOOL _didFail; + int _closeCode; + + BOOL _isPumping; + + NSMutableSet *_scheduledRunloops; + + // We use this to retain ourselves. + __strong SRWebSocket *_selfRetain; + + NSArray *_requestedProtocols; + SRIOConsumerPool *_consumerPool; +} + +@synthesize delegate = _delegate; +@synthesize url = _url; +@synthesize readyState = _readyState; +@synthesize protocol = _protocol; + +static __strong NSData *CRLFCRLF; + ++ (void)initialize; +{ + CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; +} + +- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols; +{ + self = [super init]; + if (self) { + assert(request.URL); + _url = request.URL; + _urlRequest = request; + + _requestedProtocols = [protocols copy]; + + [self _SR_commonInit]; + } + + return self; +} + +- (id)initWithURLRequest:(NSURLRequest *)request; +{ + return [self initWithURLRequest:request protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url; +{ + return [self initWithURL:url protocols:nil]; +} + +- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols; +{ + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + return [self initWithURLRequest:request protocols:protocols]; +} + +- (void)_SR_commonInit; +{ + + NSString *scheme = _url.scheme.lowercaseString; + assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]); + + if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) { + _secure = YES; + } + + _readyState = SR_CONNECTING; + _consumerStopped = YES; + _webSocketVersion = 13; + + _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + + // Going to set a specific on the queue so we can validate we're on the work queue + dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL); + + _delegateDispatchQueue = dispatch_get_main_queue(); + sr_dispatch_retain(_delegateDispatchQueue); + + _readBuffer = [[NSMutableData alloc] init]; + _outputBuffer = [[NSMutableData alloc] init]; + + _currentFrameData = [[NSMutableData alloc] init]; + + _consumers = [[NSMutableArray alloc] init]; + + _consumerPool = [[SRIOConsumerPool alloc] init]; + + _scheduledRunloops = [[NSMutableSet alloc] init]; + + [self _initializeStreams]; + + // default handlers +} + +- (void)assertOnWorkQueue; +{ + assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue)); +} + +- (void)dealloc +{ + _inputStream.delegate = nil; + _outputStream.delegate = nil; + + [_inputStream close]; + [_outputStream close]; + + sr_dispatch_release(_workQueue); + _workQueue = NULL; + + if (_receivedHTTPHeaders) { + CFRelease(_receivedHTTPHeaders); + _receivedHTTPHeaders = NULL; + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + _delegateDispatchQueue = NULL; + } +} + +#ifndef NDEBUG + +- (void)setReadyState:(SRReadyState)aReadyState; +{ + [self willChangeValueForKey:@"readyState"]; + assert(aReadyState > _readyState); + _readyState = aReadyState; + [self didChangeValueForKey:@"readyState"]; +} + +#endif + +- (void)open; +{ + assert(_url); + NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once"); + + _selfRetain = self; + + [self _connect]; +} + +// Calls block on delegate queue +- (void)_performDelegateBlock:(dispatch_block_t)block; +{ + if (_delegateOperationQueue) { + [_delegateOperationQueue addOperationWithBlock:block]; + } else { + assert(_delegateDispatchQueue); + dispatch_async(_delegateDispatchQueue, block); + } +} + +- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue; +{ + if (queue) { + sr_dispatch_retain(queue); + } + + if (_delegateDispatchQueue) { + sr_dispatch_release(_delegateDispatchQueue); + } + + _delegateDispatchQueue = queue; +} + +- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage; +{ + NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept"))); + + if (acceptHeader == nil) { + return NO; + } + + NSString *concattedString = [_secKey stringByAppendingString:SRWebSocketAppendToSecKeyString]; + NSString *expectedAccept = [concattedString stringBySHA1ThenBase64Encoding]; + + return [acceptHeader isEqualToString:expectedAccept]; +} + +- (void)_HTTPHeadersDidFinish; +{ + NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders); + + if (responseCode >= 400) { + SRFastLog(@"Request failed with response code %d", responseCode); + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2132 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode] forKey:NSLocalizedDescriptionKey]]]; + return; + + } + + if(![self _checkHandshake:_receivedHTTPHeaders]) { + [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol"))); + if (negotiatedProtocol) { + // Make sure we requested the protocol + if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) { + [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _protocol = negotiatedProtocol; + } + + self.readyState = SR_OPEN; + + if (!_didFail) { + [self _readFrameNew]; + } + + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) { + [self.delegate webSocketDidOpen:self]; + }; + }]; +} + + +- (void)_readHTTPHeader; +{ + if (_receivedHTTPHeaders == NULL) { + _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); + } + + [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) { + CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { + SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); + [self _HTTPHeadersDidFinish]; + } else { + [self _readHTTPHeader]; + } + }]; +} + +- (void)didConnect +{ + SRFastLog(@"Connected"); + CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)_url, kCFHTTPVersion1_1); + + // Set host first so it defaults + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@"%@:%@", _url.host, _url.port] : _url.host)); + + NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16]; + SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes); + _secKey = [keyBytes SR_stringByBase64Encoding]; + assert([_secKey length] == 24); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade")); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey); + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]); + + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin); + + if (_requestedProtocols) { + CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]); + } + + [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + + NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request)); + + CFRelease(request); + + [self _writeData:message]; + [self _readHTTPHeader]; +} + +- (void)_initializeStreams; +{ + NSInteger port = _url.port.integerValue; + if (port == 0) { + if (!_secure) { + port = 80; + } else { + port = 443; + } + } + NSString *host = _url.host; + + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + + CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream); + + _outputStream = CFBridgingRelease(writeStream); + _inputStream = CFBridgingRelease(readStream); + + + if (_secure) { + NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; + + [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // If we're using pinned certs, don't validate the certificate chain + if ([_urlRequest SR_SSLPinnedCertificates].count) { + [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + } + +#if DEBUG + [SSLOptions setValue:[NSNumber numberWithBool:NO] forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain]; + NSLog(@"SocketRocket: In debug mode. Allowing connection to any root cert"); +#endif + + [_outputStream setProperty:SSLOptions + forKey:(__bridge id)kCFStreamPropertySSLSettings]; + } + + _inputStream.delegate = self; + _outputStream.delegate = self; +} + +- (void)_connect; +{ + if (!_scheduledRunloops.count) { + [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; + } + + + [_outputStream open]; + [_inputStream open]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream scheduleInRunLoop:aRunLoop forMode:mode]; + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops addObject:@[aRunLoop, mode]]; +} + +- (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode; +{ + [_outputStream removeFromRunLoop:aRunLoop forMode:mode]; + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; + + [_scheduledRunloops removeObject:@[aRunLoop, mode]]; +} + +- (void)close; +{ + [self closeWithCode:-1 reason:nil]; +} + +- (void)closeWithCode:(NSInteger)code reason:(NSString *)reason; +{ + assert(code); + dispatch_async(_workQueue, ^{ + if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) { + return; + } + + BOOL wasConnecting = self.readyState == SR_CONNECTING; + + self.readyState = SR_CLOSING; + + SRFastLog(@"Closing with code %d reason %@", code, reason); + + if (wasConnecting) { + [self _disconnect]; + return; + } + + size_t maxMsgSize = [reason maximumLengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize]; + NSData *payload = mutablePayload; + + ((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code); + + if (reason) { + NSRange remainingRange = {0}; + + NSUInteger usedLength = 0; + + BOOL success = [reason getBytes:(char *)mutablePayload.mutableBytes + sizeof(uint16_t) maxLength:payload.length - sizeof(uint16_t) usedLength:&usedLength encoding:NSUTF8StringEncoding options:NSStringEncodingConversionExternalRepresentation range:NSMakeRange(0, reason.length) remainingRange:&remainingRange]; + + assert(success); + assert(remainingRange.length == 0); + + if (usedLength != maxMsgSize) { + payload = [payload subdataWithRange:NSMakeRange(0, usedLength + sizeof(uint16_t))]; + } + } + + + [self _sendFrameWithOpcode:SROpCodeConnectionClose data:payload]; + }); +} + +- (void)_closeWithProtocolError:(NSString *)message; +{ + // Need to shunt this on the _callbackQueue first to see if they received any messages + [self _performDelegateBlock:^{ + [self closeWithCode:SRStatusCodeProtocolError reason:message]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + }]; +} + +- (void)_failWithError:(NSError *)error; +{ + dispatch_async(_workQueue, ^{ + if (self.readyState != SR_CLOSED) { + _failed = YES; + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) { + [self.delegate webSocket:self didFailWithError:error]; + } + }]; + + self.readyState = SR_CLOSED; + _selfRetain = nil; + + SRFastLog(@"Failing with error %@", error.localizedDescription); + + [self _disconnect]; + } + }); +} + +- (void)_writeData:(NSData *)data; +{ + [self assertOnWorkQueue]; + + if (_closeWhenFinishedWriting) { + return; + } + [_outputBuffer appendData:data]; + [self _pumpWriting]; +} +- (void)send:(id)data; +{ + NSAssert(self.readyState != SR_CONNECTING, @"Invalid State: Cannot call send: until connection is open"); + // TODO: maybe not copy this for performance + data = [data copy]; + dispatch_async(_workQueue, ^{ + if ([data isKindOfClass:[NSString class]]) { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]]; + } else if ([data isKindOfClass:[NSData class]]) { + [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; + } else if (data == nil) { + [self _sendFrameWithOpcode:SROpCodeTextFrame data:data]; + } else { + assert(NO); + } + }); +} + +- (void)handlePing:(NSData *)pingData; +{ + // Need to pingpong this off _callbackQueue first to make sure messages happen in order + [self _performDelegateBlock:^{ + dispatch_async(_workQueue, ^{ + [self _sendFrameWithOpcode:SROpCodePong data:pingData]; + }); + }]; +} + +- (void)handlePong; +{ + // NOOP +} + +- (void)_handleMessage:(id)message +{ + SRFastLog(@"Received message"); + [self _performDelegateBlock:^{ + [self.delegate webSocket:self didReceiveMessage:message]; + }]; +} + + +static inline BOOL closeCodeIsValid(int closeCode) { + if (closeCode < 1000) { + return NO; + } + + if (closeCode >= 1000 && closeCode <= 1011) { + if (closeCode == 1004 || + closeCode == 1005 || + closeCode == 1006) { + return NO; + } + return YES; + } + + if (closeCode >= 3000 && closeCode <= 3999) { + return YES; + } + + if (closeCode >= 4000 && closeCode <= 4999) { + return YES; + } + + return NO; +} + +// Note from RFC: +// +// If there is a body, the first two +// bytes of the body MUST be a 2-byte unsigned integer (in network byte +// order) representing a status code with value /code/ defined in +// Section 7.4. Following the 2-byte integer the body MAY contain UTF-8 +// encoded data with value /reason/, the interpretation of which is not +// defined by this specification. + +- (void)handleCloseWithData:(NSData *)data; +{ + size_t dataSize = data.length; + __block uint16_t closeCode = 0; + + SRFastLog(@"Received close frame"); + + if (dataSize == 1) { + // TODO handle error + [self _closeWithProtocolError:@"Payload for close must be larger than 2 bytes"]; + return; + } else if (dataSize >= 2) { + [data getBytes:&closeCode length:sizeof(closeCode)]; + _closeCode = EndianU16_BtoN(closeCode); + if (!closeCodeIsValid(_closeCode)) { + [self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]]; + return; + } + if (dataSize > 2) { + _closeReason = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(2, dataSize - 2)] encoding:NSUTF8StringEncoding]; + if (!_closeReason) { + [self _closeWithProtocolError:@"Close reason MUST be valid UTF-8"]; + return; + } + } + } else { + _closeCode = SRStatusNoStatusReceived; + } + + [self assertOnWorkQueue]; + + if (self.readyState == SR_OPEN) { + [self closeWithCode:1000 reason:nil]; + } + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); +} + +- (void)_disconnect; +{ + [self assertOnWorkQueue]; + SRFastLog(@"Trying to disconnect"); + _closeWhenFinishedWriting = YES; + [self _pumpWriting]; +} + +- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode; +{ + // Check that the current data is valid UTF8 + + BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose); + if (!isControlFrame) { + [self _readFrameNew]; + } else { + dispatch_async(_workQueue, ^{ + [self _readFrameContinue]; + }); + } + + switch (opcode) { + case SROpCodeTextFrame: { + NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding]; + if (str == nil && frameData) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + + return; + } + [self _handleMessage:str]; + break; + } + case SROpCodeBinaryFrame: + [self _handleMessage:[frameData copy]]; + break; + case SROpCodeConnectionClose: + [self handleCloseWithData:frameData]; + break; + case SROpCodePing: + [self handlePing:frameData]; + break; + case SROpCodePong: + [self handlePong]; + break; + default: + [self _closeWithProtocolError:[NSString stringWithFormat:@"Unknown opcode %ld", (long)opcode]]; + // TODO: Handle invalid opcode + break; + } +} + +- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData; +{ + assert(frame_header.opcode != 0); + + if (self.readyState != SR_OPEN) { + return; + } + + + BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose); + + if (isControlFrame && !frame_header.fin) { + [self _closeWithProtocolError:@"Fragmented control frames not allowed"]; + return; + } + + if (isControlFrame && frame_header.payload_length >= 126) { + [self _closeWithProtocolError:@"Control frames cannot have payloads larger than 126 bytes"]; + return; + } + + if (!isControlFrame) { + _currentFrameOpcode = frame_header.opcode; + _currentFrameCount += 1; + } + + if (frame_header.payload_length == 0) { + if (isControlFrame) { + [self _handleFrameWithData:curData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + } + } else { + [self _addConsumerWithDataLength:frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) { + if (isControlFrame) { + [self _handleFrameWithData:newData opCode:frame_header.opcode]; + } else { + if (frame_header.fin) { + [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode]; + } else { + // TODO add assert that opcode is not a control; + [self _readFrameContinue]; + } + + } + } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked]; + } +} + +/* From RFC: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ + */ + +static const uint8_t SRFinMask = 0x80; +static const uint8_t SROpCodeMask = 0x0F; +static const uint8_t SRRsvMask = 0x70; +static const uint8_t SRMaskMask = 0x80; +static const uint8_t SRPayloadLenMask = 0x7F; + + +- (void)_readFrameContinue; +{ + assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0)); + + [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) { + __block frame_header header = {0}; + + const uint8_t *headerBuffer = data.bytes; + assert(data.length >= 2); + + if (headerBuffer[0] & SRRsvMask) { + [self _closeWithProtocolError:@"Server used RSV bits"]; + return; + } + + uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]); + + BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose); + + if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) { + [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"]; + return; + } + + if (receivedOpcode == 0 && self->_currentFrameCount == 0) { + [self _closeWithProtocolError:@"cannot continue a message"]; + return; + } + + header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode; + + header.fin = !!(SRFinMask & headerBuffer[0]); + + + header.masked = !!(SRMaskMask & headerBuffer[1]); + header.payload_length = SRPayloadLenMask & headerBuffer[1]; + + headerBuffer = NULL; + + if (header.masked) { + [self _closeWithProtocolError:@"Client must receive unmasked data"]; + } + + size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0; + + if (header.payload_length == 126) { + extra_bytes_needed += sizeof(uint16_t); + } else if (header.payload_length == 127) { + extra_bytes_needed += sizeof(uint64_t); + } + + if (extra_bytes_needed == 0) { + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } else { + [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) { + size_t mapped_size = data.length; + const void *mapped_buffer = data.bytes; + size_t offset = 0; + + if (header.payload_length == 126) { + assert(mapped_size >= sizeof(uint16_t)); + uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer)); + header.payload_length = newLen; + offset += sizeof(uint16_t); + } else if (header.payload_length == 127) { + assert(mapped_size >= sizeof(uint64_t)); + header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer)); + offset += sizeof(uint64_t); + } else { + assert(header.payload_length < 126 && header.payload_length >= 0); + } + + + if (header.masked) { + assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset); + memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey)); + } + + [self _handleFrameHeader:header curData:self->_currentFrameData]; + } readToCurrentFrame:NO unmaskBytes:NO]; + } + } readToCurrentFrame:NO unmaskBytes:NO]; +} + +- (void)_readFrameNew; +{ + dispatch_async(_workQueue, ^{ + [_currentFrameData setLength:0]; + + _currentFrameOpcode = 0; + _currentFrameCount = 0; + _readOpCount = 0; + _currentStringScanPosition = 0; + + [self _readFrameContinue]; + }); +} + +- (void)_pumpWriting; +{ + [self assertOnWorkQueue]; + + NSUInteger dataLength = _outputBuffer.length; + if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) { + NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]; + if (bytesWritten == -1) { + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]]; + return; + } + + _outputBufferOffset += bytesWritten; + + if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { + _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; + _outputBufferOffset = 0; + } + } + + if (_closeWhenFinishedWriting && + _outputBuffer.length - _outputBufferOffset == 0 && + (_inputStream.streamStatus != NSStreamStatusNotOpen && + _inputStream.streamStatus != NSStreamStatusClosed) && + !_sentClose) { + _sentClose = YES; + + [_outputStream close]; + [_inputStream close]; + + + for (NSArray *runLoop in [_scheduledRunloops copy]) { + [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]]; + } + + if (!_failed) { + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES]; + } + }]; + } + + _selfRetain = nil; + } +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback; +{ + [self assertOnWorkQueue]; + [self _addConsumerWithScanner:consumer callback:callback dataLength:0]; +} + +- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + [self assertOnWorkQueue]; + assert(dataLength); + + [_consumers addObject:[_consumerPool consumerWithScanner:nil handler:callback bytesNeeded:dataLength readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]]; + [self _pumpScanner]; +} + +- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength; +{ + [self assertOnWorkQueue]; + [_consumers addObject:[_consumerPool consumerWithScanner:consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]]; + [self _pumpScanner]; +} + + +static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'}; + +- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; +{ + [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler]; +} + +- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler; +{ + // TODO optimize so this can continue from where we last searched + stream_scanner consumer = ^size_t(NSData *data) { + __block size_t found_size = 0; + __block size_t match_count = 0; + + size_t size = data.length; + const unsigned char *buffer = data.bytes; + for (size_t i = 0; i < size; i++ ) { + if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) { + match_count += 1; + if (match_count == length) { + found_size = i + 1; + break; + } + } else { + match_count = 0; + } + } + return found_size; + }; + [self _addConsumerWithScanner:consumer callback:dataHandler]; +} + + +// Returns true if did work +- (BOOL)_innerPumpScanner { + + BOOL didWork = NO; + + if (self.readyState >= SR_CLOSING) { + return didWork; + } + + if (!_consumers.count) { + return didWork; + } + + size_t curSize = _readBuffer.length - _readBufferOffset; + if (!curSize) { + return didWork; + } + + SRIOConsumer *consumer = [_consumers objectAtIndex:0]; + + size_t bytesNeeded = consumer.bytesNeeded; + + size_t foundSize = 0; + if (consumer.consumer) { + NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO]; + foundSize = consumer.consumer(tempView); + } else { + assert(consumer.bytesNeeded); + if (curSize >= bytesNeeded) { + foundSize = bytesNeeded; + } else if (consumer.readToCurrentFrame) { + foundSize = curSize; + } + } + + NSData *slice = nil; + if (consumer.readToCurrentFrame || foundSize) { + NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize); + slice = [_readBuffer subdataWithRange:sliceRange]; + + _readBufferOffset += foundSize; + + if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) { + _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0; + } + + if (consumer.unmaskBytes) { + NSMutableData *mutableSlice = [slice mutableCopy]; + + NSUInteger len = mutableSlice.length; + uint8_t *bytes = mutableSlice.mutableBytes; + + for (NSUInteger i = 0; i < len; i++) { + bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)]; + _currentReadMaskOffset += 1; + } + + slice = mutableSlice; + } + + if (consumer.readToCurrentFrame) { + [_currentFrameData appendData:slice]; + + _readOpCount += 1; + + if (_currentFrameOpcode == SROpCodeTextFrame) { + // Validate UTF8 stuff. + size_t currentDataSize = _currentFrameData.length; + if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) { + // TODO: Optimize the crap out of this. Don't really have to copy all the data each time + + size_t scanSize = currentDataSize - _currentStringScanPosition; + + NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)]; + int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data); + + if (valid_utf8_size == -1) { + [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"]; + dispatch_async(_workQueue, ^{ + [self _disconnect]; + }); + return didWork; + } else { + _currentStringScanPosition += valid_utf8_size; + } + } + + } + + consumer.bytesNeeded -= foundSize; + + if (consumer.bytesNeeded == 0) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, nil); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } else if (foundSize) { + [_consumers removeObjectAtIndex:0]; + consumer.handler(self, slice); + [_consumerPool returnConsumer:consumer]; + didWork = YES; + } + } + return didWork; +} + +-(void)_pumpScanner; +{ + [self assertOnWorkQueue]; + + if (!_isPumping) { + _isPumping = YES; + } else { + return; + } + + while ([self _innerPumpScanner]) { + + } + + _isPumping = NO; +} + +//#define NOMASK + +static const size_t SRFrameHeaderOverhead = 32; + +- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data; +{ + [self assertOnWorkQueue]; + + NSAssert(data == nil || [data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"Function expects nil, NSString or NSData"); + + size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length]; + + NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; + if (!frame) { + [self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; + return; + } + uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes]; + + // set fin + frame_buffer[0] = SRFinMask | opcode; + + BOOL useMask = YES; +#ifdef NOMASK + useMask = NO; +#endif + + if (useMask) { + // set the mask and header + frame_buffer[1] |= SRMaskMask; + } + + size_t frame_buffer_size = 2; + + const uint8_t *unmasked_payload = NULL; + if ([data isKindOfClass:[NSData class]]) { + unmasked_payload = (uint8_t *)[data bytes]; + } else if ([data isKindOfClass:[NSString class]]) { + unmasked_payload = (const uint8_t *)[data UTF8String]; + } else { + assert(NO); + } + + if (payloadLength < 126) { + frame_buffer[1] |= payloadLength; + } else if (payloadLength <= UINT16_MAX) { + frame_buffer[1] |= 126; + *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength); + frame_buffer_size += sizeof(uint16_t); + } else { + frame_buffer[1] |= 127; + *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength); + frame_buffer_size += sizeof(uint64_t); + } + + if (!useMask) { + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i]; + frame_buffer_size += 1; + } + } else { + uint8_t *mask_key = frame_buffer + frame_buffer_size; + SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key); + frame_buffer_size += sizeof(uint32_t); + + // TODO: could probably optimize this with SIMD + for (size_t i = 0; i < payloadLength; i++) { + frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)]; + frame_buffer_size += 1; + } + } + + assert(frame_buffer_size <= [frame length]); + frame.length = frame_buffer_size; + + [self _writeData:frame]; +} + +- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode; +{ + if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + + NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates]; + if (sslCerts) { + SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; + if (secTrust) { + NSInteger numCerts = SecTrustGetCertificateCount(secTrust); + for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i); + NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert)); + + for (id ref in sslCerts) { + SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; + NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); + + if ([trustedCertData isEqualToData:certData]) { + _pinnedCertFound = YES; + break; + } + } + } + } + + if (!_pinnedCertFound) { + dispatch_async(_workQueue, ^{ + [self _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid server cert"] forKey:NSLocalizedDescriptionKey]]]; + }); + return; + } + } + } + + dispatch_async(_workQueue, ^{ + switch (eventCode) { + case NSStreamEventOpenCompleted: { + SRFastLog(@"NSStreamEventOpenCompleted %@", aStream); + if (self.readyState >= SR_CLOSING) { + return; + } + assert(_readBuffer); + + if (self.readyState == SR_CONNECTING && aStream == _inputStream) { + [self didConnect]; + } + [self _pumpWriting]; + [self _pumpScanner]; + break; + } + + case NSStreamEventErrorOccurred: { + SRFastLog(@"NSStreamEventErrorOccurred %@ %@", aStream, [[aStream streamError] copy]); + /// TODO specify error better! + [self _failWithError:aStream.streamError]; + _readBufferOffset = 0; + [_readBuffer setLength:0]; + break; + + } + + case NSStreamEventEndEncountered: { + [self _pumpScanner]; + SRFastLog(@"NSStreamEventEndEncountered %@", aStream); + if (aStream.streamError) { + [self _failWithError:aStream.streamError]; + } else { + if (self.readyState != SR_CLOSED) { + self.readyState = SR_CLOSED; + _selfRetain = nil; + } + + if (!_sentClose && !_failed) { + _sentClose = YES; + // If we get closed in this state it's probably not clean because we should be sending this when we send messages + [self _performDelegateBlock:^{ + if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) { + [self.delegate webSocket:self didCloseWithCode:0 reason:@"Stream end encountered" wasClean:NO]; + } + }]; + } + } + + break; + } + + case NSStreamEventHasBytesAvailable: { + SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream); + const int bufferSize = 2048; + uint8_t buffer[bufferSize]; + + while (_inputStream.hasBytesAvailable) { + int bytes_read = [_inputStream read:buffer maxLength:bufferSize]; + + if (bytes_read > 0) { + [_readBuffer appendBytes:buffer length:bytes_read]; + } else if (bytes_read < 0) { + [self _failWithError:_inputStream.streamError]; + } + + if (bytes_read != bufferSize) { + break; + } + }; + [self _pumpScanner]; + break; + } + + case NSStreamEventHasSpaceAvailable: { + SRFastLog(@"NSStreamEventHasSpaceAvailable %@", aStream); + [self _pumpWriting]; + break; + } + + default: + SRFastLog(@"(default) %@", aStream); + break; + } + }); +} + +@end + + +@implementation SRIOConsumer + +@synthesize bytesNeeded = _bytesNeeded; +@synthesize consumer = _scanner; +@synthesize handler = _handler; +@synthesize readToCurrentFrame = _readToCurrentFrame; +@synthesize unmaskBytes = _unmaskBytes; + +- (void)setupWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + _scanner = [scanner copy]; + _handler = [handler copy]; + _bytesNeeded = bytesNeeded; + _readToCurrentFrame = readToCurrentFrame; + _unmaskBytes = unmaskBytes; + assert(_scanner || _bytesNeeded); +} + + +@end + + +@implementation SRIOConsumerPool { + NSUInteger _poolSize; + NSMutableArray *_bufferedConsumers; +} + +- (id)initWithBufferCapacity:(NSUInteger)poolSize; +{ + self = [super init]; + if (self) { + _poolSize = poolSize; + _bufferedConsumers = [[NSMutableArray alloc] initWithCapacity:poolSize]; + } + return self; +} + +- (id)init +{ + return [self initWithBufferCapacity:8]; +} + +- (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner handler:(data_callback)handler bytesNeeded:(size_t)bytesNeeded readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes; +{ + SRIOConsumer *consumer = nil; + if (_bufferedConsumers.count) { + consumer = [_bufferedConsumers lastObject]; + [_bufferedConsumers removeLastObject]; + } else { + consumer = [[SRIOConsumer alloc] init]; + } + + [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; + + return consumer; +} + +- (void)returnConsumer:(SRIOConsumer *)consumer; +{ + if (_bufferedConsumers.count < _poolSize) { + [_bufferedConsumers addObject:consumer]; + } +} + +@end + + +@implementation NSURLRequest (CertificateAdditions) + +- (NSArray *)SR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSMutableURLRequest (CertificateAdditions) + +- (NSArray *)SR_SSLPinnedCertificates; +{ + return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self]; +} + +- (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates; +{ + [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self]; +} + +@end + +@implementation NSURL (SRWebSocket) + +- (NSString *)SR_origin; +{ + NSString *scheme = [self.scheme lowercaseString]; + + if ([scheme isEqualToString:@"wss"]) { + scheme = @"https"; + } else if ([scheme isEqualToString:@"ws"]) { + scheme = @"http"; + } + + if (self.port) { + return [NSString stringWithFormat:@"%@://%@:%@/", scheme, self.host, self.port]; + } else { + return [NSString stringWithFormat:@"%@://%@/", scheme, self.host]; + } +} + +@end + +static inline dispatch_queue_t log_queue() { + static dispatch_queue_t queue = 0; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("fast log queue", DISPATCH_QUEUE_SERIAL); + }); + + return queue; +} + +//#define SR_ENABLE_LOG + +static inline void SRFastLog(NSString *format, ...) { +#ifdef SR_ENABLE_LOG + __block va_list arg_list; + va_start (arg_list, format); + + NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list]; + + va_end(arg_list); + + NSLog(@"[SR] %@", formattedString); +#endif +} + + +#ifdef HAS_ICU + +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + const void * contents = [data bytes]; + long size = [data length]; + + const uint8_t *str = (const uint8_t *)contents; + + UChar32 codepoint = 1; + int32_t offset = 0; + int32_t lastOffset = 0; + while(offset < size && codepoint > 0) { + lastOffset = offset; + U8_NEXT(str, offset, size, codepoint); + } + + if (codepoint == -1) { + // Check to see if the last byte is valid or whether it was just continuing + if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) { + + size = -1; + } else { + uint8_t leadByte = str[lastOffset]; + U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte)); + + for (int i = lastOffset + 1; i < offset; i++) { + if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) { + size = -1; + } + } + + if (size != -1) { + size = lastOffset; + } + } + } + + if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) { + size = -1; + } + + return size; +} + +#else + +// This is a hack, and probably not optimal +static inline int32_t validate_dispatch_data_partial_string(NSData *data) { + static const int maxCodepointSize = 3; + + for (int i = 0; i < maxCodepointSize; i++) { + NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO]; + if (str) { + return data.length - i; + } + } + + return -1; +} + +#endif + +static _SRRunLoopThread *networkThread = nil; +static NSRunLoop *networkRunLoop = nil; + +@implementation NSRunLoop (SRWebSocket) + ++ (NSRunLoop *)SR_networkRunLoop { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + networkThread = [[_SRRunLoopThread alloc] init]; + networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; + [networkThread start]; + networkRunLoop = networkThread.runLoop; + }); + + return networkRunLoop; +} + +@end + + +@implementation _SRRunLoopThread { + dispatch_group_t _waitGroup; +} + +@synthesize runLoop = _runLoop; + +- (void)dealloc +{ + sr_dispatch_release(_waitGroup); +} + +- (id)init +{ + self = [super init]; + if (self) { + _waitGroup = dispatch_group_create(); + dispatch_group_enter(_waitGroup); + } + return self; +} + +- (void)main; +{ + @autoreleasepool { + _runLoop = [NSRunLoop currentRunLoop]; + dispatch_group_leave(_waitGroup); + + NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate distantFuture] interval:0.0 target:nil selector:nil userInfo:nil repeats:NO]; + [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode]; + + while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) { + + } + assert(NO); + } +} + +- (NSRunLoop *)runLoop; +{ + dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER); + return _runLoop; +} + +@end diff --git a/Origami Plugin/SocketIO/SocketRocket/SocketRocket-Prefix.pch b/Origami Plugin/SocketIO/SocketRocket/SocketRocket-Prefix.pch new file mode 100755 index 0000000..8c32c82 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/SocketRocket-Prefix.pch @@ -0,0 +1,27 @@ +// +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __OBJC__ + #import +#endif + +#ifdef __cplusplus +} +#endif diff --git a/Origami Plugin/SocketIO/SocketRocket/base64.c b/Origami Plugin/SocketIO/SocketRocket/base64.c new file mode 100755 index 0000000..1d76d16 --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/base64.c @@ -0,0 +1,314 @@ +/* $OpenBSD: base64.c,v 1.5 2006/10/21 09:55:03 otto Exp $ */ + +/* + * Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +/* OPENBSD ORIGINAL: lib/libc/net/base64.c */ + + +#if (!defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP)) || (!defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON)) + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "base64.h" + +static const char Base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +#if !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) +int +b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) +{ + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + u_int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} +#endif /* !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) */ + +#if !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int +b64_pton(char const *src, u_char *target, size_t targsize) +{ + u_int tarindex, state; + int ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if (tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + target[tarindex+1] = ((pos - Base64) & 0x0f) + << 4 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if (tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + target[tarindex+1] = ((pos - Base64) & 0x03) + << 6; + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for (; ch != '\0'; ch = *src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for (; ch != '\0'; ch = *src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} + +#endif /* !defined(HAVE_B64_PTON) && !defined(HAVE___B64_PTON) */ +#endif diff --git a/Origami Plugin/SocketIO/SocketRocket/base64.h b/Origami Plugin/SocketIO/SocketRocket/base64.h new file mode 100755 index 0000000..c345e2e --- /dev/null +++ b/Origami Plugin/SocketIO/SocketRocket/base64.h @@ -0,0 +1,34 @@ +// Copyright 2012 Square Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +#ifndef SocketRocket_base64_h +#define SocketRocket_base64_h + +#include + +extern int +b64_ntop(u_char const *src, + size_t srclength, + char *target, + size_t targsize); + +extern int +b64_pton(char const *src, + u_char *target, + size_t targsize); + + +#endif diff --git a/Origami.plugin/Contents/Info.plist b/Origami.plugin/Contents/Info.plist index 20607ee..2a3e340 100644 --- a/Origami.plugin/Contents/Info.plist +++ b/Origami.plugin/Contents/Info.plist @@ -3,7 +3,7 @@ BuildMachineOSBuild - 15A279b + 15E39d CFBundleDevelopmentRegion English CFBundleExecutable @@ -19,31 +19,31 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.2 + 2.2 CFBundleSupportedPlatforms MacOSX CFBundleVersion - 13 + 16 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 7A218 + 7C1002 DTPlatformVersion GM DTSDKBuild - 15A278 + 15C43 DTSDKName macosx10.11 DTXcode - 0700 + 0721 DTXcodeBuild - 7A218 + 7C1002 GFPlugIn NSHumanReadableCopyright - Copyright © 2013-2015 Facebook, Inc. All rights reserved. + Copyright © 2013-2016 Facebook, Inc. All rights reserved. NSPrincipalClass FBOrigamiPrincipal SUEnableAutomaticChecks @@ -51,7 +51,7 @@ SUEnableSystemProfiling SUFeedURL - http://origami.facebook.com/update/updates.xml.rss + https://origami.facebook.com/update/updates.xml.rss SUPublicDSAKeyFile dsa_pub.pem diff --git a/Origami.plugin/Contents/MacOS/Origami b/Origami.plugin/Contents/MacOS/Origami index 6d4736f..6d36749 100755 Binary files a/Origami.plugin/Contents/MacOS/Origami and b/Origami.plugin/Contents/MacOS/Origami differ diff --git a/Origami.plugin/Contents/Resources/DHAppleScriptPatch.xml b/Origami.plugin/Contents/Resources/DHAppleScriptPatch.xml new file mode 100644 index 0000000..fefe9c9 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHAppleScriptPatch.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Executes an AppleScript. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + AppleScript + + inputAttributes + + inputScript + + description + + name + Location + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputResult + + description + + name + Result + + + + diff --git a/Origami.plugin/Contents/Resources/DHImageAtURLPatch.xml b/Origami.plugin/Contents/Resources/DHImageAtURLPatch.xml new file mode 100644 index 0000000..664a30c --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHImageAtURLPatch.xml @@ -0,0 +1,65 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Asynchronously downloads and caches requested images. + +If you specify a single URL (a string), the image at that URL will be returned. If you specify multiple URLs (a structure of strings), a structure of images will be returned. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Image at URL + + inputAttributes + + inputURLOrURLs + + description + + name + URL(s) + + inputUseCache + + description + + name + Use Cache + + + outputAttributes + + outputImageOrStructure + + description + + name + Image(s) + + outputDone + + description + + name + Done + + + + diff --git a/Origami.plugin/Contents/Resources/DHImageWithFormattedStrings.xml b/Origami.plugin/Contents/Resources/DHImageWithFormattedStrings.xml new file mode 100644 index 0000000..16beb29 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHImageWithFormattedStrings.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Creates an image of a string generated by the String Formatter patch. This lets you put together runs of styled text. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Image With Formatted String + + inputAttributes + + inputString + + description + + name + String + + + outputAttributes + + outputImage + + description + + name + Image + + outputUnformattedString + + description + + name + Unformatted String + + + + diff --git a/Origami.plugin/Contents/Resources/DHJSONImporterPatch.xml b/Origami.plugin/Contents/Resources/DHJSONImporterPatch.xml new file mode 100644 index 0000000..654eb14 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHJSONImporterPatch.xml @@ -0,0 +1,63 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Downloads and parses JSON from a specified URL or file path. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + JSON Importer + + inputAttributes + + inputJSONLocation + + description + + name + Location + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputParsedJSON + + description + + name + Structure + + outputDoneSignal + + description + + name + Done Signal + + + + diff --git a/Origami.plugin/Contents/Resources/DHRESTRequestPatch.xml b/Origami.plugin/Contents/Resources/DHRESTRequestPatch.xml new file mode 100644 index 0000000..d023eb5 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHRESTRequestPatch.xml @@ -0,0 +1,105 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Performs a REST request and returns the result. Supports "Create" (POST), "Read" (GET), "Update" (PUT) and "Destroy" (DELETE) request types. Allows attaching an object to a "Create" or "Update" request and specification of additional headers for any request. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + REST Request + + inputAttributes + + inputEnable + + description + + name + Enable + + inputRequestType + + description + + name + Type + menu + + Create (POST) + Read (GET) + Update (PUT) + Destroy (DELETE) + + + inputURL + + description + + name + URL + + inputParameters + + description + + name + Parameters + + inputHeaders + + description + + name + Headers + + inputObject + + description + + name + Object + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputResult + + description + + name + Result + + outputDoneSignal + + description + + name + Done Signal + + + + diff --git a/Origami.plugin/Contents/Resources/DHSoundPlayerProPatch.xml b/Origami.plugin/Contents/Resources/DHSoundPlayerProPatch.xml new file mode 100644 index 0000000..cf09a53 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHSoundPlayerProPatch.xml @@ -0,0 +1,67 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Plays any QuickTime file as a sound. Supports pausing, reseting, looping, and adjusting the volume. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Sound Player Pro + + inputAttributes + + inputSoundLocation + + description + + name + Sound Location + + inputPlaySound + + description + + name + Play Sound + + inputResetSignal + + description + + name + Reset Signal + + inputLooping + + description + + name + Looping + + inputVolume + + description + + name + Volume + + + + diff --git a/Origami.plugin/Contents/Resources/DHStringAtURLPatch.xml b/Origami.plugin/Contents/Resources/DHStringAtURLPatch.xml new file mode 100644 index 0000000..325a0bd --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHStringAtURLPatch.xml @@ -0,0 +1,70 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Asynchronously downloads the contents of a URL. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + String at URL + + inputAttributes + + inputEnable + + description + + name + Enable + + inputURL + + description + + name + URL + + inputUpdateSignal + + description + + name + Update Signal + + + outputAttributes + + outputString + + description + + name + String + + outputDoneSignal + + description + + name + Done Signal + + + + diff --git a/Origami.plugin/Contents/Resources/DHStringFormatterPatch.xml b/Origami.plugin/Contents/Resources/DHStringFormatterPatch.xml new file mode 100644 index 0000000..5ce55e4 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHStringFormatterPatch.xml @@ -0,0 +1,105 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Creates a formatted string for use by the Image With Formatted String patch. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + String Formatter + + inputAttributes + + inputString + + description + + name + String + + inputColor + + description + + name + Color + + inputFontName + + description + + name + Font Name + + inputFontSize + + description + + name + Font Size + + inputBold + + description + + name + Bold + + inputUnderline + + description + + name + Underline + + inputStrikethrough + + description + + name + Strikethrough + + inputMaxWidth + + description + + name + Width + + inputMaxHeight + + description + + name + Max Height + + + outputAttributes + + outputFormattedString + + description + + name + Formatted String + + + + diff --git a/Origami.plugin/Contents/Resources/DHStructureAllKeysPatch.xml b/Origami.plugin/Contents/Resources/DHStructureAllKeysPatch.xml new file mode 100644 index 0000000..3767318 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHStructureAllKeysPatch.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Returns the keys from a named structure. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure All Keys + + inputAttributes + + inputStructure + + description + + name + Structure + + inputSort + + description + + name + Sort + + + outputAttributes + + outputStructure + + description + + name + Keys + + + + diff --git a/Origami.plugin/Contents/Resources/DHStructureAllValuesPatch.xml b/Origami.plugin/Contents/Resources/DHStructureAllValuesPatch.xml new file mode 100644 index 0000000..d52798c --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHStructureAllValuesPatch.xml @@ -0,0 +1,62 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Returns the values from a structure. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure All Values + + inputAttributes + + inputStructure + + description + + name + Structure + + inputSort + + description + + name + Sort + menu + + None + Using Keys + Using Values + + + + outputAttributes + + outputStructure + + description + + name + Values + + + + diff --git a/Origami.plugin/Contents/Resources/DHStructureMultiplePathMembersPatch.xml b/Origami.plugin/Contents/Resources/DHStructureMultiplePathMembersPatch.xml new file mode 100644 index 0000000..567dab1 --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHStructureMultiplePathMembersPatch.xml @@ -0,0 +1,58 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + Returns a structure containing the results from multiple search paths, separated by commas. Optionally specify labels for the new structure with equal signs and optionally cluster label sub-groups with a forward slash. + +For example, the path "id = story_id, user/name = actors.0.name.text, user/age = actors.0.age" would yield a structure with two top-level keys ("id" and "user") and two sub-level keys ("name" and "age" within "user"). All of the leaf-node children (in this example, "id", "name" and "age") will contain the values of their respective search path values. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure Multiple Path Members + + inputAttributes + + inputStructure + + description + + name + Structure + + inputPaths + + description + + name + Paths + + + outputAttributes + + outputStructure + + description + + name + Structure + + + + diff --git a/Origami.plugin/Contents/Resources/DHStructurePathMemberPatch.xml b/Origami.plugin/Contents/Resources/DHStructurePathMemberPatch.xml new file mode 100755 index 0000000..5a99e6f --- /dev/null +++ b/Origami.plugin/Contents/Resources/DHStructurePathMemberPatch.xml @@ -0,0 +1,65 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch returns a member or members in a structure resulting from a path search. + +For example, if you had an indexed structure with two members, a structure with key "name", value "Cat", and a structure with key "name", value "Dog", you could use a path of "0.name" to get the value "Cat". If you used a key of "*.name", you'd get a structure with "Cat" and "Dog". + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Drew Hamlin + name + Structure Path Member + + inputAttributes + + inputStructure + + description + + name + Structure + + inputPath + + description + + name + Path + + + outputAttributes + + outputMember + + description + + name + Member + + outputUntraversedPaths + + description + + name + Untraversed Paths + + + + diff --git a/Origami.plugin/Contents/Resources/Export for Origami.sketchplugin b/Origami.plugin/Contents/Resources/Export for Origami.sketchplugin index b855338..195c841 100644 --- a/Origami.plugin/Contents/Resources/Export for Origami.sketchplugin +++ b/Origami.plugin/Contents/Resources/Export for Origami.sketchplugin @@ -53,8 +53,6 @@ function main() { var group_name = ""; if (is_group(layer)) { group_name = [layer name]; - } - if (![layer isMemberOfClass:[MSArtboardGroup class]]) { inside_top_level_group = true; top_level_group_pos = calculate_real_position_for(layer); } @@ -211,27 +209,23 @@ function export_layer(layer, depth, artboard_name) { function metadata_for(layer, layer_copy) { loggle("Getting metadata for " + [layer name]); - var gkrect = [GKRect rectWithRect:[MSSliceTrimming trimmedRectForSlice:layer_copy]], + var cgrect = [MSSliceTrimming trimmedRectForSlice:layer_copy], position = calculate_real_position_for(layer), x,y,w,h, layer_hidden = [layer name].indexOf("@@hidden") > -1; x = position.x; y = position.y; - w = [gkrect width]; - h = [gkrect height]; + w = cgrect.size.width; + h = cgrect.size.height; - if ([layer isMemberOfClass:[MSArtboardGroup class]]) { + // If this is an artboard we should ignore its position (at least for now) + if (is_artboard(layer)) { loggle("Resetting x and y to 0 because artboard"); x = 0; y = 0; } - if (inside_top_level_group) { - x-= top_level_group_pos.x; - y-= top_level_group_pos.y; - loggle("Shifting x by: " + top_level_group_pos.x); - loggle("Shifting y by: " + top_level_group_pos.y); - } + loggle("Metadata for <" + [layer name] + ">: { x:"+x+", y:"+y+", width:"+w+", height:"+h+"}"); return { x: x, @@ -244,15 +238,15 @@ function metadata_for(layer, layer_copy) { } function calculate_real_position_for(layer) { - var gkrect = [GKRect rectWithRect:[MSSliceTrimming trimmedRectForSlice:layer]], + var cgrect = [MSSliceTrimming trimmedRectForSlice:layer], absrect = [layer absoluteRect]; var rulerDeltaX = [absrect rulerX] - [absrect x], rulerDeltaY = [absrect rulerY] - [absrect y], - GKRectRulerX = [gkrect x] + rulerDeltaX, - GKRectRulerY = [gkrect y] + rulerDeltaY; + CGRectRulerX = cgrect.origin.x + rulerDeltaX, + CGRectRulerY = cgrect.origin.y + rulerDeltaY; return { - x: Math.round(GKRectRulerX), - y: Math.round(GKRectRulerY) + x: Math.round(CGRectRulerX), + y: Math.round(CGRectRulerY) } } @@ -311,6 +305,10 @@ function is_group(layer) { return [layer isMemberOfClass:[MSLayerGroup class]] || [layer isMemberOfClass:[MSArtboardGroup class]] } +function is_artboard(layer) { + return [layer isMemberOfClass:[MSArtboardGroup class]]; +} + function is_symbol(layer) { return [layer parentOrSelfIsSymbol]; } diff --git a/Origami.plugin/Contents/Resources/FBCursorPatch.xml b/Origami.plugin/Contents/Resources/FBCursorPatch.xml index 1cdc6ce..5ae102c 100755 --- a/Origami.plugin/Contents/Resources/FBCursorPatch.xml +++ b/Origami.plugin/Contents/Resources/FBCursorPatch.xml @@ -40,6 +40,7 @@ Created by: Brandon Walkin Open Hand Closed Hand I Beam + Pointing Hand inputHide diff --git a/Origami.plugin/Contents/Resources/FBDescriptionPatch.xml b/Origami.plugin/Contents/Resources/FBDescriptionPatch.xml new file mode 100755 index 0000000..42e552b --- /dev/null +++ b/Origami.plugin/Contents/Resources/FBDescriptionPatch.xml @@ -0,0 +1,49 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch outputs a string description of an object. It should be similar to what you see in the tooltip when hovered over a port. It's helpful for inspecting complex structures. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + Description + + inputAttributes + + inputObject + + description + Object + name + Object + + + outputAttributes + + outputDescription + + description + Description + name + Description + + + + \ No newline at end of file diff --git a/Origami.plugin/Contents/Resources/FBFPS.xml b/Origami.plugin/Contents/Resources/FBFPS.xml new file mode 100755 index 0000000..d542b3e --- /dev/null +++ b/Origami.plugin/Contents/Resources/FBFPS.xml @@ -0,0 +1,38 @@ + + + + + nodeAttributes + + category + FPS + categories + + Environment + + copyright + Brandon Walkin + description + This patch outputs the frames per second of the composition. + name + FPS + + outputAttributes + + outputFPS + + description + Frames per second + name + FPS + + outputFPSString + + description + Frames per second string + name + FPS String + + + + \ No newline at end of file diff --git a/Origami.plugin/Contents/Resources/FBFileUpdated.xml b/Origami.plugin/Contents/Resources/FBFileUpdated.xml new file mode 100755 index 0000000..cef59fd --- /dev/null +++ b/Origami.plugin/Contents/Resources/FBFileUpdated.xml @@ -0,0 +1,49 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch outputs a pulse when the file at the specified path is modified. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + File Updated + + inputAttributes + + inputPath + + description + Path + name + Path + + + outputAttributes + + outputUpdated + + description + A pulse when the file is updated + name + Updated + + + + \ No newline at end of file diff --git a/Origami.plugin/Contents/Resources/FBLogToConsole.xml b/Origami.plugin/Contents/Resources/FBLogToConsole.xml new file mode 100755 index 0000000..c1d456e --- /dev/null +++ b/Origami.plugin/Contents/Resources/FBLogToConsole.xml @@ -0,0 +1,39 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch logs the input string to the console when it's enabled. It logs during each frame the viewer is rendering. Open Console.app to view the messages. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + Log to Console + + inputAttributes + + inputMessage + + description + The string you want to log. + name + Message + + + + \ No newline at end of file diff --git a/Origami.plugin/Contents/Resources/FBStructureShuffle.xml b/Origami.plugin/Contents/Resources/FBStructureShuffle.xml new file mode 100755 index 0000000..d85b47a --- /dev/null +++ b/Origami.plugin/Contents/Resources/FBStructureShuffle.xml @@ -0,0 +1,56 @@ + + + + + nodeAttributes + + category + Origami + categories + + Origami + + description + This patch takes an indexed structure and outputs a structure with each member in a random position. + + +Origami +http://origami.facebook.com/ + +Use of this patch is subject to the license found at http://origami.facebook.com/license/ + +Copyright: (c) 2016, Facebook, Inc. All rights reserved. + +Created by: Brandon Walkin + name + Structure Shuffle + + inputAttributes + + inputStructure + + description + Structure to be shuffled + name + Structure + + inputShuffleSignal + + description + Shuffles on the leading edge of the signal + name + Shuffle Signal + + + outputAttributes + + outputStructure + + description + Shuffled structure + name + Structure + + + + \ No newline at end of file