├── .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 |
165 |
166 |
167 |
168 |
169 |
170 | NSNegateBoolean
171 |
172 |
173 |
174 |
175 |
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 |
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 | 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 |
117 |
118 |
119 |
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 |
158 |
159 |
160 |
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 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
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 | 
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 |
--------------------------------------------------------------------------------