├── .gitignore ├── Assets ├── Icon.psd ├── ScreenshotPlaceholder.psd └── screenshots.png ├── Connecter.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Connecter ├── Base.lproj │ ├── MainMenu.xib │ └── RMConnecterWindow.xib ├── Connecter.entitlements ├── ConnectorFileIcon.icns ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon1024.png │ │ ├── Icon256.png │ │ └── Icon512.png │ └── ScreenshotPlaceholder.imageset │ │ ├── Contents.json │ │ ├── ScreenshotPlaceholder.png │ │ └── ScreenshotPlaceholder@2x.png ├── Info.plist ├── NSData+MD5.h ├── NSData+MD5.m ├── RMAddLocaleWindowController.h ├── RMAddLocaleWindowController.m ├── RMAddLocaleWindowController.xib ├── RMAppDataDocument.h ├── RMAppDataDocument.m ├── RMAppDataWindowController.h ├── RMAppDataWindowController.m ├── RMAppDataWindowController.xib ├── RMAppLocale.h ├── RMAppLocale.m ├── RMAppMetaData.h ├── RMAppMetaData.m ├── RMAppScreenshot.h ├── RMAppScreenshot.m ├── RMAppScreenshotTypeValueTransformer.h ├── RMAppScreenshotTypeValueTransformer.m ├── RMAppVersion.h ├── RMAppVersion.m ├── RMByteValueTransformer.h ├── RMByteValueTransformer.m ├── RMConnecterAppDelegate.h ├── RMConnecterAppDelegate.m ├── RMConnecterCredentials+Keychain.h ├── RMConnecterCredentials+Keychain.m ├── RMConnecterCredentials.h ├── RMConnecterCredentials.m ├── RMConnecterOperation.h ├── RMConnecterOperation.m ├── RMConnecterWindowController.h ├── RMConnecterWindowController.m ├── RMOutlineView.h ├── RMOutlineView.m ├── RMOutlineViewController.h ├── RMOutlineViewController.m ├── RMScreenshotViewController.h ├── RMScreenshotViewController.m ├── RMScreenshotViewController.xib ├── RMScreenshotsGroupView.h ├── RMScreenshotsGroupView.m ├── RMXMLObject.h ├── en.lproj │ ├── Credits.rtf │ ├── InfoPlist.strings │ ├── MainMenu.strings │ └── RMConnecterWindow.strings └── main.m ├── ConnecterTests ├── ConnecterTests-Info.plist ├── RMMetaDataModelTests.m ├── RMValueTransformerTests.m ├── TestData │ └── testmetadata.xml └── en.lproj │ └── InfoPlist.strings └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # CocoaPods 23 | Pods -------------------------------------------------------------------------------- /Assets/Icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Assets/Icon.psd -------------------------------------------------------------------------------- /Assets/ScreenshotPlaceholder.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Assets/ScreenshotPlaceholder.psd -------------------------------------------------------------------------------- /Assets/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Assets/screenshots.png -------------------------------------------------------------------------------- /Connecter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Connecter/Base.lproj/RMConnecterWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | me@developer.com 71 | 72 | 73 | 74 | 75 | NSNegateBoolean 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | NSAllRomanInputSourcesLocaleIdentifier 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | NSNegateBoolean 101 | 102 | 103 | 104 | 105 | 106 | 135 | 164 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | Get started by downloading the metadata for an existing app from iTunes Connect. You’ll need to know the SKU in order to download metadata. 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | NSNegateBoolean 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /Connecter/Connecter.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | com.apple.security.temporary-exception.files.home-relative-path.read-write 14 | /.itmstransporter/ 15 | 16 | 17 | -------------------------------------------------------------------------------- /Connecter/ConnectorFileIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Connecter/ConnectorFileIcon.icns -------------------------------------------------------------------------------- /Connecter/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "256x256", 35 | "idiom" : "mac", 36 | "filename" : "Icon256.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "Icon512.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "512x512", 47 | "idiom" : "mac", 48 | "filename" : "Icon512.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "Icon1024.png", 55 | "scale" : "2x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /Connecter/Images.xcassets/AppIcon.appiconset/Icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Connecter/Images.xcassets/AppIcon.appiconset/Icon1024.png -------------------------------------------------------------------------------- /Connecter/Images.xcassets/AppIcon.appiconset/Icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Connecter/Images.xcassets/AppIcon.appiconset/Icon256.png -------------------------------------------------------------------------------- /Connecter/Images.xcassets/AppIcon.appiconset/Icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Connecter/Images.xcassets/AppIcon.appiconset/Icon512.png -------------------------------------------------------------------------------- /Connecter/Images.xcassets/ScreenshotPlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "ScreenshotPlaceholder.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "ScreenshotPlaceholder@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Connecter/Images.xcassets/ScreenshotPlaceholder.imageset/ScreenshotPlaceholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Connecter/Images.xcassets/ScreenshotPlaceholder.imageset/ScreenshotPlaceholder.png -------------------------------------------------------------------------------- /Connecter/Images.xcassets/ScreenshotPlaceholder.imageset/ScreenshotPlaceholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realmacsoftware/RMConnecter/f769d6b619984de3a7b0b99cb7c44202d9c40fea/Connecter/Images.xcassets/ScreenshotPlaceholder.imageset/ScreenshotPlaceholder@2x.png -------------------------------------------------------------------------------- /Connecter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.realmacsoftware.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | ${MACOSX_DEPLOYMENT_TARGET} 25 | NSHumanReadableCopyright 26 | Copyright © 2014 Realmac Software, nxtbgthng GmbH 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | CFBundleDocumentTypes 32 | 33 | 34 | CFBundleTypeExtensions 35 | 36 | itmsp 37 | 38 | CFBundleTypeIconFile 39 | ConnectorFileIcon 40 | CFBundleTypeName 41 | iTunes App Metadata 42 | CFBundleTypeOSTypes 43 | 44 | ???? 45 | 46 | CFBundleTypeRole 47 | Editor 48 | NSDocumentClass 49 | RMAppDataDocument 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Connecter/NSData+MD5.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+MD5.h 3 | // Connecter 4 | // 5 | // Created by Markus on 14.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSData (MD5) 12 | 13 | - (NSString*)md5CheckSum; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Connecter/NSData+MD5.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+MD5.m 3 | // Connecter 4 | // 5 | // Created by Markus on 14.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "NSData+MD5.h" 12 | 13 | @implementation NSData (MD5) 14 | 15 | - (NSString*)md5CheckSum 16 | { 17 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 18 | CC_MD5( self.bytes, (int)self.length, result ); 19 | NSString *format = @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"; 20 | return [NSString stringWithFormat:format, 21 | result[0], result[1], result[2], result[3], 22 | result[4], result[5], result[6], result[7], 23 | result[8], result[9], result[10], result[11], 24 | result[12], result[13], result[14], result[15]]; 25 | } 26 | 27 | @end -------------------------------------------------------------------------------- /Connecter/RMAddLocaleWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMAddLocaleWindowController.h 3 | // Connecter 4 | // 5 | // Created by Markus on 26.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RMAppMetaData; 12 | 13 | @interface RMAddLocaleWindowController : NSWindowController 14 | 15 | @property (nonatomic, weak, readonly) RMAppMetaData *metaData; 16 | @property (nonatomic, readonly) NSArray *filteredLocales; 17 | 18 | - (id)initWithMetaData:(RMAppMetaData*)metaData; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Connecter/RMAddLocaleWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMAddLocaleWindowController.m 3 | // Connecter 4 | // 5 | // Created by Markus on 26.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppVersion.h" 10 | #import "RMAppLocale.h" 11 | 12 | #import "RMAddLocaleWindowController.h" 13 | 14 | NSString *const RMAddLocaleSelectionVersionKeyPath = @"versionsController.selectedObjects"; 15 | 16 | @interface RMAddLocaleWindowController () 17 | @property (nonatomic, strong) IBOutlet NSArrayController *versionsController; 18 | @property (nonatomic, strong) IBOutlet NSArrayController *filterLocalesController; 19 | @property (nonatomic, strong) NSArray *filteredLocales; 20 | @property (nonatomic, readonly) NSArray *allLocales; 21 | @end 22 | 23 | @implementation RMAddLocaleWindowController 24 | 25 | @synthesize allLocales = _allLocales; 26 | 27 | - (id)init 28 | { 29 | return [super initWithWindowNibName:NSStringFromClass([self class]) owner:self]; 30 | } 31 | 32 | - (id)initWithMetaData:(RMAppMetaData *)metaData; 33 | { 34 | self = [self init]; 35 | if (self) { 36 | _metaData = metaData; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)dealloc 42 | { 43 | [self removeObserver:self forKeyPath:RMAddLocaleSelectionVersionKeyPath]; 44 | } 45 | 46 | - (void)windowDidLoad; 47 | { 48 | [super windowDidLoad]; 49 | [self addObserver:self forKeyPath:RMAddLocaleSelectionVersionKeyPath 50 | options:NSKeyValueObservingOptionInitial context:nil]; 51 | } 52 | 53 | #pragma mark actions 54 | 55 | - (IBAction)cancel:(id)sender; 56 | { 57 | [self.window.sheetParent endSheet:self.window 58 | returnCode:NSModalResponseCancel]; 59 | } 60 | 61 | - (IBAction)addLocale:(id)sender; 62 | { 63 | RMAppVersion *selectedVersion = [[self.versionsController selectedObjects] firstObject]; 64 | RMAppLocale *selectedLocale = [[self.filterLocalesController selectedObjects] firstObject]; 65 | if ([selectedLocale isKindOfClass:[RMAppLocale class]]) { 66 | [selectedVersion addLocale:selectedLocale]; 67 | } 68 | 69 | [self.window.sheetParent endSheet:self.window 70 | returnCode:NSModalResponseOK]; 71 | } 72 | 73 | #pragma mark locales logic 74 | 75 | - (NSArray *)allLocales; 76 | { 77 | if (!_allLocales) { 78 | // based on App Metadata Specification 5.1 Revision 1 79 | NSArray *allLocales = @[@"cmn-Hans",@"cmn-Hant",@"da",@"nl",@"en-AU", 80 | @"en-CA",@"en-GB",@"en-US",@"fi",@"fr-CA", 81 | @"fr-FR",@"de-DE",@"el",@"id",@"it",@"ja",@"ko", 82 | @"ms",@"no",@"pt-BR",@"pt-PT",@"ru",@"es-MX", 83 | @"es-ES",@"sv",@"th",@"tr",@"vi"]; 84 | _allLocales = [allLocales sortedArrayUsingSelector:@selector(compare:)]; 85 | } 86 | return _allLocales; 87 | } 88 | 89 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; 90 | { 91 | if (object != self || ![keyPath isEqualToString:RMAddLocaleSelectionVersionKeyPath]) return; 92 | 93 | NSMutableArray *locales = [[self allLocales] mutableCopy]; 94 | if ([self.versionsController.selectedObjects count] > 0) { 95 | RMAppVersion *version = [[self.versionsController selectedObjects] firstObject]; 96 | for (RMAppLocale *locale in version.activeLocales) { 97 | [locales removeObject:locale.localeName]; 98 | [locales removeObject:[locale.localeName substringToIndex:2]]; 99 | } 100 | } 101 | 102 | NSMutableArray *localeObjects = [NSMutableArray array]; 103 | [locales enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 104 | RMAppLocale *locale = [[RMAppLocale alloc] init]; 105 | locale.localeName = obj; 106 | [localeObjects addObject:locale]; 107 | }]; 108 | 109 | self.filteredLocales = [localeObjects copy]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /Connecter/RMAppDataDocument.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMDocumentWindowController.h 3 | // Connecter 4 | // 5 | // Created by Markus on 14.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RMAppMetaData; 12 | 13 | @interface RMAppDataDocument : NSDocument 14 | 15 | @property (nonatomic, readonly) RMAppMetaData *metaData; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Connecter/RMAppDataDocument.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMDocumentWindowController.m 3 | // Connecter 4 | // 5 | // Created by Markus on 14.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | // Controller 10 | #import "RMAppDataWindowController.h" 11 | 12 | // Model 13 | #import "RMAppScreenshot.h" 14 | #import "RMAppMetaData.h" 15 | #import "RMAppVersion.h" 16 | #import "RMAppLocale.h" 17 | 18 | #import "RMAppDataDocument.h" 19 | 20 | NSString *const RMAppDataErrorDomain = @"RMAppDataErrorDomain"; 21 | 22 | @interface RMAppDataDocument () 23 | @property (nonatomic, strong) RMAppMetaData *metaData; 24 | @end 25 | 26 | @implementation RMAppDataDocument 27 | 28 | - (id)initWithType:(NSString *)typeName error:(NSError *__autoreleasing *)outError 29 | { 30 | self = [super initWithType:typeName error:outError]; 31 | if (self) { 32 | if (!self.metaData) { 33 | self.metaData = [[RMAppMetaData alloc] init]; 34 | } 35 | } 36 | return self; 37 | } 38 | 39 | #pragma mark create window controller 40 | 41 | - (void)makeWindowControllers; 42 | { 43 | [self addWindowController:[[RMAppDataWindowController alloc] init]]; 44 | } 45 | 46 | #pragma mark reading/saving the document 47 | 48 | - (NSString*)xmlFileName; 49 | { 50 | return @"metadata.xml"; 51 | } 52 | 53 | + (BOOL)autosavesInPlace 54 | { 55 | return YES; 56 | } 57 | 58 | - (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError **)outError; 59 | { 60 | NSData *xmlData = [[self.metaData xmlDocumentRepresentation] XMLDataWithOptions:NSXMLNodePrettyPrint]; 61 | NSFileWrapper *xmlWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:xmlData]; 62 | NSFileWrapper *folderWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:@{[self xmlFileName]:xmlWrapper}]; 63 | 64 | // save screenshots 65 | for (RMAppVersion *version in self.metaData.versions) { 66 | for (RMAppLocale *locale in version.locales) { 67 | for (RMAppScreenshot* screenshot in locale.screenshots) { 68 | if (screenshot.imageData) { 69 | NSFileWrapper *filewrapper = [[NSFileWrapper alloc] initRegularFileWithContents:screenshot.imageData]; 70 | filewrapper.preferredFilename = screenshot.filename; 71 | [folderWrapper addFileWrapper:filewrapper]; 72 | } 73 | } 74 | } 75 | } 76 | 77 | return folderWrapper; 78 | } 79 | 80 | - (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError; 81 | { 82 | NSDictionary *fileWrappers = [fileWrapper fileWrappers]; 83 | 84 | // parse xml file 85 | NSData *xmlData = [fileWrappers[[self xmlFileName]] regularFileContents]; 86 | if (xmlData) { 87 | NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:xmlData options:0 error:outError]; 88 | self.metaData = [[RMAppMetaData alloc] initWithXMLElement:document.rootElement]; 89 | 90 | // read screenshots 91 | for (RMAppVersion *version in self.metaData.versions) { 92 | for (RMAppLocale *locale in version.locales) { 93 | for (RMAppScreenshot* screenshot in locale.screenshots) { 94 | NSFileWrapper *fileWrapper = fileWrappers[screenshot.filename]; 95 | if (fileWrapper) { 96 | screenshot.imageData = [fileWrapper regularFileContents]; 97 | } 98 | } 99 | } 100 | } 101 | 102 | return YES; 103 | } 104 | 105 | NSString *errorMessage = [NSString stringWithFormat:@"Could not read xml document named %@", [self xmlFileName]]; 106 | *outError = [NSError errorWithDomain:RMAppDataErrorDomain code:0 107 | userInfo:@{NSLocalizedFailureReasonErrorKey:errorMessage}]; 108 | return NO; 109 | } 110 | 111 | @end 112 | 113 | -------------------------------------------------------------------------------- /Connecter/RMAppDataWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppDataWindowController.h 3 | // Connecter 4 | // 5 | // Created by Markus on 28.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RMAppDataDocument; 12 | 13 | @interface RMAppDataWindowController : NSWindowController 14 | 15 | @property (nonatomic, readonly) RMAppDataDocument *rmDocument; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Connecter/RMAppDataWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppDataWindowController.m 3 | // Connecter 4 | // 5 | // Created by Markus on 28.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | // Controller 10 | #import "RMAddLocaleWindowController.h" 11 | #import "RMOutlineViewController.h" 12 | 13 | // Views 14 | #import "RMScreenshotsGroupView.h" 15 | #import "RMOutlineView.h" 16 | 17 | // Model 18 | #import "RMAppDataDocument.h" 19 | #import "RMAppScreenshot.h" 20 | #import "RMAppMetaData.h" 21 | #import "RMAppVersion.h" 22 | #import "RMAppLocale.h" 23 | 24 | #import "RMAppDataWindowController.h" 25 | 26 | NSString *const RMAppDataArrangedObjectsKVOPath = @"arrangedObjects"; 27 | 28 | @interface RMAppDataWindowController () 29 | 30 | @property (nonatomic, strong) RMOutlineViewController *outlineController; 31 | @property (nonatomic, strong) RMAddLocaleWindowController *addLocaleWindowController; 32 | 33 | @property (nonatomic, strong) IBOutlet NSArrayController *versionsController; 34 | @property (nonatomic, strong) IBOutlet NSArrayController *localesController; 35 | @property (nonatomic, strong) IBOutlet NSArrayController *screenshotsController; 36 | @property (nonatomic, weak) IBOutlet RMScreenshotsGroupView *screenshotsView; 37 | @property (nonatomic, weak) IBOutlet RMOutlineView *outlineView; 38 | @property (nonatomic, weak) IBOutlet NSTabView *tabView; 39 | 40 | @end 41 | 42 | @implementation RMAppDataWindowController 43 | 44 | - (id)init 45 | { 46 | return [super initWithWindowNibName:NSStringFromClass([self class])]; 47 | } 48 | 49 | - (void)windowDidLoad 50 | { 51 | [super windowDidLoad]; 52 | 53 | // setup outlineController / outlineView 54 | self.outlineController = [[RMOutlineViewController alloc] init]; 55 | self.outlineController.versionsController = self.versionsController; 56 | self.outlineController.localesController = self.localesController; 57 | self.outlineView.dataSource = self.outlineController; 58 | self.outlineView.delegate = self.outlineController; 59 | [self.outlineView expandItem:nil expandChildren:YES]; 60 | 61 | // add new locales action 62 | __weak typeof(self) blockSelf = self; 63 | self.outlineController.addLocaleBlock = ^(NSButton *sender){ 64 | RMAddLocaleWindowController *addLocaleController = [[RMAddLocaleWindowController alloc] initWithMetaData:blockSelf.rmDocument.metaData]; 65 | blockSelf.addLocaleWindowController = addLocaleController; 66 | [sender.window beginSheet:addLocaleController.window 67 | completionHandler:^(NSModalResponse returnCode) { 68 | if (returnCode == NSModalResponseOK) { 69 | [blockSelf.outlineView reloadData]; 70 | } 71 | [addLocaleController.window orderOut:nil]; 72 | blockSelf.addLocaleWindowController = nil; 73 | }]; 74 | }; 75 | 76 | // delete locales action 77 | self.outlineView.deleteItemBlock = ^(id item){ 78 | if ([item isKindOfClass:[RMAppLocale class]]) { 79 | [blockSelf showDeleteAlertWithConfirmedBlock:^(){ 80 | RMAppLocale *locale = item; 81 | locale.shouldDeleteLocale = YES; 82 | [blockSelf.outlineView reloadData]; 83 | }]; 84 | } 85 | }; 86 | 87 | // setup screenshots view 88 | self.screenshotsView.delegate = self; 89 | [self.screenshotsController addObserver:self forKeyPath:RMAppDataArrangedObjectsKVOPath options:NSKeyValueObservingOptionInitial context:nil]; 90 | } 91 | 92 | - (void)setDocument:(NSDocument *)document; 93 | { 94 | [super setDocument:document]; 95 | if (!document) { 96 | [self.screenshotsController removeObserver:self forKeyPath:RMAppDataArrangedObjectsKVOPath]; 97 | } 98 | } 99 | 100 | - (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName; 101 | { 102 | return [NSString stringWithFormat: @"%@ - Connecter", displayName]; 103 | } 104 | 105 | #pragma mark NSAlert helper 106 | 107 | - (void)showDeleteAlertWithConfirmedBlock:(void(^)(void))confirmedBlock; 108 | { 109 | if (!confirmedBlock) return; 110 | 111 | NSAlert *alert = [[NSAlert alloc] init]; 112 | [alert addButtonWithTitle:@"Delete"]; 113 | [alert addButtonWithTitle:@"Cancel"]; 114 | [alert setMessageText:@"Delete Locale?"]; 115 | [alert setInformativeText:@"All attached data will be deleted, including any screenshot. This can't be restored."]; 116 | 117 | [alert beginSheetModalForWindow:[self.outlineView window] completionHandler:^ (NSModalResponse returnCode) { 118 | if (returnCode == NSAlertFirstButtonReturn) { 119 | confirmedBlock(); 120 | } 121 | }]; 122 | } 123 | 124 | #pragma mark KVO / NSTabViewDelegate 125 | 126 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 127 | change:(NSDictionary *)change context:(void *)context; 128 | { 129 | 130 | if ((object == self.screenshotsController && [keyPath isEqualToString:RMAppDataArrangedObjectsKVOPath])) { 131 | [self updateScreenshots]; 132 | } 133 | } 134 | 135 | - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem; 136 | { 137 | [self updateScreenshots]; 138 | } 139 | 140 | - (void)updateScreenshots; 141 | { 142 | RMAppScreenshotType type = (RMAppScreenshotType)[self.tabView.selectedTabViewItem.identifier integerValue]; 143 | NSArray *currentScreenshots = [self.screenshotsController.arrangedObjects 144 | filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"displayTarget == %d", type]]; 145 | self.screenshotsView.screenshots = currentScreenshots; 146 | } 147 | 148 | #pragma mark RMScreenshotsGroupViewDelegate 149 | 150 | - (void)screenshotsGroupViewDidUpdateScreenshots:(RMScreenshotsGroupView*)controller; 151 | { 152 | RMAppVersion *activeVersion = [self.versionsController.selectedObjects firstObject]; 153 | RMAppLocale *activeLocale = [self.localesController.selectedObjects firstObject]; 154 | 155 | // update screenshot models with correct displayTarget & update filenames 156 | RMAppScreenshotType currentDisplayTarget = (RMAppScreenshotType)[self.tabView.selectedTabViewItem.identifier integerValue]; 157 | for (RMAppScreenshot *screenshot in controller.screenshots) { 158 | screenshot.displayTarget = currentDisplayTarget; 159 | 160 | if (screenshot.imageData != nil && [screenshot.filename hasPrefix:activeLocale.localeName] == NO) { 161 | NSString *versionString = [activeVersion.versionString stringByReplacingOccurrencesOfString:@"." withString:@""]; 162 | versionString = [versionString stringByReplacingOccurrencesOfString:@"-" withString:@""]; 163 | versionString = [versionString stringByReplacingOccurrencesOfString:@"_" withString:@""]; 164 | screenshot.filename = [NSString stringWithFormat: @"%@%@%d%d.png", 165 | activeLocale.localeName, 166 | versionString, 167 | (int)screenshot.displayTarget, 168 | (int)screenshot.position]; 169 | } 170 | } 171 | 172 | // update model with new screenshots for current displayTarget 173 | NSArray *filteredScreenshots = [self.screenshotsController.arrangedObjects 174 | filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"displayTarget != %d", currentDisplayTarget]]; 175 | activeLocale.screenshots = [filteredScreenshots arrayByAddingObjectsFromArray:controller.screenshots]; 176 | } 177 | 178 | #pragma mark helper 179 | 180 | - (RMAppDataDocument*)rmDocument; 181 | { 182 | return self.document; 183 | } 184 | 185 | @end 186 | -------------------------------------------------------------------------------- /Connecter/RMAppDataWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Title 53 | Title 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Keywords 79 | Keywords 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | Support URL 194 | Support URL 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | Marketing URL 220 | Marketing URL 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | Privacy Policy URL 246 | Privacy Policy URL 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 356 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /Connecter/RMAppLocale.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppLocale.h 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMXMLObject.h" 10 | 11 | #import 12 | 13 | @interface RMAppLocale : NSObject 14 | 15 | @property (nonatomic, assign) BOOL shouldDeleteLocale; 16 | 17 | @property (nonatomic, copy) NSString *localeName; 18 | @property (nonatomic, copy) NSString *title; 19 | @property (nonatomic, copy) NSString *appDescription; 20 | @property (nonatomic, copy) NSString *whatsNew; 21 | @property (nonatomic, copy) NSString *softwareURL; 22 | @property (nonatomic, copy) NSString *supportURL; 23 | @property (nonatomic, copy) NSString *privacyURL; 24 | 25 | @property (nonatomic, strong) NSArray *keywords; 26 | @property (nonatomic, strong) NSArray *screenshots; 27 | 28 | @property (nonatomic, readonly) NSString *formattedLocaleNameShort; 29 | @property (nonatomic, readonly) NSString *formattedLocaleNameFull; 30 | 31 | @end 32 | 33 | -------------------------------------------------------------------------------- /Connecter/RMAppLocale.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppLocale.m 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppScreenshot.h" 10 | 11 | #import "RMAppLocale.h" 12 | 13 | @implementation RMAppLocale 14 | 15 | - (id)init 16 | { 17 | self = [super init]; 18 | if (self) { 19 | self.localeName = @"en-US"; 20 | } 21 | return self; 22 | } 23 | 24 | - (id)initWithXMLElement:(NSXMLElement *)xmlElement; 25 | { 26 | self = [super init]; 27 | if (self) { 28 | self.shouldDeleteLocale = NO; 29 | 30 | if ([[xmlElement name] isEqualToString:@"locale"]) { 31 | self.localeName = [[xmlElement attributeForName:@"name"] stringValue]; 32 | self.shouldDeleteLocale = [[[xmlElement attributeForName:@"remove"] stringValue] boolValue]; 33 | 34 | self.title = [[[xmlElement elementsForName:@"title"] firstObject] stringValue]; 35 | self.appDescription = [[[xmlElement elementsForName:@"description"] firstObject] stringValue]; 36 | self.whatsNew = [[[xmlElement elementsForName:@"version_whats_new"] firstObject] stringValue]; 37 | self.softwareURL = [[[xmlElement elementsForName:@"software_url"] firstObject] stringValue]; 38 | self.supportURL = [[[xmlElement elementsForName:@"support_url"] firstObject] stringValue]; 39 | self.privacyURL = [[[xmlElement elementsForName:@"privacy_url"] firstObject] stringValue]; 40 | 41 | // keywords 42 | NSMutableArray *keywordsArray = [NSMutableArray array]; 43 | NSXMLElement *keywordElements = [[xmlElement elementsForName:@"keywords"] firstObject]; 44 | for (NSXMLElement *element in [keywordElements children]) { 45 | [keywordsArray addObject:[element stringValue]]; 46 | } 47 | self.keywords = [keywordsArray copy]; 48 | 49 | // screenshots 50 | NSMutableArray *screenshots = [NSMutableArray array]; 51 | NSXMLElement *screenshotsElement = [[xmlElement elementsForName:@"software_screenshots"] firstObject]; 52 | NSArray *screenshotElements = [screenshotsElement children]; 53 | for (NSXMLElement *screenshotElement in screenshotElements) { 54 | [screenshots addObject:[[RMAppScreenshot alloc] initWithXMLElement:screenshotElement]]; 55 | } 56 | self.screenshots = screenshots; 57 | } 58 | } 59 | return self; 60 | } 61 | 62 | - (NSXMLElement *)xmlRepresentation; 63 | { 64 | NSXMLElement *locale = [NSXMLElement elementWithName:@"locale"]; 65 | 66 | if (self.shouldDeleteLocale) { 67 | [locale setAttributesWithDictionary:@{@"name":self.localeName, 68 | @"remove":@"true"}]; 69 | } else { 70 | [locale setAttributesWithDictionary:@{@"name":self.localeName}]; 71 | 72 | [locale addChild:[NSXMLElement elementWithName:@"title" 73 | stringValue:self.title]]; 74 | 75 | [locale addChild:[NSXMLElement elementWithName:@"description" 76 | stringValue:self.appDescription]]; 77 | 78 | [locale addChild:[NSXMLElement elementWithName:@"version_whats_new" 79 | stringValue:self.whatsNew]]; 80 | 81 | [locale addChild:[NSXMLElement elementWithName:@"software_url" 82 | stringValue:self.softwareURL]]; 83 | 84 | [locale addChild:[NSXMLElement elementWithName:@"support_url" 85 | stringValue:self.supportURL]]; 86 | 87 | [locale addChild:[NSXMLElement elementWithName:@"privacy_url" 88 | stringValue:self.privacyURL]]; 89 | 90 | // keywords 91 | NSXMLElement *keywords = [NSXMLElement elementWithName:@"keywords"]; 92 | NSArray *keywordStrings = self.keywords; 93 | for (NSString *keywordString in keywordStrings) { 94 | [keywords addChild:[NSXMLElement elementWithName:@"keyword" 95 | stringValue:keywordString]]; 96 | } 97 | [locale addChild:keywords]; 98 | 99 | // screenshots 100 | NSXMLElement *screenshots = [NSXMLElement elementWithName:@"software_screenshots"]; 101 | for (RMAppLocale *screenshot in self.screenshots) { 102 | NSXMLElement *screenshotElement = [screenshot xmlRepresentation]; 103 | [screenshots addChild:screenshotElement]; 104 | } 105 | [locale addChild:screenshots]; 106 | } 107 | 108 | return locale; 109 | } 110 | 111 | #pragma mark Locale Formatting 112 | 113 | - (NSString *)formattedLocaleNameShort; 114 | { 115 | NSLocale *locale = [NSLocale currentLocale]; 116 | NSString *language = [locale displayNameForKey:NSLocaleLanguageCode 117 | value:[self mappedLocaleNameWithLocaleName:self.localeName]]; 118 | return [NSString stringWithFormat: @"%@ (%@)", language, self.localeName]; 119 | } 120 | 121 | - (NSString *)formattedLocaleNameFull; 122 | { 123 | NSLocale *locale = [NSLocale currentLocale]; 124 | NSString *language = [locale displayNameForKey:NSLocaleIdentifier 125 | value:[self mappedLocaleNameWithLocaleName:self.localeName]]; 126 | NSMutableString *spacer = [NSMutableString string]; 127 | for (NSInteger i=0; i<(10-self.localeName.length); i+=3) [spacer appendString:@"\t"]; 128 | return [NSString stringWithFormat: @"%@%@%@", self.localeName, spacer, language]; 129 | } 130 | 131 | - (NSString*)mappedLocaleNameWithLocaleName:(NSString*)localeName; 132 | { 133 | // iTunes Connect uses RFC 5646 naming, but OS X uses ISO 639-1/ISO 639-2 and ISO 3166-1 naming 134 | NSDictionary *iosMapping = @{@"cmn-Hans": @"zh-Hans",@"cmn-Hant":@"zh-Hant"}; 135 | if (iosMapping[localeName]) return iosMapping[localeName]; 136 | return localeName; 137 | } 138 | 139 | @end 140 | 141 | -------------------------------------------------------------------------------- /Connecter/RMAppMetaData.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppMetaData.h 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMXMLObject.h" 10 | #import 11 | 12 | @interface RMAppMetaData : NSObject 13 | 14 | @property (nonatomic, strong) NSString *metadataToken; 15 | @property (nonatomic, strong) NSString *provider; 16 | @property (nonatomic, strong) NSString *teamID; 17 | @property (nonatomic, strong) NSString *vendorID; 18 | 19 | @property (nonatomic, strong) NSArray *versions; 20 | 21 | - (NSXMLDocument*)xmlDocumentRepresentation; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Connecter/RMAppMetaData.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppMetaData.m 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppVersion.h" 10 | 11 | #import "RMAppMetaData.h" 12 | 13 | NSString *const RMAppMetaDataXMLNS = @"http://apple.com/itunes/importer"; 14 | NSString *const RMAppMetaDataVersion = @"software5.1"; 15 | 16 | @interface RMAppMetaData () 17 | @property (nonatomic, strong) NSXMLElement *readOnlyNode; 18 | @property (nonatomic, strong) NSArray *unhandledMetaDataXMLNodes; 19 | @end 20 | 21 | @implementation RMAppMetaData 22 | 23 | - (id)init 24 | { 25 | self = [super init]; 26 | if (self) { 27 | self.versions = @[[[RMAppVersion alloc] init]]; 28 | } 29 | return self; 30 | } 31 | 32 | - (id)initWithXMLElement:(NSXMLElement*)xmlElement; 33 | { 34 | self = [super init]; 35 | if (self) { 36 | if ([[xmlElement name] isEqualToString:@"package"]) { 37 | NSXMLElement *software = [[xmlElement elementsForName:@"software"] firstObject]; 38 | NSXMLElement *metaData = [[software elementsForName:@"software_metadata"] firstObject]; 39 | NSXMLElement *versions = [[metaData elementsForName:@"versions"] firstObject]; 40 | 41 | NSArray *versionElements = [versions children]; 42 | NSMutableArray *versionsArray = [NSMutableArray array]; 43 | for (NSXMLElement *version in versionElements) { 44 | [versionsArray addObject:[[RMAppVersion alloc] initWithXMLElement:version]]; 45 | } 46 | self.versions = versionsArray; 47 | self.metadataToken = [[[xmlElement elementsForName:@"metadata_token"] firstObject] objectValue]; 48 | self.provider = [[[xmlElement elementsForName:@"provider"] firstObject] objectValue]; 49 | self.teamID = [[[xmlElement elementsForName:@"team_id"] firstObject] objectValue]; 50 | self.vendorID = [[[software elementsForName:@"vendor_id"] firstObject] objectValue]; 51 | 52 | self.readOnlyNode = [[software elementsForName:@"read_only_info"] firstObject]; 53 | self.unhandledMetaDataXMLNodes = [metaData.children filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"name != 'versions'"]]; 54 | 55 | } 56 | } 57 | return self; 58 | } 59 | 60 | - (NSXMLElement *)xmlRepresentation; 61 | { 62 | NSXMLElement *root = [NSXMLElement elementWithName:@"package"]; 63 | [root setAttributesWithDictionary:@{@"xmlns":RMAppMetaDataXMLNS, 64 | @"version":RMAppMetaDataVersion}]; 65 | 66 | [root addChild:[NSXMLElement elementWithName:@"metadata_token" stringValue:self.metadataToken]]; 67 | [root addChild:[NSXMLElement elementWithName:@"provider" stringValue:self.provider]]; 68 | [root addChild:[NSXMLElement elementWithName:@"team_id" stringValue:self.teamID]]; 69 | 70 | NSXMLElement *software = [NSXMLElement elementWithName:@"software"]; 71 | NSXMLElement *vendor = [NSXMLElement elementWithName:@"vendor_id" stringValue:self.vendorID]; 72 | NSXMLElement *metadata = [NSXMLElement elementWithName:@"software_metadata"]; 73 | [root addChild:software]; 74 | [software addChild:vendor]; 75 | [software addChild:self.readOnlyNode]; 76 | [software addChild:metadata]; 77 | 78 | // versions 79 | NSXMLElement *versions = [NSXMLElement elementWithName:@"versions"]; 80 | for (RMAppVersion *version in self.versions) { 81 | NSXMLElement *versionElement = [version xmlRepresentation]; 82 | [versions addChild:versionElement]; 83 | } 84 | [metadata addChild:versions]; 85 | 86 | // save unhandled data 87 | for (NSXMLElement *element in self.unhandledMetaDataXMLNodes) { 88 | [metadata addChild:element]; 89 | } 90 | 91 | return root; 92 | } 93 | 94 | - (NSXMLDocument *)xmlDocumentRepresentation; 95 | { 96 | NSXMLDocument *document = [[NSXMLDocument alloc] initWithRootElement:self.xmlRepresentation]; 97 | [document setCharacterEncoding:@"UTF-8"]; 98 | [document setVersion:@"1.0"]; 99 | [document setStandalone:YES]; 100 | [document setMIMEType:@"text/xml"]; 101 | return document; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Connecter/RMAppScreenshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppScreenshot.h 3 | // Connecter 4 | // 5 | // Created by Markus on 19.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppScreenshotTypeValueTransformer.h" 10 | #import "RMXMLObject.h" 11 | 12 | #import 13 | 14 | @interface RMAppScreenshot : NSObject 15 | 16 | @property (nonatomic, assign) RMAppScreenshotType displayTarget; 17 | @property (nonatomic, assign) int position; 18 | 19 | @property (nonatomic, strong) NSData *imageData; 20 | 21 | @property (nonatomic, strong) NSString *filename; 22 | @property (nonatomic, strong) NSString *checksum; 23 | @property (nonatomic, strong) NSString *checksumType; 24 | @property (nonatomic, assign) int size; 25 | 26 | @end 27 | 28 | -------------------------------------------------------------------------------- /Connecter/RMAppScreenshot.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppScreenshot.m 3 | // Connecter 4 | // 5 | // Created by Markus on 19.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "NSData+MD5.h" 10 | 11 | #import "RMAppScreenshot.h" 12 | 13 | NSString *const RMAppScreenshotChecksumTypeStringMD5 = @"md5"; 14 | 15 | @implementation RMAppScreenshot 16 | 17 | + (void)initialize; 18 | { 19 | [super initialize]; 20 | [NSValueTransformer setValueTransformer:[[RMAppScreenshotTypeValueTransformer alloc] init] 21 | forName:RMAppScreenshotTypeValueTransformerName]; 22 | } 23 | 24 | #pragma mark RMXMLObject 25 | 26 | - (id)initWithXMLElement:(NSXMLElement *)xmlElement 27 | { 28 | self = [super init]; 29 | if (self) { 30 | if ([[xmlElement name] isEqualToString:@"software_screenshot"]) { 31 | NSString *typeString = [[xmlElement attributeForName:@"display_target"] objectValue]; 32 | self.displayTarget = [[[self displayTargetValueTransformer] transformedValue:typeString] integerValue]; 33 | 34 | self.position = [[[xmlElement attributeForName:@"position"] objectValue] integerValue]; 35 | self.filename = [[[xmlElement elementsForName:@"file_name"] firstObject] objectValue]; 36 | self.size = [[[[xmlElement elementsForName:@"size"] firstObject] objectValue] integerValue]; 37 | 38 | NSXMLElement *checksumElement = [[xmlElement elementsForName:@"checksum"] firstObject]; 39 | self.checksum = [checksumElement objectValue]; 40 | self.checksumType = [[checksumElement attributeForName:@"type"] objectValue]; 41 | } 42 | } 43 | return self; 44 | } 45 | 46 | - (NSXMLElement *)xmlRepresentation; 47 | { 48 | NSXMLElement *screenshot = [NSXMLElement elementWithName:@"software_screenshot"]; 49 | 50 | NSString *typeString = [[self displayTargetValueTransformer] reverseTransformedValue:@(self.displayTarget)]; 51 | [screenshot setAttributesWithDictionary:@{@"display_target":typeString, 52 | @"position":[NSString stringWithFormat:@"%d", self.position]}]; 53 | 54 | [screenshot addChild:[NSXMLElement elementWithName:@"file_name" 55 | stringValue:self.filename]]; 56 | [screenshot addChild:[NSXMLElement elementWithName:@"size" 57 | stringValue:[NSString stringWithFormat: @"%d", self.size]]]; 58 | 59 | NSXMLElement *checksum = [NSXMLElement elementWithName:@"checksum" 60 | stringValue:self.checksum]; 61 | [checksum setAttributesWithDictionary:@{@"type":self.checksumType}]; 62 | [screenshot addChild:checksum]; 63 | 64 | return screenshot; 65 | } 66 | 67 | #pragma mark RMAppScreenshot 68 | 69 | - (void)setImageData:(NSData *)imageData; 70 | { 71 | if (imageData == _imageData) return; 72 | _imageData = imageData; 73 | 74 | self.checksum = [imageData md5CheckSum]; 75 | self.checksumType = RMAppScreenshotChecksumTypeStringMD5; 76 | self.size = [imageData length]; 77 | } 78 | 79 | - (NSString *)description; 80 | { 81 | return [NSString stringWithFormat: @"%@ - %@, %d., %@, %d", 82 | [super description], 83 | [[self displayTargetValueTransformer] reverseTransformedValue:@(self.displayTarget)], 84 | self.position, self.filename, self.size]; 85 | } 86 | 87 | - (RMAppScreenshotTypeValueTransformer*)displayTargetValueTransformer; 88 | { 89 | return (RMAppScreenshotTypeValueTransformer*)[NSValueTransformer valueTransformerForName:RMAppScreenshotTypeValueTransformerName]; 90 | } 91 | 92 | @end 93 | 94 | -------------------------------------------------------------------------------- /Connecter/RMAppScreenshotTypeValueTransformer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppScreenshotTypeValueTransformer.h 3 | // Connecter 4 | // 5 | // Created by Markus on 21.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, RMAppScreenshotType) { 12 | RMAppScreenshotTypeiPhone35inch, 13 | RMAppScreenshotTypeiPhone4inch, 14 | RMAppScreenshotTypeiPhone47inch, 15 | RMAppScreenshotTypeiPhone55inch, 16 | RMAppScreenshotTypeiPad, 17 | RMAppScreenshotTypeMac 18 | }; 19 | 20 | extern NSString *const RMAppScreenshotTypeValueTransformerName; 21 | 22 | @interface RMAppScreenshotTypeValueTransformer : NSValueTransformer 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Connecter/RMAppScreenshotTypeValueTransformer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppScreenshotTypeValueTransformer.m 3 | // Connecter 4 | // 5 | // Created by Markus on 21.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppScreenshotTypeValueTransformer.h" 10 | 11 | 12 | NSString *const RMAppScreenshotTypeValueTransformerName = @"RMAppScreenshotTypeValueTransformerName"; 13 | 14 | NSString *const RMAppScreenshotTypeStringIphone35inch = @"iOS-3.5-in"; 15 | NSString *const RMAppScreenshotTypeStringIphone4inch = @"iOS-4-in"; 16 | NSString *const RMAppScreenshotTypeStringIphone47inch = @"iOS-4.7-in"; 17 | NSString *const RMAppScreenshotTypeStringIphone55inch = @"iOS-5.5-in"; 18 | NSString *const RMAppScreenshotTypeStringIpad = @"iOS-iPad"; 19 | NSString *const RMAppScreenshotTypeStringMac = @"Mac"; 20 | 21 | @implementation RMAppScreenshotTypeValueTransformer 22 | 23 | - (id)transformedValue:(id)value; 24 | { 25 | if ([value isKindOfClass:[NSString class]]) { 26 | NSString *string = (NSString*)value; 27 | if ([string isEqualToString:RMAppScreenshotTypeStringIphone35inch]) { 28 | return @(RMAppScreenshotTypeiPhone35inch); 29 | } else if ([string isEqualToString:RMAppScreenshotTypeStringIphone4inch]) { 30 | return @(RMAppScreenshotTypeiPhone4inch); 31 | } else if ([string isEqualToString:RMAppScreenshotTypeStringIphone47inch]) { 32 | return @(RMAppScreenshotTypeiPhone47inch); 33 | } else if ([string isEqualToString:RMAppScreenshotTypeStringIphone55inch]) { 34 | return @(RMAppScreenshotTypeiPhone55inch); 35 | } else if ([string isEqualToString:RMAppScreenshotTypeStringIpad]) { 36 | return @(RMAppScreenshotTypeiPad); 37 | } else if ([string isEqualToString:RMAppScreenshotTypeStringMac]) { 38 | return @(RMAppScreenshotTypeMac); 39 | } 40 | } 41 | return nil; 42 | } 43 | 44 | - (id)reverseTransformedValue:(id)value; 45 | { 46 | if ([value isKindOfClass:[NSNumber class]]) { 47 | RMAppScreenshotType type = [value integerValue]; 48 | if (type == RMAppScreenshotTypeiPhone35inch) { 49 | return RMAppScreenshotTypeStringIphone35inch; 50 | } else if (type == RMAppScreenshotTypeiPhone4inch) { 51 | return RMAppScreenshotTypeStringIphone4inch; 52 | } else if (type == RMAppScreenshotTypeiPhone47inch) { 53 | return RMAppScreenshotTypeStringIphone47inch; 54 | } else if (type == RMAppScreenshotTypeiPhone55inch) { 55 | return RMAppScreenshotTypeStringIphone55inch; 56 | } else if (type == RMAppScreenshotTypeiPad) { 57 | return RMAppScreenshotTypeStringIpad; 58 | } else if (type == RMAppScreenshotTypeMac) { 59 | return RMAppScreenshotTypeStringMac; 60 | } 61 | } 62 | return nil; 63 | } 64 | 65 | + (BOOL)allowsReverseTransformation; 66 | { 67 | return YES; 68 | } 69 | 70 | + (Class)transformedValueClass; 71 | { 72 | return [NSNumber class]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /Connecter/RMAppVersion.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppVersion.h 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMXMLObject.h" 10 | #import 11 | 12 | @class RMAppLocale; 13 | 14 | @interface RMAppVersion : NSObject 15 | 16 | @property (nonatomic, copy) NSString *versionString; 17 | @property (nonatomic, strong) NSArray *locales; 18 | @property (nonatomic, readonly) NSArray *activeLocales; 19 | 20 | - (void)addLocale:(RMAppLocale*)locale; 21 | 22 | @end 23 | 24 | -------------------------------------------------------------------------------- /Connecter/RMAppVersion.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMAppVersion.m 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppLocale.h" 10 | 11 | #import "RMAppVersion.h" 12 | 13 | @implementation RMAppVersion 14 | 15 | - (id)init 16 | { 17 | self = [super init]; 18 | if (self) { 19 | self.versionString = @"1.0"; 20 | self.locales = @[[[RMAppLocale alloc] init]]; 21 | } 22 | return self; 23 | } 24 | 25 | - (id)initWithXMLElement:(NSXMLElement*)xmlElement; 26 | { 27 | self = [super init]; 28 | if (self) { 29 | if ([[xmlElement name] isEqualToString:@"version"]) { 30 | self.versionString = [[xmlElement attributeForName:@"string"] stringValue]; 31 | 32 | NSMutableArray *locales = [NSMutableArray array]; 33 | NSXMLElement *localesElement = [[xmlElement elementsForName:@"locales"] firstObject]; 34 | NSArray *localeElements = [localesElement children]; 35 | for (NSXMLElement *localeElement in localeElements) { 36 | [locales addObject:[[RMAppLocale alloc] initWithXMLElement:localeElement]]; 37 | } 38 | self.locales = [locales copy]; 39 | } 40 | } 41 | return self; 42 | } 43 | 44 | - (NSXMLElement *)xmlRepresentation; 45 | { 46 | NSXMLElement *version = [NSXMLElement elementWithName:@"version"]; 47 | [version setAttributesWithDictionary:@{@"string":self.versionString}]; 48 | 49 | // locales 50 | NSXMLElement *locales = [NSXMLElement elementWithName:@"locales"]; 51 | for (RMAppLocale *locale in self.locales) { 52 | NSXMLElement *localeElement = [locale xmlRepresentation]; 53 | [locales addChild:localeElement]; 54 | } 55 | [version addChild:locales]; 56 | 57 | return version; 58 | } 59 | 60 | + (NSSet *)keyPathsForValuesAffectingActiveLocales; 61 | { 62 | return [NSSet setWithObject:@"locales"]; 63 | } 64 | 65 | - (NSArray *)activeLocales; 66 | { 67 | return [self.locales filteredArrayUsingPredicate: 68 | [NSPredicate predicateWithFormat:@"shouldDeleteLocale != %@", @(YES)]]; 69 | } 70 | 71 | - (void)addLocale:(RMAppLocale*)locale; 72 | { 73 | __block BOOL foundLocale = NO; 74 | [self.locales enumerateObjectsUsingBlock:^(RMAppLocale *existingLocale, NSUInteger idx, BOOL *stop) { 75 | if ([existingLocale.localeName isEqualToString:locale.localeName]) { 76 | existingLocale.shouldDeleteLocale = NO; 77 | foundLocale = YES; 78 | *stop = YES; 79 | } 80 | }]; 81 | 82 | if (!foundLocale) { 83 | self.locales = [self.locales arrayByAddingObject:locale]; 84 | } 85 | } 86 | 87 | @end 88 | 89 | -------------------------------------------------------------------------------- /Connecter/RMByteValueTransformer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMByteValueTransformer.h 3 | // Connecter 4 | // 5 | // Created by Markus on 21.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RMByteValueTransformer : NSValueTransformer 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Connecter/RMByteValueTransformer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMByteValueTransformer.m 3 | // Connecter 4 | // 5 | // Created by Markus on 21.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMByteValueTransformer.h" 10 | 11 | @implementation RMByteValueTransformer 12 | 13 | - (id)transformedValue:(id)value; 14 | { 15 | if ([value isKindOfClass:[NSNumber class]]) { 16 | CGFloat newValue = [value floatValue]; 17 | if (newValue < 0) newValue = 0; 18 | 19 | if (newValue > 1000000) { 20 | newValue /= 1000000; 21 | return [NSString stringWithFormat: @"%.1f MB", newValue]; 22 | } 23 | 24 | if (newValue > 1000) { 25 | newValue /= 1000; 26 | return [NSString stringWithFormat: @"%.0f kb", newValue]; 27 | } 28 | 29 | return [NSString stringWithFormat: @"%.0f b", newValue]; 30 | } 31 | return nil; 32 | } 33 | 34 | + (Class)transformedValueClass; 35 | { 36 | return [NSString class]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Connecter/RMConnecterAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMiTunesConnecterAppDelegate.h 3 | // Connecter 4 | // 5 | // Created by Nik Fletcher on 31/01/2014. 6 | // 7 | // Copyright (c) 2014 Realmac Software 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | #import 29 | 30 | @interface RMConnecterAppDelegate : NSObject 31 | 32 | @property (readonly, strong, nonatomic) NSWindowController *mainWindowController; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Connecter/RMConnecterAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMiTunesConnecterAppDelegate.m 3 | // Connecter 4 | // 5 | // Created by Nik Fletcher on 31/01/2014. 6 | // 7 | // Copyright (c) 2014 Realmac Software 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | #import "RMConnecterAppDelegate.h" 29 | 30 | #import "RMConnecterWindowController.h" 31 | 32 | @interface RMConnecterAppDelegate () 33 | @property (readwrite, strong, nonatomic) NSWindowController *mainWindowController; 34 | @end 35 | 36 | @implementation RMConnecterAppDelegate 37 | 38 | - (void)applicationDidFinishLaunching:(NSNotification *)notification 39 | { 40 | RMConnecterWindowController *windowController = [[RMConnecterWindowController alloc] init]; 41 | [self setMainWindowController:windowController]; 42 | [windowController showWindow:nil]; 43 | } 44 | 45 | - (BOOL)applicationShouldHandleReopen:(NSApplication *)application hasVisibleWindows:(BOOL)hasVisibleWindows 46 | { 47 | [[self mainWindowController] showWindow:application]; 48 | 49 | return YES; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Connecter/RMConnecterCredentials+Keychain.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMConnecterCredentials+Keychain.h 3 | // Connecter 4 | // 5 | // Created by Damien DeVille on 2/2/14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMConnecterCredentials.h" 10 | 11 | @interface RMConnecterCredentials (Keychain) 12 | 13 | /*! 14 | \brief 15 | Attempt to retrieve credentials for `itunesconnect.apple.com` from the keychain. 16 | Note that calling this methot might lead to a modal security alert asking for keychain permission being presented to the user. 17 | */ 18 | + (instancetype)findCredentialsInKeychainForUsername:(NSString *)username error:(NSError **)errorRef; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Connecter/RMConnecterCredentials+Keychain.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMConnecterCredentials+Keychain.m 3 | // Connecter 4 | // 5 | // Created by Damien DeVille on 2/2/14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMConnecterCredentials+Keychain.h" 10 | 11 | @implementation RMConnecterCredentials (Keychain) 12 | 13 | static CFDictionaryRef CF_RETURNS_RETAINED _RMConnecterCreateKeychainQuery(NSString *username) 14 | { 15 | CFMutableDictionaryRef query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 16 | CFDictionarySetValue(query, kSecClass, kSecClassInternetPassword); 17 | CFDictionarySetValue(query, kSecAttrServer, CFSTR("itunesconnect.apple.com")); 18 | if (username != nil) { 19 | CFDictionarySetValue(query, kSecAttrAccount, (CFStringRef)username); 20 | } 21 | CFDictionarySetValue(query, kSecReturnAttributes, kCFBooleanTrue); 22 | CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue); 23 | return query; 24 | } 25 | 26 | static CFTypeRef CF_RETURNS_RETAINED _RMConnecterRetrieveKeychainItem(CFDictionaryRef query, NSError **errorRef) 27 | { 28 | CFTypeRef item = NULL; 29 | OSStatus itemError = SecItemCopyMatching(query, &item); 30 | 31 | if (itemError != errSecSuccess) { 32 | if (errorRef != NULL) { 33 | *errorRef = [NSError errorWithDomain:NSOSStatusErrorDomain code:itemError userInfo:nil]; 34 | } 35 | return nil; 36 | } 37 | 38 | return item; 39 | } 40 | 41 | + (instancetype)findCredentialsInKeychainForUsername:(NSString *)username error:(NSError **)errorRef 42 | { 43 | CFDictionaryRef query = _RMConnecterCreateKeychainQuery(username); 44 | CFTypeRef item = _RMConnecterRetrieveKeychainItem(query, errorRef); 45 | CFRelease(query); 46 | 47 | if (item == NULL) { 48 | return nil; 49 | } 50 | 51 | NSString *account = (NSString *)CFDictionaryGetValue(item, kSecAttrAccount); 52 | NSString *password = [[NSString alloc] initWithData:(NSData *)CFDictionaryGetValue(item, kSecValueData) encoding:NSUTF8StringEncoding]; 53 | 54 | CFRelease(item); 55 | 56 | return [RMConnecterCredentials credentialsWithUsername:account password:password]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Connecter/RMConnecterCredentials.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMConnecterCredentials.h 3 | // Connecter 4 | // 5 | // Created by Damien DeVille on 2/2/14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RMConnecterCredentials : NSObject 12 | 13 | + (instancetype)credentialsWithUsername:(NSString *)username password:(NSString *)password; 14 | 15 | @property (copy, nonatomic) NSString *username; 16 | @property (copy, nonatomic) NSString *password; 17 | 18 | /*! 19 | \brief 20 | Returns YES if both `username` and `password` are set for a given credentials instance. 21 | It is KVO dependent on `username` and `password` and can be used to bind controls that depend on the credentials availability. 22 | */ 23 | @property (readonly, getter = isValid, assign, nonatomic) BOOL valid; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Connecter/RMConnecterCredentials.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMConnecterCredentials.m 3 | // Connecter 4 | // 5 | // Created by Damien DeVille on 2/2/14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMConnecterCredentials.h" 10 | 11 | @implementation RMConnecterCredentials 12 | 13 | + (instancetype)credentialsWithUsername:(NSString *)username password:(NSString *)password 14 | { 15 | RMConnecterCredentials *credentials = [[self alloc] init]; 16 | [credentials setUsername:username]; 17 | [credentials setPassword:password]; 18 | return credentials; 19 | } 20 | 21 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key 22 | { 23 | NSMutableSet *keyPaths = [NSMutableSet setWithSet:[super keyPathsForValuesAffectingValueForKey:key]]; 24 | 25 | if ([key isEqualToString:@"valid"]) { 26 | [keyPaths addObject:@"username"]; 27 | [keyPaths addObject:@"password"]; 28 | } 29 | 30 | return keyPaths; 31 | } 32 | 33 | - (BOOL)isValid 34 | { 35 | return ([[self username] length] > 0 && [[self password] length] > 0); 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Connecter/RMConnecterOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMConnecterOperation.h 3 | // Connecter 4 | // 5 | // Created by Damien DeVille on 2/2/14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RMConnecterOperation : NSOperation 12 | 13 | /*! 14 | \brief 15 | Create a connecter operation launching a program at the specified path with the specified arguments. 16 | */ 17 | - (id)initWithToolLaunchPath:(NSString *)launchPath arguments:(NSArray *)arguments; 18 | 19 | /*! 20 | \brief 21 | Upon completion this property will be populated with the result and an eventual error can be retrieved by reference. 22 | */ 23 | @property (readonly, copy, atomic) NSString * (^completionProvider)(NSError **errorRef); 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Connecter/RMConnecterOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMConnecterOperation.m 3 | // Connecter 4 | // 5 | // Created by Damien DeVille on 2/2/14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMConnecterOperation.h" 10 | 11 | @interface RMConnecterOperation (/* Parameters */) 12 | 13 | @property (copy, nonatomic) NSString *launchPath; 14 | @property (strong, nonatomic) NSArray *arguments; 15 | 16 | @end 17 | 18 | @interface RMConnecterOperation (/* Operation */) 19 | 20 | @property (assign, atomic) BOOL isExecuting, isFinished; 21 | 22 | @property (readwrite, copy, atomic) NSString * (^completionProvider)(NSError **errorRef); 23 | 24 | @end 25 | 26 | @interface RMConnecterOperation (/* Private */) 27 | 28 | @property (strong, nonatomic) NSOperationQueue *controlQueue; 29 | @property (strong, nonatomic) dispatch_queue_t workQueue; 30 | 31 | @property (strong, nonatomic) NSTask *task; 32 | @property (strong, nonatomic) dispatch_io_t taskConnection; 33 | 34 | @property (strong, nonatomic) NSMutableData *response; 35 | 36 | @end 37 | 38 | @implementation RMConnecterOperation 39 | 40 | - (id)initWithToolLaunchPath:(NSString *)launchPath arguments:(NSArray *)arguments 41 | { 42 | self = [self init]; 43 | if (self == nil) { 44 | return nil; 45 | } 46 | 47 | NSParameterAssert(launchPath != nil); 48 | _launchPath = [launchPath copy]; 49 | 50 | NSParameterAssert(arguments != nil); 51 | _arguments = arguments; 52 | 53 | _controlQueue = [[NSOperationQueue alloc] init]; 54 | [_controlQueue setMaxConcurrentOperationCount:1]; 55 | 56 | _workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 57 | 58 | _completionProvider = [^ NSString * (NSError **errorRef) { 59 | if (errorRef != NULL) { 60 | *errorRef = [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]; 61 | } 62 | return nil; 63 | } copy]; 64 | 65 | return self; 66 | } 67 | 68 | #pragma mark - NSOperation 69 | 70 | - (BOOL)isConcurrent 71 | { 72 | return YES; 73 | } 74 | 75 | static NSString * const _RMConnecterOperationIsExecutingKey = @"isExecuting"; 76 | static NSString * const _RMConnecterOperationIsFinishedKey = @"isFinished"; 77 | 78 | - (void)start 79 | { 80 | [[self controlQueue] addOperationWithBlock:^ { 81 | if ([self isCancelled]) { 82 | [self _finish]; 83 | return; 84 | } 85 | 86 | [self willChangeValueForKey:_RMConnecterOperationIsExecutingKey]; 87 | [self setIsExecuting:YES]; 88 | [self didChangeValueForKey:_RMConnecterOperationIsExecutingKey]; 89 | 90 | [self _startTask]; 91 | }]; 92 | } 93 | 94 | - (void)cancel 95 | { 96 | [[self controlQueue] addOperationWithBlock:^ { 97 | [[self task] terminate]; 98 | 99 | [super cancel]; 100 | }]; 101 | 102 | [super cancel]; 103 | } 104 | 105 | - (void)_finish 106 | { 107 | [self willChangeValueForKey:_RMConnecterOperationIsExecutingKey]; 108 | [self setIsExecuting:NO]; 109 | [self didChangeValueForKey:_RMConnecterOperationIsExecutingKey]; 110 | 111 | [self willChangeValueForKey:_RMConnecterOperationIsFinishedKey]; 112 | [self setIsFinished:YES]; 113 | [self didChangeValueForKey:_RMConnecterOperationIsFinishedKey]; 114 | } 115 | 116 | #pragma mark - Task 117 | 118 | - (void)_startTask 119 | { 120 | [self __setupTask]; 121 | [self __setupConnection]; 122 | 123 | [self __startTask]; 124 | } 125 | 126 | - (void)__setupTask 127 | { 128 | NSTask *task = [[NSTask alloc] init]; 129 | [task setLaunchPath:[self launchPath]]; 130 | [task setArguments:[self arguments]]; 131 | 132 | [task setStandardOutput:[NSPipe pipe]]; 133 | [task setStandardError:[NSFileHandle fileHandleWithNullDevice]]; 134 | 135 | [self setTask:task]; 136 | } 137 | 138 | - (void)__setupConnection 139 | { 140 | dispatch_io_t connection = dispatch_io_create(DISPATCH_IO_STREAM, [[[[self task] standardOutput] fileHandleForReading] fileDescriptor], [self workQueue], ^ (int error) { 141 | if (error != 0) { 142 | [self _didReceiveTaskError:[NSError errorWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]]; 143 | return; 144 | } 145 | }); 146 | [self setTaskConnection:connection]; 147 | 148 | dispatch_io_read(connection, 0, SIZE_MAX, [self workQueue], ^ (bool done, dispatch_data_t receivedData, int error) { 149 | if (receivedData != nil && dispatch_data_get_size(receivedData) != 0) { 150 | if ([self response] == nil) { 151 | [self setResponse:[NSMutableData data]]; 152 | } 153 | 154 | void const *bytes = NULL; size_t bytesLength = 0; 155 | dispatch_data_t contiguousData __attribute__((unused, objc_precise_lifetime)) = dispatch_data_create_map(receivedData, &bytes, &bytesLength); 156 | 157 | [[self response] appendBytes:bytes length:bytesLength]; 158 | } 159 | 160 | if (error != 0) { 161 | dispatch_io_close(connection, DISPATCH_IO_STOP); 162 | 163 | [self _didReceiveTaskError:[NSError errorWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]]; 164 | return; 165 | } 166 | 167 | if (done) { 168 | dispatch_io_close(connection, DISPATCH_IO_STOP); 169 | 170 | [self _didReceiveTaskResponse:[self response]]; 171 | } 172 | }); 173 | } 174 | 175 | - (void)__startTask 176 | { 177 | [[self task] launch]; 178 | } 179 | 180 | #pragma mark - Completion 181 | 182 | - (void)_didReceiveTaskError:(NSError *)error 183 | { 184 | [self setCompletionProvider:^ NSString * (NSError **errorRef) { 185 | if (errorRef != NULL) { 186 | *errorRef = error; 187 | } 188 | return nil; 189 | }]; 190 | 191 | [self _finish]; 192 | } 193 | 194 | - (void)_didReceiveTaskResponse:(NSData *)response 195 | { 196 | NSString *responseString = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding]; 197 | 198 | [self setCompletionProvider:^ NSString * (NSError **errorRef) { 199 | return responseString; 200 | }]; 201 | 202 | [self _finish]; 203 | } 204 | 205 | @end 206 | -------------------------------------------------------------------------------- /Connecter/RMConnecterWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMiTunesConnecterAppDelegate.h 3 | // Connecter 4 | // 5 | // Created by Nik Fletcher on 31/01/2014. 6 | // 7 | // Copyright (c) 2014 Realmac Software 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | #import 29 | 30 | @class RMConnecterCredentials; 31 | 32 | @interface RMConnecterWindowController : NSWindowController 33 | 34 | @property (readonly, strong, nonatomic) RMConnecterCredentials *credentials; 35 | @property (readonly, copy, nonatomic) NSString *appSKU; 36 | 37 | @property (readonly, copy, nonatomic) NSString *status; 38 | @property (readonly, copy, nonatomic) NSString *log; 39 | 40 | @property (readonly, getter = isLoading, assign, nonatomic) BOOL loading; 41 | @property (readonly, assign, nonatomic) BOOL hasTransporter; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Connecter/RMConnecterWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMiTunesConnecterAppDelegate.h 3 | // Connecter 4 | // 5 | // Created by Nik Fletcher on 31/01/2014. 6 | // 7 | // Copyright (c) 2014 Realmac Software 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | #import "RMConnecterWindowController.h" 29 | 30 | #import "RMConnecterOperation.h" 31 | #import "RMConnecterCredentials.h" 32 | #import "RMConnecterCredentials+Keychain.h" 33 | 34 | static NSString * const _RMConnecterLastPackageLocationDefaultsKey = @"lastPackageLocation"; 35 | static NSString * const _RMConnecterCredentialsUsernameDefaultsKey = @"credentialsUsername"; 36 | 37 | @interface RMConnecterWindowController (/* User interface */) 38 | 39 | @property (strong, nonatomic) IBOutlet NSTextField *usernameTextField, *passwordTextField; 40 | 41 | @end 42 | 43 | @interface RMConnecterWindowController (/* Data */) 44 | 45 | @property (readwrite, strong, nonatomic) RMConnecterCredentials *credentials; 46 | 47 | @property (readwrite, copy, nonatomic) NSString *appSKU; 48 | 49 | @property (readwrite, copy, nonatomic) NSString *internalStatus; 50 | @property (readwrite, copy, nonatomic) NSString *log; 51 | 52 | @property (readwrite, assign, nonatomic) BOOL loading; 53 | @property (readwrite, assign, nonatomic) BOOL hasTransporter; 54 | 55 | @property (strong, nonatomic) NSOperationQueue *operationQueue; 56 | 57 | @end 58 | 59 | @implementation RMConnecterWindowController 60 | 61 | static NSString *_RMConnecterTransporterPath(void) 62 | { 63 | static NSString * const _RMConnecterApplicationLoaderName = @"Application Loader"; 64 | 65 | NSString *applicationLoaderPath = [[NSWorkspace sharedWorkspace] fullPathForApplication:_RMConnecterApplicationLoaderName]; 66 | if (applicationLoaderPath == nil) { 67 | return nil; 68 | } 69 | 70 | return [applicationLoaderPath stringByAppendingPathComponent:@"Contents/itms/bin/iTMSTransporter"]; 71 | } 72 | 73 | + (void)load 74 | { 75 | @autoreleasepool { 76 | NSDictionary *registrationDefaults = @{_RMConnecterLastPackageLocationDefaultsKey : @"~/Desktop"}; 77 | [[NSUserDefaults standardUserDefaults] registerDefaults:registrationDefaults]; 78 | } 79 | } 80 | 81 | + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key 82 | { 83 | NSMutableSet *keyPaths = [NSMutableSet setWithSet:[super keyPathsForValuesAffectingValueForKey:key]]; 84 | 85 | if ([key isEqualToString:@"status"]) { 86 | [keyPaths addObject:@"credentials.valid"]; 87 | [keyPaths addObject:@"internalStatus"]; 88 | } 89 | 90 | return keyPaths; 91 | } 92 | 93 | - (id)init 94 | { 95 | return [self initWithWindowNibName:@"RMConnecterWindow" owner:self]; 96 | } 97 | 98 | - (void)windowDidLoad 99 | { 100 | [super windowDidLoad]; 101 | 102 | NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 103 | [self setOperationQueue:operationQueue]; 104 | 105 | NSString *currentUsername = [[NSUserDefaults standardUserDefaults] stringForKey:_RMConnecterCredentialsUsernameDefaultsKey]; 106 | 107 | RMConnecterCredentials *credentials = [RMConnecterCredentials findCredentialsInKeychainForUsername:currentUsername error:NULL]; 108 | if (credentials == nil) { 109 | credentials = [[RMConnecterCredentials alloc] init]; 110 | } 111 | [self setCredentials:credentials]; 112 | 113 | BOOL hasTransporter = (_RMConnecterTransporterPath() != nil); 114 | [self setHasTransporter:hasTransporter]; 115 | 116 | [self setInternalStatus:NSLocalizedString(@"Awaiting your command…", @"Awaiting your Command String")]; 117 | 118 | [self performSelector:@selector(_checkTransporter) withObject:nil afterDelay:0.0]; 119 | } 120 | 121 | - (void)_checkTransporter 122 | { 123 | if ([self hasTransporter]) { 124 | return; 125 | } 126 | 127 | [self setInternalStatus:NSLocalizedString(@"Please install iTunes Transporter", @"Status Field Install Transporter String")]; 128 | 129 | NSAlert *alert = [[NSAlert alloc] init]; 130 | [alert addButtonWithTitle:NSLocalizedString(@"OK", @"OK")]; 131 | [alert addButtonWithTitle:NSLocalizedString(@"Get Xcode", "Transporter Missing Alert Get Xcode Button Label")]; 132 | [alert setMessageText:NSLocalizedString(@"Unable to Locate iTMSTransporter", @"")]; 133 | [alert setInformativeText:NSLocalizedString(@"Connecter requires the iTMSTransporter binary to be installed. Please install Xcode from the Mac App Store.", @"")]; 134 | 135 | [alert beginSheetModalForWindow:[self window] completionHandler:^ (NSModalResponse returnCode) { 136 | if (returnCode == NSAlertSecondButtonReturn) { 137 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://itunes.apple.com/gb/app/xcode/id497799835?mt=12"]]; 138 | } 139 | }]; 140 | } 141 | 142 | #pragma mark - Properties 143 | 144 | - (NSString *)status 145 | { 146 | if (![[self credentials] isValid]) { 147 | return NSLocalizedString(@"Please enter your iTunes Connect credentials…", @"Enter Credentials Prompt"); 148 | } 149 | return [self internalStatus]; 150 | } 151 | 152 | #pragma mark - Actions 153 | 154 | - (IBAction)chooseiTunesPackage:(id)sender 155 | { 156 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 157 | [openPanel setAllowsMultipleSelection:NO]; 158 | [openPanel setCanChooseDirectories:NO]; 159 | [openPanel setCanCreateDirectories:NO]; 160 | [openPanel setCanChooseFiles:YES]; 161 | [openPanel setAllowedFileTypes:@[@"itmsp"]]; 162 | 163 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 164 | NSString * filePath = [defaults stringForKey:_RMConnecterLastPackageLocationDefaultsKey]; 165 | NSURL *fileURL = [NSURL fileURLWithPath:filePath]; 166 | [openPanel setDirectoryURL:fileURL]; 167 | 168 | [openPanel beginSheetModalForWindow:[self window] completionHandler:^ (NSInteger result) { 169 | if (result == NSFileHandlingPanelCancelButton) { 170 | return; 171 | } 172 | 173 | [self setLog:@""]; 174 | 175 | NSURL *selectedPackageURL = [openPanel URL]; 176 | [[NSUserDefaults standardUserDefaults] setObject:[selectedPackageURL path] forKey:_RMConnecterLastPackageLocationDefaultsKey]; 177 | 178 | switch ([sender tag]) { 179 | case 1: 180 | [self verifyiTunesPackageAtURL:selectedPackageURL]; 181 | [self setInternalStatus:[NSString stringWithFormat:NSLocalizedString(@"Verifying iTunes Package: %@", @"Verifying Package String"), [selectedPackageURL path]]]; 182 | break; 183 | case 2: 184 | [self submitPackageAtURL:selectedPackageURL]; 185 | [self setInternalStatus:[NSString stringWithFormat:NSLocalizedString(@"Submitting iTunes Package: %@", @"Submitting Package String"), [selectedPackageURL path]]]; 186 | break; 187 | default: 188 | break; 189 | } 190 | }]; 191 | } 192 | 193 | - (IBAction)selectLocationForDownloadedMetadata:(id)sender 194 | { 195 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 196 | [openPanel setAllowsMultipleSelection:NO]; 197 | [openPanel setCanChooseDirectories:YES]; 198 | [openPanel setCanCreateDirectories:YES]; 199 | [openPanel setCanChooseFiles:NO]; 200 | 201 | NSString * filePath = [[NSUserDefaults standardUserDefaults] stringForKey:_RMConnecterLastPackageLocationDefaultsKey]; 202 | NSURL *fileURL = [NSURL fileURLWithPath:filePath]; 203 | [openPanel setDirectoryURL:fileURL]; 204 | 205 | [openPanel beginSheetModalForWindow:[self window] completionHandler:^ (NSInteger result) { 206 | if (result == NSFileHandlingPanelCancelButton) { 207 | return; 208 | } 209 | 210 | [self setLog:@""]; 211 | 212 | NSURL *selectedPackageURL = [openPanel URL]; 213 | [self setInternalStatus:[NSString stringWithFormat:NSLocalizedString(@"Retrieving package from iTunes Connect. Metadata will be downloaded to %@", "Downloaded Info String"), [selectedPackageURL path]]]; 214 | [self lookupMetadataAndPlaceInPackageAtURL:selectedPackageURL]; 215 | }]; 216 | } 217 | 218 | #pragma mark - iTunes Connect Interaction 219 | 220 | - (void)lookupMetadataAndPlaceInPackageAtURL:(NSURL *)packageURL 221 | { 222 | NSArray *arguments = @[ 223 | @"-vendor_id", [NSString stringWithFormat:@"\"%@\"", [self appSKU]], 224 | @"-destination", packageURL, 225 | ]; 226 | [self _enqueueiTunesConnectInteractionOperationForPackageAtURL:packageURL method:@"lookupMetadata" arguments:arguments openPackageUponTermination:YES]; 227 | } 228 | 229 | - (void)verifyiTunesPackageAtURL:(NSURL *)packageURL 230 | { 231 | NSArray *arguments = @[ 232 | @"-f", packageURL, 233 | ]; 234 | [self _enqueueiTunesConnectInteractionOperationForPackageAtURL:packageURL method:@"verify" arguments:arguments openPackageUponTermination:NO]; 235 | } 236 | 237 | - (void)submitPackageAtURL:(NSURL *)packageURL 238 | { 239 | NSArray *arguments = @[ 240 | @"-f", packageURL, 241 | ]; 242 | [self _enqueueiTunesConnectInteractionOperationForPackageAtURL:packageURL method:@"upload" arguments:arguments openPackageUponTermination:NO]; 243 | } 244 | 245 | - (void)_enqueueiTunesConnectInteractionOperationForPackageAtURL:(NSURL *)packageURL method:(NSString *)method arguments:(NSArray *)arguments openPackageUponTermination:(BOOL)openPackageUponTermination 246 | { 247 | NSParameterAssert(packageURL != nil); 248 | NSParameterAssert(method != nil); 249 | 250 | NSString *launchPath = _RMConnecterTransporterPath(); 251 | 252 | NSArray *launchArguments = @[ 253 | @"-m", method, 254 | @"-u", [NSString stringWithFormat:@"\"%@\"", [[self credentials] username]], 255 | @"-p", [NSString stringWithFormat:@"\"%@\"", [[self credentials] password]], 256 | ]; 257 | launchArguments = [launchArguments arrayByAddingObjectsFromArray:arguments]; 258 | 259 | [self setLoading:YES]; 260 | 261 | RMConnecterOperation *connecterOperation = [[RMConnecterOperation alloc] initWithToolLaunchPath:launchPath arguments:launchArguments]; 262 | [[self operationQueue] addOperation:connecterOperation]; 263 | 264 | NSOperation *resultOperation = [NSBlockOperation blockOperationWithBlock:^ { 265 | [self setLoading:NO]; 266 | 267 | NSError *connecterError = nil; 268 | NSString *connecterResult = [connecterOperation completionProvider](&connecterError); 269 | 270 | [self setInternalStatus:NSLocalizedString(@"Finished", "Finished Interacting with iTunes Connect Strings")]; 271 | [self setLog:connecterResult]; 272 | 273 | if (openPackageUponTermination) { 274 | [self openDocumentWithPackageURL:packageURL]; 275 | } 276 | }]; 277 | [resultOperation addDependency:connecterOperation]; 278 | [[NSOperationQueue mainQueue] addOperation:resultOperation]; 279 | } 280 | 281 | - (NSError*)openDocumentWithPackageURL:(NSURL*)packageURL; 282 | { 283 | NSError *error; 284 | NSString *filename = [NSString stringWithFormat: @"%@.itmsp", [self appSKU]]; 285 | NSString *filePath = [[packageURL relativePath] stringByAppendingPathComponent:filename]; 286 | NSURL *fileURL = [NSURL fileURLWithPath:filePath]; 287 | [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:fileURL display:YES error:&error]; 288 | 289 | return error; 290 | } 291 | 292 | #pragma mark - NSControlSubclassNotifications 293 | 294 | - (void)controlTextDidEndEditing:(NSNotification *)notification 295 | { 296 | NSTextField *textField = [notification object]; 297 | 298 | if (textField == [self usernameTextField]) { 299 | NSString *username = [textField stringValue]; 300 | 301 | if ([username length] == 0) { 302 | return; 303 | } 304 | 305 | if ([[[self credentials] password] length] == 0) { 306 | RMConnecterCredentials *credentials = [RMConnecterCredentials findCredentialsInKeychainForUsername:username error:NULL]; 307 | if (credentials != nil) { 308 | [self setCredentials:credentials]; 309 | } 310 | } 311 | 312 | [[NSUserDefaults standardUserDefaults] setObject:username forKey:_RMConnecterCredentialsUsernameDefaultsKey]; 313 | } 314 | } 315 | 316 | @end 317 | -------------------------------------------------------------------------------- /Connecter/RMOutlineView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMOutlineView.h 3 | // Connecter 4 | // 5 | // Created by Markus on 27.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^RMOutlineViewDeleteItemBlock)(id item); 12 | 13 | @interface RMOutlineView : NSOutlineView 14 | 15 | @property (nonatomic, copy) RMOutlineViewDeleteItemBlock deleteItemBlock; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Connecter/RMOutlineView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMOutlineView.m 3 | // Connecter 4 | // 5 | // Created by Markus on 27.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMOutlineView.h" 10 | 11 | @implementation RMOutlineView 12 | 13 | - (IBAction)delete:(id)sender; 14 | { 15 | if (self.deleteItemBlock) { 16 | id item = [self itemAtRow:self.selectedRow]; 17 | self.deleteItemBlock(item); 18 | } 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Connecter/RMOutlineViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMOutlineViewController.h 3 | // Connecter 4 | // 5 | // Created by Markus on 25.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class RMOutlineViewController; 13 | typedef void(^OutlineControllerAddLocaleBlock)(id sender); 14 | 15 | @interface RMOutlineViewController : NSObject 16 | 17 | @property (nonatomic, weak) NSArrayController *versionsController; 18 | @property (nonatomic, weak) NSArrayController *localesController; 19 | 20 | @property (nonatomic, copy) OutlineControllerAddLocaleBlock addLocaleBlock; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Connecter/RMOutlineViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMOutlineViewController.m 3 | // Connecter 4 | // 5 | // Created by Markus on 25.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppVersion.h" 10 | #import "RMAppLocale.h" 11 | 12 | #import "RMOutlineViewController.h" 13 | 14 | @implementation RMOutlineViewController 15 | 16 | #pragma mark NSOutlineViewDataSource 17 | 18 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item; 19 | { 20 | if ([item isKindOfClass:[RMAppVersion class]]) { 21 | RMAppVersion *version = item; 22 | return version.activeLocales.count; 23 | } 24 | return [self.versionsController.arrangedObjects count] + 1; 25 | } 26 | 27 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item; 28 | { 29 | if ([item isKindOfClass:[RMAppVersion class]]) { 30 | RMAppVersion *version = item; 31 | return version.activeLocales[index]; 32 | } 33 | 34 | if ([self.versionsController.arrangedObjects count] > index) { 35 | return [self.versionsController.arrangedObjects objectAtIndex:index]; 36 | } else { 37 | return [self addLocaleButtonItem]; 38 | } 39 | } 40 | 41 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item; 42 | { 43 | if ([item isKindOfClass:[RMAppVersion class]]) { 44 | return YES; 45 | } 46 | return NO; 47 | } 48 | 49 | #pragma mark NSOutlineViewDelegate 50 | 51 | - (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item; 52 | { 53 | if([item isKindOfClass:[RMAppLocale class]] || [item isKindOfClass:[RMAppVersion class]]) { 54 | NSTextField *textField = [[NSTextField alloc] init]; 55 | [textField setEditable:NO]; 56 | [textField setSelectable:YES]; 57 | [textField setBezeled:NO]; 58 | [textField setBackgroundColor:[NSColor clearColor]]; 59 | 60 | textField.stringValue = [self displayValueForItem:item]; 61 | 62 | return textField; 63 | } 64 | 65 | else if([self isAddLocaleButtonItem:item]) { 66 | NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 100)]; 67 | NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)]; 68 | button.autoresizingMask = NSViewMaxYMargin | NSViewWidthSizable; 69 | [button setBezelStyle:NSInlineBezelStyle]; 70 | [button setTitle:@"Add Locale"]; 71 | [button setTarget:self]; 72 | [button setAction:@selector(addLocale:)]; 73 | [view addSubview:button]; 74 | return view; 75 | } 76 | 77 | return nil; 78 | } 79 | 80 | - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item; 81 | { 82 | if ([self isAddLocaleButtonItem:item]) { 83 | return outlineView.rowHeight*2; 84 | } 85 | return outlineView.rowHeight; 86 | } 87 | 88 | - (NSIndexSet *)outlineView:(NSOutlineView *)outlineView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes; 89 | { 90 | NSUInteger index = [proposedSelectionIndexes firstIndex]; 91 | id item = [outlineView itemAtRow:index]; 92 | 93 | // don't change selection, if button row is selected 94 | if([self isAddLocaleButtonItem:item]) { 95 | return [NSIndexSet indexSetWithIndex:[outlineView selectedRow]]; 96 | } 97 | 98 | // find next locale, if another item is selected (e.g. a version) 99 | while (![item isKindOfClass:[RMAppLocale class]]) { 100 | index++; 101 | item = [outlineView itemAtRow:index]; 102 | if (!item) return nil; 103 | } 104 | 105 | return [NSIndexSet indexSetWithIndex:index]; 106 | } 107 | 108 | #pragma mark NSOutlineView Notifications 109 | 110 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification; 111 | { 112 | NSOutlineView *view = notification.object; 113 | RMAppLocale *locale = [view itemAtRow:view.selectedRow]; 114 | RMAppVersion *version = [view parentForItem:locale]; 115 | 116 | if (locale && version) { 117 | NSAssert([locale isKindOfClass:[RMAppLocale class]] && [version isKindOfClass:[RMAppVersion class]], 118 | @"Selected item is not of type RMAppLocale, or parent is not of type RMAppVersion."); 119 | [self.versionsController setSelectedObjects:@[version]]; 120 | [self.localesController setSelectedObjects:@[locale]]; 121 | } 122 | } 123 | 124 | - (void)outlineViewItemDidExpand:(NSNotification *)notification; 125 | { 126 | NSOutlineView *view = notification.object; 127 | RMAppVersion *version = [notification.userInfo objectForKey:@"NSObject"]; 128 | NSAssert([version isKindOfClass:[RMAppVersion class]], @"Expanded item is not of type RMAppVersion."); 129 | 130 | if (version == self.versionsController.selectedObjects.firstObject) { 131 | NSInteger row = [view rowForItem:self.localesController.selectedObjects.firstObject]; 132 | [view selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; 133 | } 134 | } 135 | 136 | #pragma mark Actions 137 | 138 | - (void)addLocale:(NSButton*)button; 139 | { 140 | if (self.addLocaleBlock) { 141 | self.addLocaleBlock(button); 142 | } 143 | } 144 | 145 | #pragma mark Helper 146 | 147 | - (NSString*)displayValueForItem:(id)item; 148 | { 149 | if ([item isKindOfClass:[RMAppVersion class]]) { 150 | RMAppVersion *version = item; 151 | return version.versionString; 152 | } 153 | else if ([item isKindOfClass:[RMAppLocale class]]) { 154 | RMAppLocale *locale = item; 155 | return locale.formattedLocaleNameShort; 156 | } 157 | return nil; 158 | } 159 | 160 | - (id)addLocaleButtonItem; 161 | { 162 | return @"Button"; 163 | } 164 | 165 | - (BOOL)isAddLocaleButtonItem:(id)item; 166 | { 167 | return ([item isKindOfClass:[NSString class]] && 168 | [item isEqualToString:@"Button"]); 169 | } 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /Connecter/RMScreenshotViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMScreenshotViewController.h 3 | // Connecter 4 | // 5 | // Created by Markus on 19.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class RMAppScreenshot; 12 | @protocol RMScreenshotViewControllerDelegate; 13 | 14 | @interface RMScreenshotViewController : NSViewController 15 | 16 | @property (weak) IBOutlet NSImageView *imageView; 17 | 18 | @property (nonatomic, assign) NSInteger position; 19 | @property (nonatomic, strong) RMAppScreenshot *screenshot; 20 | 21 | @property (nonatomic, weak) id delegate; 22 | 23 | @end 24 | 25 | 26 | 27 | @protocol RMScreenshotViewControllerDelegate 28 | - (void)screenshotViewControllerDidUpdateScreenshot:(RMScreenshotViewController*)controller; 29 | @end -------------------------------------------------------------------------------- /Connecter/RMScreenshotViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMScreenshotViewController.m 3 | // Connecter 4 | // 5 | // Created by Markus on 19.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppScreenshot.h" 10 | 11 | #import "RMScreenshotViewController.h" 12 | 13 | @interface RMScreenshotViewController () 14 | @end 15 | 16 | @implementation RMScreenshotViewController 17 | 18 | - (void)setScreenshot:(RMAppScreenshot *)screenshot; 19 | { 20 | if (screenshot == _screenshot) return; 21 | _screenshot = screenshot; 22 | 23 | self.imageView.image = [[NSImage alloc] initWithData:screenshot.imageData]; 24 | if(!self.imageView.image && screenshot != nil) { 25 | self.imageView.image = [NSImage imageNamed:@"ScreenshotPlaceholder"]; 26 | } 27 | } 28 | 29 | - (IBAction)imageDidChange:(NSImageView*)sender; 30 | { 31 | if (sender.image) 32 | { 33 | // get PNG representation 34 | [sender.image lockFocus]; 35 | NSBitmapImageRep *bitmapRep = [sender.image.representations firstObject]; 36 | NSData *imageData = [bitmapRep representationUsingType:NSPNGFileType properties:nil]; 37 | [sender.image unlockFocus]; 38 | 39 | // update screenshot 40 | if (!self.screenshot) { 41 | RMAppScreenshot *screenshot = [[RMAppScreenshot alloc] init]; 42 | screenshot.position = self.position; 43 | screenshot.imageData = imageData; 44 | self.screenshot = screenshot; 45 | } else { 46 | self.screenshot.imageData = imageData; 47 | } 48 | } 49 | else 50 | { 51 | // remove screenshot 52 | self.screenshot = nil; 53 | } 54 | 55 | // inform delegate 56 | if (self.delegate) { 57 | [self.delegate screenshotViewControllerDidUpdateScreenshot:self]; 58 | } 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Connecter/RMScreenshotViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | None 39 | None 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | - 56 | - 57 | RMByteValueTransformer 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Connecter/RMScreenshotsGroupView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RMScreenshotsGroupView.h 3 | // Connecter 4 | // 5 | // Created by Markus on 19.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppScreenshot.h" 10 | 11 | #import 12 | 13 | @protocol RMScreenshotsGroupViewDelegate; 14 | 15 | @interface RMScreenshotsGroupView : NSView 16 | 17 | @property (nonatomic, strong) NSArray *screenshots; 18 | 19 | @property (nonatomic, weak) id delegate; 20 | 21 | @end 22 | 23 | 24 | @protocol RMScreenshotsGroupViewDelegate 25 | - (void)screenshotsGroupViewDidUpdateScreenshots:(RMScreenshotsGroupView*)controller; 26 | @end 27 | 28 | -------------------------------------------------------------------------------- /Connecter/RMScreenshotsGroupView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMScreenshotsGroupView.m 3 | // Connecter 4 | // 5 | // Created by Markus on 19.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMScreenshotViewController.h" 10 | 11 | #import "RMScreenshotsGroupView.h" 12 | 13 | @interface RMScreenshotsGroupView () 14 | @property (nonatomic, copy) NSArray *screenshotViewController; 15 | @end 16 | 17 | @implementation RMScreenshotsGroupView 18 | 19 | - (void)awakeFromNib; 20 | { 21 | [super awakeFromNib]; 22 | [self addScreenshotViews]; 23 | } 24 | 25 | - (void)addScreenshotViews; 26 | { 27 | NSMutableArray *controllerArray = [NSMutableArray array]; 28 | for (NSInteger i=0; i<5; i++) { 29 | RMScreenshotViewController *controller = [[RMScreenshotViewController alloc] initWithNibName:@"RMScreenshotViewController" bundle:nil]; 30 | controller.position = i+1; 31 | controller.delegate = self; 32 | [controllerArray addObject:controller]; 33 | [self addSubview:controller.view]; 34 | } 35 | self.screenshotViewController = controllerArray; 36 | 37 | // relayout 38 | [self setFrame:self.frame]; 39 | } 40 | 41 | - (void)setFrame:(NSRect)frameRect; 42 | { 43 | [super setFrame:frameRect]; 44 | 45 | NSInteger xPos=0, margin=0; 46 | NSInteger viewWidth = [[[self.screenshotViewController firstObject] view] frame].size.width; 47 | margin = 5 + (frameRect.size.width-viewWidth*5)/8.0; 48 | xPos = floor((frameRect.size.width-(viewWidth*5+margin*4))/2.0); 49 | 50 | for (NSView *view in [self.screenshotViewController valueForKey:@"view"]) { 51 | [view setFrameOrigin:NSMakePoint(xPos, 0)]; 52 | [view setFrameSize:NSMakeSize(viewWidth, frameRect.size.height)]; 53 | xPos += view.frame.size.width + margin; 54 | } 55 | } 56 | 57 | #pragma mark setter 58 | 59 | - (void)setScreenshots:(NSArray*)screenshots; 60 | { 61 | _screenshots = screenshots; 62 | if (_screenshots == nil) _screenshots = [NSArray array]; 63 | 64 | for (RMScreenshotViewController *controller in self.screenshotViewController) { 65 | controller.screenshot = nil; 66 | } 67 | 68 | for (RMAppScreenshot *screenshot in screenshots) { 69 | int index = screenshot.position-1; 70 | if(self.screenshotViewController.count > index) { 71 | [self.screenshotViewController[index] setScreenshot:screenshot]; 72 | } 73 | } 74 | } 75 | 76 | #pragma mark updates 77 | 78 | - (void)screenshotViewControllerDidUpdateScreenshot:(RMScreenshotViewController*)controller; 79 | { 80 | NSInteger index = (controller.position-1); 81 | NSMutableArray *screenshots = [NSMutableArray arrayWithArray:self.screenshots]; 82 | if (screenshots.count > index) { 83 | if (controller.screenshot) { 84 | [screenshots replaceObjectAtIndex:index withObject:controller.screenshot]; 85 | } else { 86 | [screenshots removeObjectAtIndex:index]; 87 | } 88 | } else { 89 | [screenshots addObject:controller.screenshot]; 90 | } 91 | 92 | // fix positions 93 | [screenshots enumerateObjectsUsingBlock:^(RMAppScreenshot *screenshot, NSUInteger idx, BOOL *stop) { 94 | screenshot.position = idx+1; 95 | }]; 96 | 97 | [self setScreenshots:[screenshots copy]]; 98 | 99 | // inform delegate 100 | if (self.delegate) { 101 | [self.delegate screenshotsGroupViewDidUpdateScreenshots:self]; 102 | } 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /Connecter/RMXMLObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // XMLObject.h 3 | // Connecter 4 | // 5 | // Created by Markus on 18.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol RMXMLObject 12 | 13 | - (id)initWithXMLElement:(NSXMLElement*)xmlElement; 14 | - (NSXMLElement*)xmlRepresentation; 15 | 16 | @end -------------------------------------------------------------------------------- /Connecter/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1265 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | \paperw11900\paperh16840\vieww9600\viewh8400\viewkind0 5 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 6 | 7 | \f0\fs24 \cf0 Developed by:\ 8 | {\field{\*\fldinst{HYPERLINK "http://realmacsoftware.com"}}{\fldrslt Realmac Software}} & {\field{\*\fldinst{HYPERLINK "http://nxtbgthng.com"}}{\fldrslt nxtbgthng GmbH}}\ 9 | \ 10 | Source {\field{\*\fldinst{HYPERLINK "https://github.com/realmacsoftware/RMConnecter"}}{\fldrslt available on GitHub}}.\ 11 | Pull Requests Welcome.} -------------------------------------------------------------------------------- /Connecter/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Connecter/en.lproj/MainMenu.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "5"; */ 3 | "5.title" = "Bring All to Front"; 4 | 5 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "19"; */ 6 | "19.title" = "Window"; 7 | 8 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "23"; */ 9 | "23.title" = "Minimize"; 10 | 11 | /* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */ 12 | "24.title" = "Window"; 13 | 14 | /* Class = "NSMenu"; title = "AMainMenu"; ObjectID = "29"; */ 15 | "29.title" = "AMainMenu"; 16 | 17 | /* Class = "NSMenuItem"; title = "Connecter"; ObjectID = "56"; */ 18 | "56.title" = "Connecter"; 19 | 20 | /* Class = "NSMenu"; title = "Connecter"; ObjectID = "57"; */ 21 | "57.title" = "Connecter"; 22 | 23 | /* Class = "NSMenuItem"; title = "About Connecter"; ObjectID = "58"; */ 24 | "58.title" = "About Connecter"; 25 | 26 | /* Class = "NSMenuItem"; title = "Open…"; ObjectID = "72"; */ 27 | "72.title" = "Open…"; 28 | 29 | /* Class = "NSMenuItem"; title = "Close"; ObjectID = "73"; */ 30 | "73.title" = "Close"; 31 | 32 | /* Class = "NSMenuItem"; title = "Save…"; ObjectID = "75"; */ 33 | "75.title" = "Save…"; 34 | 35 | /* Class = "NSMenuItem"; title = "Page Setup..."; ObjectID = "77"; */ 36 | "77.title" = "Page Setup..."; 37 | 38 | /* Class = "NSMenuItem"; title = "Print…"; ObjectID = "78"; */ 39 | "78.title" = "Print…"; 40 | 41 | /* Class = "NSMenu"; title = "File"; ObjectID = "81"; */ 42 | "81.title" = "File"; 43 | 44 | /* Class = "NSMenuItem"; title = "New"; ObjectID = "82"; */ 45 | "82.title" = "New"; 46 | 47 | /* Class = "NSMenuItem"; title = "File"; ObjectID = "83"; */ 48 | "83.title" = "File"; 49 | 50 | /* Class = "NSMenuItem"; title = "Revert to Saved"; ObjectID = "112"; */ 51 | "112.title" = "Revert to Saved"; 52 | 53 | /* Class = "NSMenuItem"; title = "Open Recent"; ObjectID = "124"; */ 54 | "124.title" = "Open Recent"; 55 | 56 | /* Class = "NSMenu"; title = "Open Recent"; ObjectID = "125"; */ 57 | "125.title" = "Open Recent"; 58 | 59 | /* Class = "NSMenuItem"; title = "Clear Menu"; ObjectID = "126"; */ 60 | "126.title" = "Clear Menu"; 61 | 62 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "129"; */ 63 | "129.title" = "Preferences…"; 64 | 65 | /* Class = "NSMenu"; title = "Services"; ObjectID = "130"; */ 66 | "130.title" = "Services"; 67 | 68 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "131"; */ 69 | "131.title" = "Services"; 70 | 71 | /* Class = "NSMenuItem"; title = "Hide Connecter"; ObjectID = "134"; */ 72 | "134.title" = "Hide Connecter"; 73 | 74 | /* Class = "NSMenuItem"; title = "Quit Connecter"; ObjectID = "136"; */ 75 | "136.title" = "Quit Connecter"; 76 | 77 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */ 78 | "145.title" = "Hide Others"; 79 | 80 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */ 81 | "150.title" = "Show All"; 82 | 83 | /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "195"; */ 84 | "195.title" = "Stop Speaking"; 85 | 86 | /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "196"; */ 87 | "196.title" = "Start Speaking"; 88 | 89 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "197"; */ 90 | "197.title" = "Copy"; 91 | 92 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "198"; */ 93 | "198.title" = "Select All"; 94 | 95 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "199"; */ 96 | "199.title" = "Cut"; 97 | 98 | /* Class = "NSMenu"; title = "Spelling and Grammar"; ObjectID = "200"; */ 99 | "200.title" = "Spelling and Grammar"; 100 | 101 | /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "201"; */ 102 | "201.title" = "Check Document Now"; 103 | 104 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "202"; */ 105 | "202.title" = "Delete"; 106 | 107 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "203"; */ 108 | "203.title" = "Paste"; 109 | 110 | /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "204"; */ 111 | "204.title" = "Show Spelling and Grammar"; 112 | 113 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "205"; */ 114 | "205.title" = "Edit"; 115 | 116 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "207"; */ 117 | "207.title" = "Undo"; 118 | 119 | /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "208"; */ 120 | "208.title" = "Find Next"; 121 | 122 | /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "209"; */ 123 | "209.title" = "Find…"; 124 | 125 | /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "210"; */ 126 | "210.title" = "Jump to Selection"; 127 | 128 | /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "211"; */ 129 | "211.title" = "Speech"; 130 | 131 | /* Class = "NSMenu"; title = "Speech"; ObjectID = "212"; */ 132 | "212.title" = "Speech"; 133 | 134 | /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "213"; */ 135 | "213.title" = "Find Previous"; 136 | 137 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "215"; */ 138 | "215.title" = "Redo"; 139 | 140 | /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "216"; */ 141 | "216.title" = "Spelling and Grammar"; 142 | 143 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "217"; */ 144 | "217.title" = "Edit"; 145 | 146 | /* Class = "NSMenuItem"; title = "Find"; ObjectID = "218"; */ 147 | "218.title" = "Find"; 148 | 149 | /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "219"; */ 150 | "219.title" = "Check Spelling While Typing"; 151 | 152 | /* Class = "NSMenu"; title = "Find"; ObjectID = "220"; */ 153 | "220.title" = "Find"; 154 | 155 | /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "221"; */ 156 | "221.title" = "Use Selection for Find"; 157 | 158 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "239"; */ 159 | "239.title" = "Zoom"; 160 | 161 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "295"; */ 162 | "295.title" = "View"; 163 | 164 | /* Class = "NSMenu"; title = "View"; ObjectID = "296"; */ 165 | "296.title" = "View"; 166 | 167 | /* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "297"; */ 168 | "297.title" = "Show Toolbar"; 169 | 170 | /* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "298"; */ 171 | "298.title" = "Customize Toolbar…"; 172 | 173 | /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "346"; */ 174 | "346.title" = "Check Grammar With Spelling"; 175 | 176 | /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "348"; */ 177 | "348.title" = "Substitutions"; 178 | 179 | /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "349"; */ 180 | "349.title" = "Substitutions"; 181 | 182 | /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "350"; */ 183 | "350.title" = "Smart Copy/Paste"; 184 | 185 | /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "351"; */ 186 | "351.title" = "Smart Quotes"; 187 | 188 | /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "354"; */ 189 | "354.title" = "Smart Links"; 190 | 191 | /* Class = "NSMenuItem"; title = "Format"; ObjectID = "375"; */ 192 | "375.title" = "Format"; 193 | 194 | /* Class = "NSMenu"; title = "Format"; ObjectID = "376"; */ 195 | "376.title" = "Format"; 196 | 197 | /* Class = "NSMenuItem"; title = "Font"; ObjectID = "377"; */ 198 | "377.title" = "Font"; 199 | 200 | /* Class = "NSMenu"; title = "Font"; ObjectID = "388"; */ 201 | "388.title" = "Font"; 202 | 203 | /* Class = "NSMenuItem"; title = "Show Fonts"; ObjectID = "389"; */ 204 | "389.title" = "Show Fonts"; 205 | 206 | /* Class = "NSMenuItem"; title = "Bold"; ObjectID = "390"; */ 207 | "390.title" = "Bold"; 208 | 209 | /* Class = "NSMenuItem"; title = "Italic"; ObjectID = "391"; */ 210 | "391.title" = "Italic"; 211 | 212 | /* Class = "NSMenuItem"; title = "Underline"; ObjectID = "392"; */ 213 | "392.title" = "Underline"; 214 | 215 | /* Class = "NSMenuItem"; title = "Bigger"; ObjectID = "394"; */ 216 | "394.title" = "Bigger"; 217 | 218 | /* Class = "NSMenuItem"; title = "Smaller"; ObjectID = "395"; */ 219 | "395.title" = "Smaller"; 220 | 221 | /* Class = "NSMenuItem"; title = "Kern"; ObjectID = "397"; */ 222 | "397.title" = "Kern"; 223 | 224 | /* Class = "NSMenuItem"; title = "Ligatures"; ObjectID = "398"; */ 225 | "398.title" = "Ligatures"; 226 | 227 | /* Class = "NSMenuItem"; title = "Baseline"; ObjectID = "399"; */ 228 | "399.title" = "Baseline"; 229 | 230 | /* Class = "NSMenuItem"; title = "Show Colors"; ObjectID = "401"; */ 231 | "401.title" = "Show Colors"; 232 | 233 | /* Class = "NSMenuItem"; title = "Copy Style"; ObjectID = "403"; */ 234 | "403.title" = "Copy Style"; 235 | 236 | /* Class = "NSMenuItem"; title = "Paste Style"; ObjectID = "404"; */ 237 | "404.title" = "Paste Style"; 238 | 239 | /* Class = "NSMenu"; title = "Baseline"; ObjectID = "405"; */ 240 | "405.title" = "Baseline"; 241 | 242 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "406"; */ 243 | "406.title" = "Use Default"; 244 | 245 | /* Class = "NSMenuItem"; title = "Superscript"; ObjectID = "407"; */ 246 | "407.title" = "Superscript"; 247 | 248 | /* Class = "NSMenuItem"; title = "Subscript"; ObjectID = "408"; */ 249 | "408.title" = "Subscript"; 250 | 251 | /* Class = "NSMenuItem"; title = "Raise"; ObjectID = "409"; */ 252 | "409.title" = "Raise"; 253 | 254 | /* Class = "NSMenuItem"; title = "Lower"; ObjectID = "410"; */ 255 | "410.title" = "Lower"; 256 | 257 | /* Class = "NSMenu"; title = "Ligatures"; ObjectID = "411"; */ 258 | "411.title" = "Ligatures"; 259 | 260 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "412"; */ 261 | "412.title" = "Use Default"; 262 | 263 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "413"; */ 264 | "413.title" = "Use None"; 265 | 266 | /* Class = "NSMenuItem"; title = "Use All"; ObjectID = "414"; */ 267 | "414.title" = "Use All"; 268 | 269 | /* Class = "NSMenu"; title = "Kern"; ObjectID = "415"; */ 270 | "415.title" = "Kern"; 271 | 272 | /* Class = "NSMenuItem"; title = "Use Default"; ObjectID = "416"; */ 273 | "416.title" = "Use Default"; 274 | 275 | /* Class = "NSMenuItem"; title = "Use None"; ObjectID = "417"; */ 276 | "417.title" = "Use None"; 277 | 278 | /* Class = "NSMenuItem"; title = "Tighten"; ObjectID = "418"; */ 279 | "418.title" = "Tighten"; 280 | 281 | /* Class = "NSMenuItem"; title = "Loosen"; ObjectID = "419"; */ 282 | "419.title" = "Loosen"; 283 | 284 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "450"; */ 285 | "450.title" = "Transformations"; 286 | 287 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "451"; */ 288 | "451.title" = "Transformations"; 289 | 290 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "452"; */ 291 | "452.title" = "Make Upper Case"; 292 | 293 | /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "454"; */ 294 | "454.title" = "Correct Spelling Automatically"; 295 | 296 | /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "457"; */ 297 | "457.title" = "Show Substitutions"; 298 | 299 | /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "460"; */ 300 | "460.title" = "Smart Dashes"; 301 | 302 | /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "462"; */ 303 | "462.title" = "Text Replacement"; 304 | 305 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "465"; */ 306 | "465.title" = "Make Lower Case"; 307 | 308 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "466"; */ 309 | "466.title" = "Capitalize"; 310 | 311 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "485"; */ 312 | "485.title" = "Paste and Match Style"; 313 | 314 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "490"; */ 315 | "490.title" = "Help"; 316 | 317 | /* Class = "NSMenu"; title = "Help"; ObjectID = "491"; */ 318 | "491.title" = "Help"; 319 | 320 | /* Class = "NSMenuItem"; title = "Connecter Help"; ObjectID = "492"; */ 321 | "492.title" = "Connecter Help"; 322 | 323 | /* Class = "NSMenuItem"; title = "Text"; ObjectID = "496"; */ 324 | "496.title" = "Text"; 325 | 326 | /* Class = "NSMenu"; title = "Text"; ObjectID = "497"; */ 327 | "497.title" = "Text"; 328 | 329 | /* Class = "NSMenuItem"; title = "Align Left"; ObjectID = "498"; */ 330 | "498.title" = "Align Left"; 331 | 332 | /* Class = "NSMenuItem"; title = "Center"; ObjectID = "499"; */ 333 | "499.title" = "Center"; 334 | 335 | /* Class = "NSMenuItem"; title = "Justify"; ObjectID = "500"; */ 336 | "500.title" = "Justify"; 337 | 338 | /* Class = "NSMenuItem"; title = "Align Right"; ObjectID = "501"; */ 339 | "501.title" = "Align Right"; 340 | 341 | /* Class = "NSMenuItem"; title = "Writing Direction"; ObjectID = "503"; */ 342 | "503.title" = "Writing Direction"; 343 | 344 | /* Class = "NSMenuItem"; title = "Show Ruler"; ObjectID = "505"; */ 345 | "505.title" = "Show Ruler"; 346 | 347 | /* Class = "NSMenuItem"; title = "Copy Ruler"; ObjectID = "506"; */ 348 | "506.title" = "Copy Ruler"; 349 | 350 | /* Class = "NSMenuItem"; title = "Paste Ruler"; ObjectID = "507"; */ 351 | "507.title" = "Paste Ruler"; 352 | 353 | /* Class = "NSMenu"; title = "Writing Direction"; ObjectID = "508"; */ 354 | "508.title" = "Writing Direction"; 355 | 356 | /* Class = "NSMenuItem"; title = "Paragraph"; ObjectID = "509"; */ 357 | "509.title" = "Paragraph"; 358 | 359 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "510"; */ 360 | "510.title" = "\tDefault"; 361 | 362 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "511"; */ 363 | "511.title" = "\tLeft to Right"; 364 | 365 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "512"; */ 366 | "512.title" = "\tRight to Left"; 367 | 368 | /* Class = "NSMenuItem"; title = "Selection"; ObjectID = "514"; */ 369 | "514.title" = "Selection"; 370 | 371 | /* Class = "NSMenuItem"; title = "\tDefault"; ObjectID = "515"; */ 372 | "515.title" = "\tDefault"; 373 | 374 | /* Class = "NSMenuItem"; title = "\tLeft to Right"; ObjectID = "516"; */ 375 | "516.title" = "\tLeft to Right"; 376 | 377 | /* Class = "NSMenuItem"; title = "\tRight to Left"; ObjectID = "517"; */ 378 | "517.title" = "\tRight to Left"; 379 | 380 | /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "534"; */ 381 | "534.title" = "Find and Replace…"; 382 | -------------------------------------------------------------------------------- /Connecter/en.lproj/RMConnecterWindow.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSTextFieldCell"; title = "Get started by downloading the metadata for an existing app from iTunes Connect. You’ll need to know the SKU in order to download metadata."; ObjectID = "0Fq-U9-mJS"; */ 3 | "0Fq-U9-mJS.title" = "Get started by downloading the metadata for an existing app from iTunes Connect. You’ll need to know the SKU in order to download metadata."; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Waiting…"; ObjectID = "2CU-si-Gz2"; */ 6 | "2CU-si-Gz2.title" = "Waiting…"; 7 | 8 | /* Class = "NSTextFieldCell"; placeholderString = "me@developer.com"; ObjectID = "2Xt-OJ-52M"; */ 9 | "2Xt-OJ-52M.placeholderString" = "me@developer.com"; 10 | 11 | /* Class = "NSBox"; title = "Box"; ObjectID = "2d1-4k-Fny"; */ 12 | "2d1-4k-Fny.title" = "Box"; 13 | 14 | /* Class = "NSTextFieldCell"; title = "Submit to iTunes Connect"; ObjectID = "8I0-0d-deA"; */ 15 | "8I0-0d-deA.title" = "Submit to iTunes Connect"; 16 | 17 | /* Class = "NSButtonCell"; title = "Verify"; ObjectID = "HQR-0U-lnJ"; */ 18 | "HQR-0U-lnJ.title" = "Verify"; 19 | 20 | /* Class = "NSWindow"; title = "Connecter"; ObjectID = "HZn-zH-jiB"; */ 21 | "HZn-zH-jiB.title" = "Connecter"; 22 | 23 | /* Class = "NSButtonCell"; title = "Submit"; ObjectID = "Mfg-SV-EY5"; */ 24 | "Mfg-SV-EY5.title" = "Submit"; 25 | 26 | /* Class = "IBBindingConnection"; ibShadowedIsNilPlaceholder = "me@developer.com"; ObjectID = "Q6n-HH-aGh"; */ 27 | "Q6n-HH-aGh.ibShadowedIsNilPlaceholder" = "me@developer.com"; 28 | 29 | /* Class = "NSTextFieldCell"; title = "Download from iTunes Connect"; ObjectID = "TM4-3a-0D6"; */ 30 | "TM4-3a-0D6.title" = "Download from iTunes Connect"; 31 | 32 | /* Class = "NSBox"; title = "Box"; ObjectID = "V2f-1y-Jxv"; */ 33 | "V2f-1y-Jxv.title" = "Box"; 34 | 35 | /* Class = "NSTextFieldCell"; placeholderString = "Enter a SKU from iTunes Connect"; ObjectID = "bJr-Sf-kip"; */ 36 | "bJr-Sf-kip.placeholderString" = "Enter a SKU from iTunes Connect"; 37 | 38 | /* Class = "NSButtonCell"; title = "Download"; ObjectID = "cdm-fF-b09"; */ 39 | "cdm-fF-b09.title" = "Download"; 40 | 41 | /* Class = "NSTextFieldCell"; title = "iTunes Connect Username:"; ObjectID = "fru-rP-igj"; */ 42 | "fru-rP-igj.title" = "iTunes Connect Username:"; 43 | 44 | /* Class = "NSTextFieldCell"; title = "iTunes Connect Password:"; ObjectID = "oCh-09-a4j"; */ 45 | "oCh-09-a4j.title" = "iTunes Connect Password:"; 46 | 47 | /* Class = "NSTextFieldCell"; title = "Before submitting an iTunes Connect package, you should verify the contents of the package."; ObjectID = "vlG-7g-G9O"; */ 48 | "vlG-7g-G9O.title" = "Before submitting an iTunes Connect package, you should verify the contents of the package."; 49 | -------------------------------------------------------------------------------- /Connecter/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Connecter 4 | // 5 | // Created by Nik Fletcher on 31/01/2014. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char **argv) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /ConnecterTests/ConnecterTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.realmacsoftware.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ConnecterTests/RMMetaDataModelTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ConnecterTests.m 3 | // ConnecterTests 4 | // 5 | // Created by Nik Fletcher on 31/01/2014. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppMetaData.h" 10 | #import "RMAppVersion.h" 11 | #import "RMAppLocale.h" 12 | #import "RMAppScreenshot.h" 13 | #import "RMAppScreenshotTypeValueTransformer.h" 14 | 15 | #import 16 | 17 | @interface RMMetaDataModelTests : XCTestCase 18 | @property (nonatomic, strong) RMAppMetaData *metaData; 19 | @end 20 | 21 | @implementation RMMetaDataModelTests 22 | 23 | - (void)setUp 24 | { 25 | [super setUp]; 26 | 27 | // read xml file 28 | NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; 29 | NSData *xmlData = [NSData dataWithContentsOfFile:[testBundle pathForResource:@"testmetadata" ofType:@"xml"]]; 30 | NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:xmlData options:0 error:nil]; 31 | self.metaData = [[RMAppMetaData alloc] initWithXMLElement:document.rootElement]; 32 | } 33 | 34 | - (void)tearDown 35 | { 36 | // Put teardown code here. This method is called after the invocation of each test method in the class. 37 | [super tearDown]; 38 | } 39 | 40 | #pragma mark tests 41 | 42 | - (void)testAppMetaData 43 | { 44 | RMAppMetaData *metaData = self.metaData; 45 | 46 | XCTAssertEqualObjects(metaData.metadataToken, @"someToken"); 47 | XCTAssertEqualObjects(metaData.provider, @"someProvider"); 48 | XCTAssertEqualObjects(metaData.teamID, @"someTeamID-01"); 49 | XCTAssertEqualObjects(metaData.vendorID, @"test"); 50 | XCTAssertEqualObjects(@(metaData.versions.count), @(1)); 51 | } 52 | 53 | - (void)testAppVersion 54 | { 55 | RMAppVersion *version = self.metaData.versions.firstObject; 56 | 57 | XCTAssertEqualObjects(version.versionString, @"2.0"); 58 | XCTAssertEqualObjects(@(version.locales.count), @(2)); 59 | } 60 | 61 | - (void)testAppLocale 62 | { 63 | RMAppVersion *version = self.metaData.versions.firstObject; 64 | 65 | RMAppLocale *deLocale = version.locales.firstObject; 66 | XCTAssertEqualObjects(@(deLocale.shouldDeleteLocale), @(NO)); 67 | XCTAssertEqualObjects(deLocale.localeName, @"de-DE"); 68 | XCTAssertEqualObjects(deLocale.title, @"german name"); 69 | XCTAssertEqualObjects(deLocale.appDescription, @"Berlin, du kannst so schön hässlich sein. So dreckig und grau."); 70 | XCTAssertEqualObjects(deLocale.keywords, (@[@"berlin", @"munich"])); 71 | XCTAssertEqualObjects(deLocale.whatsNew, @"Die Eröffnung des Berliner Flughafens verzögert sich."); 72 | XCTAssertEqualObjects(deLocale.softwareURL, @""); 73 | XCTAssertEqualObjects(deLocale.supportURL, @""); 74 | XCTAssertEqualObjects(deLocale.privacyURL, @""); 75 | XCTAssertEqualObjects(@(deLocale.screenshots.count), @(3)); 76 | 77 | RMAppLocale *enLocale = version.locales.lastObject; 78 | XCTAssertEqualObjects(@(enLocale.shouldDeleteLocale), @(NO)); 79 | XCTAssertEqualObjects(enLocale.localeName, @"en-US"); 80 | XCTAssertEqualObjects(enLocale.title, @"english title"); 81 | XCTAssertEqualObjects(enLocale.appDescription, @"Just some random text."); 82 | XCTAssertEqualObjects(enLocale.keywords, (@[@"nyc", @"sf"])); 83 | XCTAssertEqualObjects(enLocale.whatsNew, @"yep thats new!"); 84 | XCTAssertEqualObjects(enLocale.softwareURL, @"www.google.com"); 85 | XCTAssertEqualObjects(enLocale.supportURL, @"www.support.url"); 86 | XCTAssertEqualObjects(enLocale.privacyURL, @""); 87 | XCTAssertEqualObjects(@(enLocale.screenshots.count), @(1)); 88 | } 89 | 90 | - (void)testAppScreenshots 91 | { 92 | RMAppVersion *version = self.metaData.versions.firstObject; 93 | NSArray *deScreenshots = [version.locales[0] screenshots]; 94 | NSArray *enScreenshots = [version.locales[1] screenshots]; 95 | 96 | RMAppScreenshot *deScreenshot = deScreenshots[0]; 97 | XCTAssertEqualObjects(@(deScreenshot.displayTarget), @(RMAppScreenshotTypeiPhone35inch)); 98 | XCTAssertEqualObjects(@(deScreenshot.position), @(1)); 99 | XCTAssertEqualObjects(deScreenshot.filename, @"de-DE1704.png"); 100 | XCTAssertEqualObjects(@(deScreenshot.size), @(172190)); 101 | XCTAssertEqualObjects(deScreenshot.checksum, @"fb6b243baf13426a88f8382ab79283c2"); 102 | XCTAssertEqualObjects(deScreenshot.checksumType, @"md5"); 103 | 104 | deScreenshot = deScreenshots[1]; 105 | XCTAssertEqualObjects(@(deScreenshot.displayTarget), @(RMAppScreenshotTypeiPad)); 106 | XCTAssertEqualObjects(@(deScreenshot.position), @(1)); 107 | XCTAssertEqualObjects(deScreenshot.filename, @"de-DE1721.png"); 108 | XCTAssertEqualObjects(@(deScreenshot.size), @(655892)); 109 | XCTAssertEqualObjects(deScreenshot.checksum, @"bcd2598924a7d0309d27dcdfa3cc2149"); 110 | XCTAssertEqualObjects(deScreenshot.checksumType, @"md5"); 111 | 112 | deScreenshot = deScreenshots[2]; 113 | XCTAssertEqualObjects(@(deScreenshot.displayTarget), @(RMAppScreenshotTypeiPhone4inch)); 114 | XCTAssertEqualObjects(@(deScreenshot.position), @(1)); 115 | XCTAssertEqualObjects(deScreenshot.filename, @"de-DE1712.png"); 116 | XCTAssertEqualObjects(@(deScreenshot.size), @(186242)); 117 | XCTAssertEqualObjects(deScreenshot.checksum, @"7347462d3b543f33ccc6ba3c790602c3"); 118 | XCTAssertEqualObjects(deScreenshot.checksumType, @"md5"); 119 | 120 | RMAppScreenshot *enScreenshot = enScreenshots[0]; 121 | XCTAssertEqualObjects(@(enScreenshot.displayTarget), @(RMAppScreenshotTypeiPhone35inch)); 122 | XCTAssertEqualObjects(@(enScreenshot.position), @(1)); 123 | XCTAssertEqualObjects(enScreenshot.filename, @"1.png"); 124 | XCTAssertEqualObjects(@(enScreenshot.size), @(168337)); 125 | XCTAssertEqualObjects(enScreenshot.checksum, @"957cce7ad063f79c8beffdc43d2e9e6b"); 126 | XCTAssertEqualObjects(enScreenshot.checksumType, @"md5"); 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /ConnecterTests/RMValueTransformerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RMValueTransformerTests.m 3 | // Connecter 4 | // 5 | // Created by Markus Emrich on 22.02.14. 6 | // Copyright (c) 2014 Realmac Software. All rights reserved. 7 | // 8 | 9 | #import "RMAppScreenshotTypeValueTransformer.h" 10 | #import "RMByteValueTransformer.h" 11 | 12 | #import 13 | 14 | @interface RMValueTransformerTests : XCTestCase 15 | 16 | @end 17 | 18 | 19 | @implementation RMValueTransformerTests 20 | 21 | - (void)testAppScreenshotTypeValueTransformer; 22 | { 23 | RMAppScreenshotTypeValueTransformer *transformer = [[RMAppScreenshotTypeValueTransformer alloc] init]; 24 | 25 | XCTAssertEqualObjects([transformer transformedValue:@"iOS-3.5-in"], @(RMAppScreenshotTypeiPhone35inch)); 26 | XCTAssertEqualObjects([transformer transformedValue:@"iOS-4-in"], @(RMAppScreenshotTypeiPhone4inch)); 27 | XCTAssertEqualObjects([transformer transformedValue:@"iOS-iPad"], @(RMAppScreenshotTypeiPad)); 28 | XCTAssertEqualObjects([transformer transformedValue:@"Mac"], @(RMAppScreenshotTypeMac)); 29 | 30 | XCTAssertEqualObjects([transformer transformedValue:@(0)], nil); 31 | XCTAssertEqualObjects([transformer transformedValue:@(1)], nil); 32 | XCTAssertEqualObjects([transformer transformedValue:nil], nil); 33 | XCTAssertEqualObjects([transformer transformedValue:@"text"], nil); 34 | } 35 | 36 | - (void)testAppScreenshotTypeValueTransformerReverse; 37 | { 38 | RMAppScreenshotTypeValueTransformer *transformer = [[RMAppScreenshotTypeValueTransformer alloc] init]; 39 | 40 | XCTAssertEqualObjects([transformer reverseTransformedValue:@(RMAppScreenshotTypeiPhone35inch)], @"iOS-3.5-in"); 41 | XCTAssertEqualObjects([transformer reverseTransformedValue:@(RMAppScreenshotTypeiPhone4inch)], @"iOS-4-in"); 42 | XCTAssertEqualObjects([transformer reverseTransformedValue:@(RMAppScreenshotTypeiPad)], @"iOS-iPad"); 43 | XCTAssertEqualObjects([transformer reverseTransformedValue:@(RMAppScreenshotTypeMac)], @"Mac"); 44 | 45 | XCTAssertEqualObjects([transformer reverseTransformedValue:@(123)], nil); 46 | XCTAssertEqualObjects([transformer reverseTransformedValue:@(-1)], nil); 47 | XCTAssertEqualObjects([transformer reverseTransformedValue:nil], nil); 48 | XCTAssertEqualObjects([transformer reverseTransformedValue:@"text"], nil); 49 | } 50 | 51 | - (void)testByteValueTransformer; 52 | { 53 | RMByteValueTransformer *transformer = [[RMByteValueTransformer alloc] init]; 54 | 55 | XCTAssertEqualObjects([transformer transformedValue:@(0)], @"0 b"); 56 | XCTAssertEqualObjects([transformer transformedValue:@(-121421)], @"0 b"); 57 | XCTAssertEqualObjects([transformer transformedValue:@(500)], @"500 b"); 58 | XCTAssertEqualObjects([transformer transformedValue:@(1520)], @"2 kb"); 59 | XCTAssertEqualObjects([transformer transformedValue:@(258213)], @"258 kb"); 60 | XCTAssertEqualObjects([transformer transformedValue:@(4893252)], @"4.9 MB"); 61 | XCTAssertEqualObjects([transformer transformedValue:@(83249234)], @"83.2 MB"); 62 | XCTAssertEqualObjects([transformer transformedValue:@(100000000)], @"100.0 MB"); 63 | 64 | XCTAssertEqualObjects([transformer transformedValue:nil], nil); 65 | XCTAssertEqualObjects([transformer transformedValue:@"text"], nil); 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /ConnecterTests/TestData/testmetadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | someToken 4 | someProvider 5 | someTeamID-01 6 | 7 | test 8 | 9 | 10 | 11 | 12 | 13 | german name 14 | Berlin, du kannst so schön hässlich sein. So dreckig und grau. 15 | Die Eröffnung des Berliner Flughafens verzögert sich. 16 | 17 | 18 | 19 | 20 | berlin 21 | munich 22 | 23 | 24 | 25 | de-DE1704.png 26 | 172190 27 | fb6b243baf13426a88f8382ab79283c2 28 | 29 | 30 | de-DE1721.png 31 | 655892 32 | bcd2598924a7d0309d27dcdfa3cc2149 33 | 34 | 35 | de-DE1712.png 36 | 186242 37 | 7347462d3b543f33ccc6ba3c790602c3 38 | 39 | 40 | 41 | 42 | english title 43 | Just some random text. 44 | yep thats new! 45 | www.google.com 46 | www.support.url 47 | 48 | 49 | nyc 50 | sf 51 | 52 | 53 | 54 | 1.png 55 | 168337 56 | 957cce7ad063f79c8beffdc43d2e9e6b 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /ConnecterTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Connecter 2 | =========== 3 | 4 | Connecter is an OS X app to help you interact with iTunes Connect. It allows you to **download, edit & update app metadata** from and for iTunes Connect. If you’ve got Xcode installed, you’re all sorted and ready to use this with your Apple ID and password. 5 | 6 | Connecter is a work-in-progress, built quickly to allow us to improve our own iTunes Connect workflow. For the more seasoned developers amongst us, looking back at the commit history you can probably tell that the codebase was written in a morning by a Product Manager :-). 7 | 8 | We’re open-sourcing it because we think it might be useful to other iOS and OS X developers, and because as we add more features to the app it’ll save all of us time. 9 | 10 | If you’ve got any feedback, feel free to open an Issue, submit a Pull Request or contact nikf ([@nikf](https://twitter.com/nikf)) or jaydee3 ([@jaydee3](https://twitter.com/jaydee3)). Thanks to [Damien DeVille](https://twitter.com/damiendeville) for the code review. 11 | 12 | ## Screenshots 13 | 14 | ![Screenshot](Assets/screenshots.png) 15 | 16 | ## Contributors 17 | 18 | - [Nik Fletcher](https://twitter.com/nikf), Realmac Software 19 | - [Damien DeVille](https://twitter.com/damiendeville), Realmac Software 20 | - [Markus Emrich](https://twitter.com/jaydee3), nxtbgthng GmbH 21 | 22 | ## Points of Interest 23 | 24 | - Connecter is sandboxed. However as the iTMSTransporter binary requires read-write access to ~/.itmstransporter/ Connecter has a temporary entitlement for this location. 25 | - We won’t be suppling a downloadable app for Connecter. Given the sensitive nature of your iTunes Connect credentials, we’re providing the fully-auditable source code that you can then build. 26 | 27 | ## Still To-Do 28 | 29 | **Core:** 30 | 31 | - Better presentation of feedback from the Transporter binary. 32 | - Support retrieval of In-App Purchase and Game Center metadata. 33 | - Persist login details in the Keychain. 34 | - Error handling. 35 | 36 | **Editor:** 37 | 38 | - Make locales list editable (remove/add locales) 39 | - Reuse existing NSFileWrapper (don't rewrite screenshots, if no changes happened) 40 | - Make the "Download from ITC" part its own window (shown when creating a new file) 41 | - Make the verify/submit part its own window (shown when verify/submit is selected in the menu, while a document is open) 42 | - Move ITC credentials into a separate settings window 43 | - ~~Add a document icon~~ be91b16 44 | - Support drag & drop for multiple screenshots at once, automatically assign correct device types 45 | - Support drag & drop reordering of screenshots 46 | - Visible Input validation (required fields, Max length. especially for keywords, screenshot size/format) 47 | 48 | 49 | ## License 50 | 51 | Connecter is released under the MIT License: 52 | 53 | The MIT License (MIT) 54 | 55 | Copyright (c) 2014 Realmac Software & Contributors 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy 58 | of this software and associated documentation files (the "Software"), to deal 59 | in the Software without restriction, including without limitation the rights 60 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | copies of the Software, and to permit persons to whom the Software is 62 | furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in all 65 | copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 70 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 71 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 72 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 73 | SOFTWARE. 74 | --------------------------------------------------------------------------------