mirror of
https://github.com/somegeekintn/SimDirs.git
synced 2026-04-27 14:57:40 +00:00
restructure, clean up and try to do a better job tracking down locations
This commit is contained in:
parent
99fc6abf0b
commit
3e8236883f
12 changed files with 809 additions and 296 deletions
|
|
@ -12,6 +12,9 @@
|
|||
C9194D3019C09A03004178EB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9194D2F19C09A03004178EB /* Images.xcassets */; };
|
||||
C9194D3319C09A03004178EB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9194D3119C09A03004178EB /* MainMenu.xib */; };
|
||||
C9194D3F19C09A03004178EB /* SimDirsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C9194D3E19C09A03004178EB /* SimDirsTests.m */; };
|
||||
C9B7078A1A03A5C70001CB77 /* QSSimAppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = C9B707891A03A5C70001CB77 /* QSSimAppInfo.m */; };
|
||||
C9B707901A03A63E0001CB77 /* QSSimDeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = C9B7078F1A03A63E0001CB77 /* QSSimDeviceInfo.m */; };
|
||||
C9B707931A03B6C30001CB77 /* QSSimViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C9B707921A03B6C30001CB77 /* QSSimViewController.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
|
@ -35,6 +38,12 @@
|
|||
C9194D3819C09A03004178EB /* SimDirsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimDirsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C9194D3D19C09A03004178EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
C9194D3E19C09A03004178EB /* SimDirsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimDirsTests.m; sourceTree = "<group>"; };
|
||||
C9B707881A03A5C70001CB77 /* QSSimAppInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QSSimAppInfo.h; sourceTree = "<group>"; };
|
||||
C9B707891A03A5C70001CB77 /* QSSimAppInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QSSimAppInfo.m; sourceTree = "<group>"; };
|
||||
C9B7078E1A03A63E0001CB77 /* QSSimDeviceInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QSSimDeviceInfo.h; sourceTree = "<group>"; };
|
||||
C9B7078F1A03A63E0001CB77 /* QSSimDeviceInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QSSimDeviceInfo.m; sourceTree = "<group>"; };
|
||||
C9B707911A03B6C30001CB77 /* QSSimViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QSSimViewController.h; sourceTree = "<group>"; };
|
||||
C9B707921A03B6C30001CB77 /* QSSimViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QSSimViewController.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -78,6 +87,12 @@
|
|||
children = (
|
||||
C9194D2C19C09A03004178EB /* AppDelegate.h */,
|
||||
C9194D2D19C09A03004178EB /* AppDelegate.m */,
|
||||
C9B707881A03A5C70001CB77 /* QSSimAppInfo.h */,
|
||||
C9B707891A03A5C70001CB77 /* QSSimAppInfo.m */,
|
||||
C9B7078E1A03A63E0001CB77 /* QSSimDeviceInfo.h */,
|
||||
C9B7078F1A03A63E0001CB77 /* QSSimDeviceInfo.m */,
|
||||
C9B707911A03B6C30001CB77 /* QSSimViewController.h */,
|
||||
C9B707921A03B6C30001CB77 /* QSSimViewController.m */,
|
||||
C9194D2F19C09A03004178EB /* Images.xcassets */,
|
||||
C9194D3119C09A03004178EB /* MainMenu.xib */,
|
||||
C9194D2819C09A03004178EB /* Supporting Files */,
|
||||
|
|
@ -211,6 +226,9 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C9194D2E19C09A03004178EB /* AppDelegate.m in Sources */,
|
||||
C9B707901A03A63E0001CB77 /* QSSimDeviceInfo.m in Sources */,
|
||||
C9B7078A1A03A5C70001CB77 /* QSSimAppInfo.m in Sources */,
|
||||
C9B707931A03B6C30001CB77 /* QSSimViewController.m in Sources */,
|
||||
C9194D2B19C09A03004178EB /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -398,6 +416,7 @@
|
|||
C9194D4419C09A03004178EB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
C9194D4519C09A03004178EB /* Build configuration list for PBXNativeTarget "SimDirsTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
|
|
@ -406,6 +425,7 @@
|
|||
C9194D4719C09A03004178EB /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "QSSimDeviceInfo.h"
|
||||
#import "QSSimViewController.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
|
||||
@property (nonatomic, weak) IBOutlet NSWindow *window;
|
||||
@property (nonatomic, weak) IBOutlet NSOutlineView *locationOutline;
|
||||
@property (nonatomic, strong) NSMutableArray *simLocations;
|
||||
|
||||
@end
|
||||
|
||||
|
|
@ -20,274 +20,11 @@
|
|||
|
||||
- (void) applicationDidFinishLaunching: (NSNotification *) inNotification
|
||||
{
|
||||
[self.locationOutline setDoubleAction: @selector(handleRowSelect:)];
|
||||
[self updateLocations];
|
||||
}
|
||||
|
||||
- (void) applicationWillTerminate: (NSNotification *) inNotification
|
||||
-(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) inSender
|
||||
{
|
||||
}
|
||||
|
||||
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) updateLocations
|
||||
{
|
||||
[self discoverSimLocations];
|
||||
[self.locationOutline reloadData];
|
||||
}
|
||||
|
||||
- (void) discoverSimLocations
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *libraryDirURL = [[fileManager URLsForDirectory: NSLibraryDirectory inDomains: NSUserDomainMask] firstObject];
|
||||
|
||||
self.simLocations = [NSMutableArray array];
|
||||
|
||||
if (libraryDirURL != nil) {
|
||||
libraryDirURL = [libraryDirURL URLByAppendingPathComponent: @"Developer/CoreSimulator/Devices"];
|
||||
if (libraryDirURL != nil) {
|
||||
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtURL: libraryDirURL includingPropertiesForKeys: nil options: NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles errorHandler: nil];
|
||||
NSURL *baseInfoURL;
|
||||
|
||||
while ((baseInfoURL = [dirEnum nextObject])) {
|
||||
NSURL *deviceInfoURL = [baseInfoURL URLByAppendingPathComponent: @"device.plist"];
|
||||
|
||||
if (deviceInfoURL != nil && [fileManager fileExistsAtPath: [deviceInfoURL path]]) {
|
||||
NSData *plistData = [NSData dataWithContentsOfURL: deviceInfoURL];
|
||||
|
||||
if (plistData != nil) {
|
||||
NSDictionary *plistInfo;
|
||||
|
||||
plistInfo = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil];
|
||||
if (plistInfo != nil) {
|
||||
NSMutableDictionary *deviceInfo = [NSMutableDictionary dictionary];
|
||||
NSString *name = plistInfo[@"name"];
|
||||
NSString *runtime = plistInfo[@"runtime"];
|
||||
NSRange runtimeRange;
|
||||
|
||||
runtimeRange = [runtime rangeOfString: @"iOS.*" options: NSRegularExpressionSearch];
|
||||
if (runtimeRange.location != NSNotFound) {
|
||||
NSArray *versionComponents = [[runtime substringWithRange: runtimeRange] componentsSeparatedByString: @"-"];
|
||||
|
||||
deviceInfo[@"path"] = [baseInfoURL path];
|
||||
deviceInfo[@"title"] = [NSString stringWithFormat: @"%@: %@ %@.%@", name, versionComponents[0], versionComponents[1], versionComponents[2]];
|
||||
[self updateDeviceInfoForApps: deviceInfo];
|
||||
[self updateDeviceInfoForAppsFromLogs: deviceInfo];
|
||||
[self.simLocations addObject: deviceInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) addPath: (NSString *) inPath
|
||||
withTitle: (NSString *) inTitle
|
||||
withBundleInfo: (NSMutableDictionary *) inBundleInfo
|
||||
{
|
||||
NSMutableArray *pathList = inBundleInfo[@"children"];
|
||||
NSMutableDictionary *pathInfo;
|
||||
NSInteger pathIndex;
|
||||
|
||||
if (pathList == nil) {
|
||||
pathList = [NSMutableArray array];
|
||||
inBundleInfo[@"children"] = pathList;
|
||||
}
|
||||
|
||||
pathIndex = [pathList indexOfObjectPassingTest: ^BOOL(id inObject, NSUInteger inIndex, BOOL *outStop) {
|
||||
*outStop = [inObject[@"path"] isEqualToString: inPath];
|
||||
return *outStop;
|
||||
}];
|
||||
|
||||
if (pathIndex == NSNotFound) {
|
||||
pathInfo = [NSMutableDictionary dictionary];
|
||||
pathInfo[@"title"] = inTitle;
|
||||
pathInfo[@"path"] = inPath;
|
||||
[pathList addObject: pathInfo];
|
||||
}
|
||||
// we already have this path
|
||||
}
|
||||
|
||||
- (void) addPath: (NSString *) inPath
|
||||
withTitle: (NSString *) inTitle
|
||||
forBundleID: (NSString *) inBundleID
|
||||
withDeviceInfo: (NSMutableDictionary *) inDeviceInfo
|
||||
{
|
||||
NSMutableArray *bundleList = inDeviceInfo[@"children"];
|
||||
NSMutableDictionary *bundleInfo;
|
||||
NSInteger bundleIndex;
|
||||
|
||||
if (bundleList == nil) {
|
||||
bundleList = [NSMutableArray array];
|
||||
inDeviceInfo[@"children"] = bundleList;
|
||||
}
|
||||
|
||||
bundleIndex = [bundleList indexOfObjectPassingTest: ^BOOL(id inObject, NSUInteger inIndex, BOOL *outStop) {
|
||||
*outStop = [inObject[@"title"] isEqualToString: inBundleID];
|
||||
return *outStop;
|
||||
}];
|
||||
if (bundleIndex == NSNotFound) {
|
||||
bundleInfo = [NSMutableDictionary dictionary];
|
||||
bundleInfo[@"title"] = inBundleID;
|
||||
[bundleList addObject: bundleInfo];
|
||||
}
|
||||
else {
|
||||
bundleInfo = [bundleList objectAtIndex: bundleIndex];
|
||||
}
|
||||
|
||||
[self addPath: inPath withTitle: inTitle withBundleInfo: bundleInfo];
|
||||
}
|
||||
|
||||
- (void) updateDeviceInfoForApps: (NSMutableDictionary *) inDeviceInfo
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *launchMapInfoURL = [[NSURL alloc] initFileURLWithPath: inDeviceInfo[@"path"]];
|
||||
|
||||
launchMapInfoURL = [launchMapInfoURL URLByAppendingPathComponent: @"data/Library/MobileInstallation/LastLaunchServicesMap.plist"];
|
||||
if (launchMapInfoURL != nil && [fileManager fileExistsAtPath: [launchMapInfoURL path]]) {
|
||||
NSData *plistData = [NSData dataWithContentsOfURL: launchMapInfoURL];
|
||||
NSDictionary *launchInfo;
|
||||
NSDictionary *userInfo;
|
||||
|
||||
launchInfo = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil];
|
||||
userInfo = launchInfo[@"User"];
|
||||
|
||||
for (NSString *bundleID in userInfo) {
|
||||
NSDictionary *appInfo = userInfo[bundleID];
|
||||
|
||||
if (appInfo != nil) {
|
||||
NSArray *pathKeys = @[
|
||||
@{ @"title" : @"Bundle Location", @"pathKey" : @"BundleContainer" },
|
||||
@{ @"title" : @"Sandbox Location", @"pathKey" : @"Container" } ];
|
||||
|
||||
[pathKeys enumerateObjectsUsingBlock: ^(id inObject, NSUInteger inIndex, BOOL *outStop) {
|
||||
NSString *appPath = appInfo[inObject[@"pathKey"]];
|
||||
|
||||
//NSLog(@"test %@ - %@", appPath, [fileManager fileExistsAtPath: appPath] ? @"YES" : @"NO");
|
||||
if (appPath != nil && [fileManager fileExistsAtPath: appPath]) {
|
||||
[self addPath: appPath withTitle: inObject[@"title"] forBundleID: bundleID withDeviceInfo: inDeviceInfo];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateDeviceInfoForAppsFromLogs: (NSMutableDictionary *) inDeviceInfo
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *installLogURL = [[NSURL alloc] initFileURLWithPath: inDeviceInfo[@"path"]];
|
||||
|
||||
installLogURL = [installLogURL URLByAppendingPathComponent: @"data/Library/Logs/MobileInstallation/mobile_installation.log.0"];
|
||||
if (installLogURL != nil && [fileManager fileExistsAtPath: [installLogURL path]]) {
|
||||
NSString *installLog = [[NSString alloc] initWithContentsOfURL: installLogURL usedEncoding: nil error: nil];
|
||||
|
||||
if (installLog != nil) {
|
||||
NSRange logMentionRange;
|
||||
|
||||
for (NSString *line in [installLog componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]) {
|
||||
logMentionRange = [line rangeOfString: @"Made container live for "];
|
||||
if (logMentionRange.location != NSNotFound) {
|
||||
NSArray *installParts = [[line substringFromIndex: logMentionRange.location + logMentionRange.length] componentsSeparatedByString: @" "];
|
||||
|
||||
if ([installParts count] == 3) { // expecting com.foo.bar at UUID
|
||||
NSString *bundleID = [installParts objectAtIndex: 0];
|
||||
|
||||
if ([bundleID rangeOfString: @"com.apple"].location == NSNotFound) {
|
||||
NSString *path = [installParts objectAtIndex: 2];
|
||||
|
||||
if (path != nil && [fileManager fileExistsAtPath: path]) {
|
||||
NSString *pathTitle;
|
||||
|
||||
if ([path rangeOfString: @"Data/Application"].location != NSNotFound) {
|
||||
pathTitle = @"Sandbox Location";
|
||||
}
|
||||
else if ([path rangeOfString: @"Bundle/Application"].location != NSNotFound) {
|
||||
pathTitle = @"Bundle Location";
|
||||
}
|
||||
else {
|
||||
pathTitle = @"???";
|
||||
}
|
||||
|
||||
[self addPath: path withTitle: pathTitle forBundleID: bundleID withDeviceInfo: inDeviceInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Handlers
|
||||
|
||||
- (IBAction) handleRowSelect: (id) inSender
|
||||
{
|
||||
if (inSender == self.locationOutline) {
|
||||
id item = [self.locationOutline itemAtRow: [self.locationOutline clickedRow]];
|
||||
|
||||
if (item != nil) {
|
||||
if (item[@"path"] != nil) {
|
||||
NSURL *itemPathURL = [[NSURL alloc] initFileURLWithPath: item[@"path"]];
|
||||
|
||||
if (itemPathURL != nil) {
|
||||
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[ itemPathURL ]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ([self.locationOutline isItemExpanded: item]) {
|
||||
[self.locationOutline collapseItem: item];
|
||||
}
|
||||
else {
|
||||
[self.locationOutline expandItem: item];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction) handleUpdate: (id) inSender
|
||||
{
|
||||
[self updateLocations];
|
||||
}
|
||||
|
||||
#pragma mark - NSOutlineViewDataSource
|
||||
|
||||
- (NSInteger) outlineView: (NSOutlineView *) inOutlineView
|
||||
numberOfChildrenOfItem: (id) inItem
|
||||
{
|
||||
NSArray *target = inItem == nil ? self.simLocations : inItem[@"children"];
|
||||
|
||||
return [target count];
|
||||
}
|
||||
|
||||
- (id) outlineView: (NSOutlineView *) inOutlineView
|
||||
child: (NSInteger) inIndex
|
||||
ofItem: (id) inItem
|
||||
{
|
||||
NSArray *target = inItem == nil ? self.simLocations : inItem[@"children"];
|
||||
|
||||
return [target objectAtIndex: inIndex];
|
||||
}
|
||||
|
||||
- (BOOL) outlineView: (NSOutlineView *) inOutlineView
|
||||
isItemExpandable: (id) inItem
|
||||
{
|
||||
NSArray *target = inItem == nil ? self.simLocations : inItem[@"children"];
|
||||
|
||||
return [target count] ? YES : NO;
|
||||
}
|
||||
|
||||
- (id) outlineView: (NSOutlineView *) inOutlineView
|
||||
objectValueForTableColumn: (NSTableColumn *) inTableColumn
|
||||
byItem: (id) inItem
|
||||
{
|
||||
return [inItem objectForKey: @"title"];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6245" systemVersion="13E28" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6245"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
|
@ -13,11 +14,14 @@
|
|||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate">
|
||||
<connections>
|
||||
<outlet property="locationOutline" destination="hie-77-RjD" id="vaX-Cy-hrm"/>
|
||||
<outlet property="window" destination="nYB-8M-ZYr" id="B0K-mf-fOD"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="SyX-BE-Buh" userLabel="Main Controller" customClass="QSSimViewController">
|
||||
<connections>
|
||||
<outlet property="locationOutline" destination="hie-77-RjD" id="xmZ-A6-jSF"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="SimDirs" id="1Xt-HY-uBw">
|
||||
|
|
@ -103,21 +107,31 @@
|
|||
</menu>
|
||||
<window title="SimDirs" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" frameAutosaveName="main" animationBehavior="default" id="nYB-8M-ZYr">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="100" y="1000" width="192" height="320"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1418"/>
|
||||
<view key="contentView" id="5rQ-mH-cgE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="192" height="320"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eiG-1E-Kei">
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iz9-Ye-hd1">
|
||||
<rect key="frame" x="54" y="13" width="85" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Update" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="QEX-ht-ahg">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="handleUpdate:" target="SyX-BE-Buh" id="yt1-0o-8m2"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="20" horizontalPageScroll="10" verticalLineScroll="20" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eiG-1E-Kei">
|
||||
<rect key="frame" x="0.0" y="61" width="192" height="259"/>
|
||||
<clipView key="contentView" id="Tuz-5T-KKT">
|
||||
<rect key="frame" x="1" y="17" width="238" height="117"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" indentationPerLevel="16" outlineTableColumn="dVq-2Q-Cyd" id="hie-77-RjD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="238" height="117"/>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="18" indentationPerLevel="16" outlineTableColumn="dVq-2Q-Cyd" id="hie-77-RjD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="190" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
|
|
@ -129,7 +143,7 @@
|
|||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="3Wd-eV-7Zx">
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" allowsUndo="NO" alignment="left" title="Text Cell" id="3Wd-eV-7Zx">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
|
|
@ -138,7 +152,7 @@
|
|||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Voe-Tx-rLC" id="Gmi-Hc-w3X"/>
|
||||
<outlet property="dataSource" destination="SyX-BE-Buh" id="PQC-0Q-20T"/>
|
||||
</connections>
|
||||
</outlineView>
|
||||
</subviews>
|
||||
|
|
@ -153,16 +167,6 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iz9-Ye-hd1">
|
||||
<rect key="frame" x="54" y="13" width="85" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Update" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="QEX-ht-ahg">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="handleUpdate:" target="Voe-Tx-rLC" id="OGg-PQ-qdt"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="eiG-1E-Kei" firstAttribute="leading" secondItem="5rQ-mH-cgE" secondAttribute="leading" id="2Sc-05-Xvd"/>
|
||||
|
|
@ -173,15 +177,7 @@
|
|||
<constraint firstAttribute="bottom" secondItem="iz9-Ye-hd1" secondAttribute="bottom" constant="20" id="lw2-pT-lqw"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="145" y="261"/>
|
||||
<point key="canvasLocation" x="150" y="313"/>
|
||||
</window>
|
||||
<button verticalHuggingPriority="750" id="0gu-fg-9qr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="82" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Button" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ucj-7g-fYh">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>0.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
|
|
|||
13
SimDirs/QSDeviceInfo.h
Normal file
13
SimDirs/QSDeviceInfo.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// QSDeviceInfo.h
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface QSDeviceInfo : NSObject
|
||||
|
||||
@end
|
||||
13
SimDirs/QSDeviceInfo.m
Normal file
13
SimDirs/QSDeviceInfo.m
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// QSDeviceInfo.m
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import "QSDeviceInfo.h"
|
||||
|
||||
@implementation QSDeviceInfo
|
||||
|
||||
@end
|
||||
31
SimDirs/QSSimAppInfo.h
Normal file
31
SimDirs/QSSimAppInfo.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// QSSimAppInfo.h
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "QSSimViewController.h"
|
||||
|
||||
|
||||
@interface QSSimAppInfo : NSObject <QSOutlineProvider>
|
||||
|
||||
- (id) initWithBundleID: (NSString *) inBundleID;
|
||||
|
||||
- (void) updateFromLastLaunchMapInfo: (NSDictionary *) inMapInfo;
|
||||
- (void) updateFromAppStateInfo: (NSDictionary *) inStateInfo;
|
||||
- (void) refinePaths;
|
||||
|
||||
@property (nonatomic, strong) NSString *bundleID;
|
||||
@property (nonatomic, strong) NSString *appName;
|
||||
@property (nonatomic, strong) NSString *appShortVersion;
|
||||
@property (nonatomic, strong) NSString *appVersion;
|
||||
|
||||
@property (nonatomic, strong) NSString *bundlePath;
|
||||
@property (nonatomic, strong) NSString *sandBoxPath;
|
||||
@property (nonatomic, readonly) NSString *title;
|
||||
@property (nonatomic, readonly) BOOL hasValidPaths;
|
||||
|
||||
@end
|
||||
224
SimDirs/QSSimAppInfo.m
Normal file
224
SimDirs/QSSimAppInfo.m
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
//
|
||||
// QSSimAppInfo.m
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import "QSSimAppInfo.h"
|
||||
|
||||
|
||||
@interface QSSimAppInfo ()
|
||||
|
||||
@property (nonatomic, strong) NSArray *childItems;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation QSSimAppInfo
|
||||
|
||||
- (id) initWithBundleID: (NSString *) inBundleID
|
||||
{
|
||||
if ((self = [super init]) != nil) {
|
||||
self.bundleID = inBundleID;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
{
|
||||
// return [NSString stringWithFormat: @"%@: bundle %@ sandbox %@", self.bundleID, [self.bundlePath lastPathComponent], [self.sandBoxPath lastPathComponent]];
|
||||
return self.bundleID;
|
||||
}
|
||||
|
||||
- (BOOL) testPath: (NSString *) inPath
|
||||
{
|
||||
return inPath != nil && [[NSFileManager defaultManager] fileExistsAtPath: inPath] ? YES : NO;
|
||||
}
|
||||
|
||||
- (void) updateFromLastLaunchMapInfo: (NSDictionary *) inMapInfo
|
||||
{
|
||||
NSString *path;
|
||||
|
||||
path = inMapInfo[@"BundleContainer"];
|
||||
if (self.bundlePath == nil && [self testPath: path]) {
|
||||
self.bundlePath = path;
|
||||
}
|
||||
path = inMapInfo[@"Container"];
|
||||
if (self.sandBoxPath == nil && [self testPath: path]) {
|
||||
self.sandBoxPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) updateFromAppStateInfo: (NSDictionary *) inStateInfo
|
||||
{
|
||||
NSDictionary *compatInfo = inStateInfo[@"compatibilityInfo"];
|
||||
|
||||
if (compatInfo != nil) {
|
||||
NSString *path;
|
||||
|
||||
path = compatInfo[@"bundlePath"];
|
||||
if (self.bundlePath == nil && [self testPath: path]) {
|
||||
self.bundlePath = path;
|
||||
}
|
||||
path = compatInfo[@"sandboxPath"];
|
||||
if (self.sandBoxPath == nil && [self testPath: path]) {
|
||||
self.sandBoxPath = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) refinePaths
|
||||
{
|
||||
NSURL *infoURL;
|
||||
|
||||
if (self.bundlePath != nil && [[self.bundlePath lastPathComponent] rangeOfString: @".app"].location == NSNotFound) {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *bundleURL = [[NSURL alloc] initFileURLWithPath: self.bundlePath];
|
||||
NSURL *appURL;
|
||||
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtURL: bundleURL includingPropertiesForKeys: nil
|
||||
options: NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles errorHandler: nil];
|
||||
|
||||
while ((appURL = [dirEnum nextObject])) {
|
||||
NSString *appPath = [appURL path];
|
||||
|
||||
if ([[appPath lastPathComponent] rangeOfString: @".app"].location != NSNotFound) {
|
||||
self.bundlePath = appPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infoURL = [[NSURL alloc] initFileURLWithPath: self.bundlePath];
|
||||
infoURL = [infoURL URLByAppendingPathComponent: @"Info.plist"];
|
||||
if (infoURL != nil && [[NSFileManager defaultManager] fileExistsAtPath: [infoURL path]]) {
|
||||
NSData *plistData = [NSData dataWithContentsOfURL: infoURL];
|
||||
|
||||
if (plistData != nil) {
|
||||
NSDictionary *plistInfo;
|
||||
|
||||
plistInfo = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil];
|
||||
if (plistInfo != nil) {
|
||||
self.appName = plistInfo[(__bridge NSString *)kCFBundleNameKey];
|
||||
self.appShortVersion = plistInfo[@"CFBundleShortVersionString"];
|
||||
self.appVersion = plistInfo[(__bridge NSString *)kCFBundleVersionKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - QSOutlineProvider
|
||||
|
||||
- (NSInteger) outlineChildCount
|
||||
{
|
||||
return [self.childItems count];
|
||||
}
|
||||
|
||||
- (id) outlineChildAtIndex: (NSInteger) inIndex
|
||||
{
|
||||
NSDictionary *pathInfo = [self.childItems objectAtIndex: inIndex];
|
||||
|
||||
return pathInfo[@"title"];
|
||||
}
|
||||
|
||||
- (BOOL) outlineItemIsExpanable
|
||||
{
|
||||
return [self outlineChildCount] ? YES : NO;
|
||||
}
|
||||
|
||||
- (NSString *) outlineItemValueForColumn: (NSTableColumn *) inTableColumn
|
||||
{
|
||||
return [inTableColumn.identifier isEqualToString: @"title"] ? self.title : nil;
|
||||
}
|
||||
|
||||
- (BOOL) outlineItemPerformAction
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) outlineItemPerformActionForChild: (id) inChild
|
||||
{
|
||||
NSInteger pathIndex;
|
||||
BOOL handled = NO;
|
||||
|
||||
pathIndex = [self.childItems indexOfObjectPassingTest: ^(id inObject, NSUInteger inIndex, BOOL *outStop) {
|
||||
NSDictionary *pathInfo = inObject;
|
||||
|
||||
*outStop = [pathInfo[@"title"] isEqualToString: inChild];
|
||||
|
||||
return *outStop;
|
||||
}];
|
||||
|
||||
if (pathIndex != NSNotFound) {
|
||||
NSDictionary *pathInfo = [self.childItems objectAtIndex: pathIndex];
|
||||
NSURL *itemPathURL = [[NSURL alloc] initFileURLWithPath: pathInfo[@"path"]];
|
||||
|
||||
if (itemPathURL != nil) {
|
||||
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[ itemPathURL ]];
|
||||
}
|
||||
handled = YES;
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
#pragma mark - Setters / Getters
|
||||
|
||||
- (NSArray *) childItems
|
||||
{
|
||||
// any more than these two items and perhaps a dedicated class would be better
|
||||
|
||||
if (_childItems == nil) {
|
||||
NSMutableArray *childItems = [NSMutableArray array];
|
||||
|
||||
if (self.bundlePath != nil) {
|
||||
[childItems addObject: @{ @"title" : @"Bundle Location", @"path" : self.bundlePath }];
|
||||
}
|
||||
if (self.sandBoxPath != nil) {
|
||||
[childItems addObject: @{ @"title" : @"Sandbox Location", @"path" : self.sandBoxPath }];
|
||||
}
|
||||
|
||||
_childItems = childItems;
|
||||
}
|
||||
|
||||
return _childItems;
|
||||
}
|
||||
|
||||
- (void) setBundlePath: (NSString *) inBundlePath
|
||||
{
|
||||
_bundlePath = inBundlePath;
|
||||
_childItems = nil;
|
||||
}
|
||||
|
||||
- (void) setSandBoxPath: (NSString *) inSandBoxPath
|
||||
{
|
||||
_sandBoxPath = inSandBoxPath;
|
||||
_childItems = nil;
|
||||
}
|
||||
|
||||
- (NSString *) title
|
||||
{
|
||||
NSString *title;
|
||||
|
||||
if (self.appName != nil) {
|
||||
title = [NSString stringWithFormat: @"%@ v%@", self.appName, self.appShortVersion];
|
||||
if (![self.appShortVersion isEqualToString: self.appVersion]) {
|
||||
title = [title stringByAppendingString: [NSString stringWithFormat: @" (%@)", self.appVersion]];
|
||||
}
|
||||
title = [title stringByAppendingString: [NSString stringWithFormat: @" - %@", self.bundleID]];
|
||||
}
|
||||
else {
|
||||
title = self.bundleID;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
- (BOOL) hasValidPaths
|
||||
{
|
||||
return [self.childItems count] ? YES : NO;
|
||||
}
|
||||
|
||||
@end
|
||||
22
SimDirs/QSSimDeviceInfo.h
Normal file
22
SimDirs/QSSimDeviceInfo.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// QSSimDeviceInfo.h
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "QSSimViewController.h"
|
||||
|
||||
@interface QSSimDeviceInfo : NSObject <QSOutlineProvider>
|
||||
|
||||
+ (NSArray *) gatherDeviceLocations;
|
||||
|
||||
@property (nonatomic, strong) NSURL *baseURL;
|
||||
@property (nonatomic, strong) NSString *name;
|
||||
@property (nonatomic, strong) NSString *model;
|
||||
@property (nonatomic, strong) NSString *version;
|
||||
@property (nonatomic, readonly) NSString *title;
|
||||
|
||||
@end
|
||||
288
SimDirs/QSSimDeviceInfo.m
Normal file
288
SimDirs/QSSimDeviceInfo.m
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
//
|
||||
// QSSimDeviceInfo.m
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import "QSSimDeviceInfo.h"
|
||||
#import "QSSimAppInfo.h"
|
||||
|
||||
|
||||
@interface QSSimDeviceInfo ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *appList;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation QSSimDeviceInfo
|
||||
|
||||
+ (NSArray *) gatherDeviceLocations
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *libraryDirURL = [[fileManager URLsForDirectory: NSLibraryDirectory inDomains: NSUserDomainMask] firstObject];
|
||||
NSMutableArray *deviceList = [NSMutableArray array];
|
||||
|
||||
if (libraryDirURL != nil) {
|
||||
libraryDirURL = [libraryDirURL URLByAppendingPathComponent: @"Developer/CoreSimulator/Devices"];
|
||||
if (libraryDirURL != nil) {
|
||||
NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtURL: libraryDirURL includingPropertiesForKeys: nil
|
||||
options: NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles errorHandler: nil];
|
||||
NSURL *baseInfoURL;
|
||||
QSSimDeviceInfo *deviceInfo;
|
||||
|
||||
while ((baseInfoURL = [dirEnum nextObject])) {
|
||||
deviceInfo = [[QSSimDeviceInfo alloc] initWithURL: baseInfoURL];
|
||||
if (deviceInfo != nil) {
|
||||
[deviceList addObject: deviceInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deviceList;
|
||||
}
|
||||
|
||||
- (id) initWithURL: (NSURL *) inDeviceURL
|
||||
{
|
||||
if ((self = [super init]) != nil) {
|
||||
NSURL *deviceInfoURL = [inDeviceURL URLByAppendingPathComponent: @"device.plist"];
|
||||
BOOL initOK = NO;
|
||||
|
||||
self.baseURL = inDeviceURL;
|
||||
if (deviceInfoURL != nil && [[NSFileManager defaultManager] fileExistsAtPath: [deviceInfoURL path]]) {
|
||||
NSData *plistData = [NSData dataWithContentsOfURL: deviceInfoURL];
|
||||
|
||||
if (plistData != nil) {
|
||||
NSDictionary *plistInfo;
|
||||
|
||||
plistInfo = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil];
|
||||
if (plistInfo != nil) {
|
||||
NSString *runtime = plistInfo[@"runtime"];
|
||||
NSRange runtimeRange;
|
||||
|
||||
runtimeRange = [runtime rangeOfString: @"iOS.*" options: NSRegularExpressionSearch];
|
||||
if (runtimeRange.location != NSNotFound) {
|
||||
NSArray *versionComponents = [[runtime substringWithRange: runtimeRange] componentsSeparatedByString: @"-"];
|
||||
|
||||
self.name = plistInfo[@"name"];
|
||||
self.model = versionComponents[0];
|
||||
self.version = [NSString stringWithFormat: @"%@.%@", versionComponents[1], versionComponents[2]];
|
||||
self.appList = [NSMutableArray array];
|
||||
|
||||
[self gatherAppInfoFromLastLaunchMap];
|
||||
[self gatherAppInfoFromAppState];
|
||||
[self cleanupAndRefineAppList];
|
||||
|
||||
initOK = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initOK) {
|
||||
self = nil;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *) description
|
||||
{
|
||||
NSMutableArray *childDescriptions = [NSMutableArray array];
|
||||
|
||||
for (QSSimAppInfo *appInfo in self.appList) {
|
||||
[childDescriptions addObject: [appInfo description]];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat: @"%@: %@ %@ %@", self.name, self.model, self.version, childDescriptions];
|
||||
}
|
||||
|
||||
// LastLaunchServicesMap.plist seems to be the most reliable location to gather app info
|
||||
- (void) gatherAppInfoFromLastLaunchMap
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *launchMapInfoURL = [self.baseURL URLByAppendingPathComponent: @"data/Library/MobileInstallation/LastLaunchServicesMap.plist"];
|
||||
|
||||
if (launchMapInfoURL != nil && [fileManager fileExistsAtPath: [launchMapInfoURL path]]) {
|
||||
NSData *plistData = [NSData dataWithContentsOfURL: launchMapInfoURL];
|
||||
NSDictionary *launchInfo;
|
||||
NSDictionary *userInfo;
|
||||
|
||||
launchInfo = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil];
|
||||
userInfo = launchInfo[@"User"];
|
||||
|
||||
for (NSString *bundleID in userInfo) {
|
||||
QSSimAppInfo *appInfo = [self appInfoWithBundleID: bundleID];
|
||||
|
||||
if (appInfo != nil) {
|
||||
[appInfo updateFromLastLaunchMapInfo: userInfo[bundleID]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applicationState.plist sometimes has info that LastLaunchServicesMap.plist doesn't
|
||||
- (void) gatherAppInfoFromAppState
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *apStateInfoURL = [self.baseURL URLByAppendingPathComponent: @"data/Library/BackBoard/applicationState.plist"];
|
||||
|
||||
if (apStateInfoURL != nil && [fileManager fileExistsAtPath: [apStateInfoURL path]]) {
|
||||
NSData *plistData = [NSData dataWithContentsOfURL: apStateInfoURL];
|
||||
NSDictionary *stateInfo;
|
||||
|
||||
stateInfo = [NSPropertyListSerialization propertyListWithData: plistData options: NSPropertyListImmutable format: nil error: nil];
|
||||
|
||||
for (NSString *bundleID in stateInfo) {
|
||||
if ([bundleID rangeOfString: @"com.apple"].location == NSNotFound) {
|
||||
QSSimAppInfo *appInfo = [self appInfoWithBundleID: bundleID];
|
||||
|
||||
if (appInfo != nil) {
|
||||
[appInfo updateFromAppStateInfo: stateInfo[bundleID]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) cleanupAndRefineAppList
|
||||
{
|
||||
NSMutableArray *mysteryApps = [NSMutableArray array];
|
||||
|
||||
for (QSSimAppInfo *appInfo in self.appList) {
|
||||
if (!appInfo.hasValidPaths) {
|
||||
[mysteryApps addObject: appInfo];
|
||||
}
|
||||
}
|
||||
|
||||
[self.appList removeObjectsInArray: mysteryApps];
|
||||
[self.appList sortUsingDescriptors: @[ [NSSortDescriptor sortDescriptorWithKey: @"title" ascending:YES ]]];
|
||||
|
||||
for (QSSimAppInfo *appInfo in self.appList) {
|
||||
[appInfo refinePaths];
|
||||
}
|
||||
}
|
||||
|
||||
// Used to also scan installation logs for clues but uncertain how useful this is.
|
||||
// Leaving it here in case I decide to put it back
|
||||
|
||||
#if 0
|
||||
- (void) updateDeviceInfoForAppsFromLogs: (NSMutableDictionary *) inDeviceInfo
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *installLogURL = [[NSURL alloc] initFileURLWithPath: inDeviceInfo[@"path"]];
|
||||
|
||||
installLogURL = [installLogURL URLByAppendingPathComponent: @"data/Library/Logs/MobileInstallation/mobile_installation.log.0"];
|
||||
if (installLogURL != nil && [fileManager fileExistsAtPath: [installLogURL path]]) {
|
||||
NSString *installLog = [[NSString alloc] initWithContentsOfURL: installLogURL usedEncoding: nil error: nil];
|
||||
|
||||
if (installLog != nil) {
|
||||
NSRange logMentionRange;
|
||||
|
||||
for (NSString *line in [installLog componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]) {
|
||||
logMentionRange = [line rangeOfString: @"Made container live for "];
|
||||
if (logMentionRange.location != NSNotFound) {
|
||||
NSArray *installParts = [[line substringFromIndex: logMentionRange.location + logMentionRange.length] componentsSeparatedByString: @" "];
|
||||
|
||||
if ([installParts count] == 3) { // expecting com.foo.bar at UUID
|
||||
NSString *bundleID = [installParts objectAtIndex: 0];
|
||||
|
||||
if ([bundleID rangeOfString: @"com.apple"].location == NSNotFound) {
|
||||
NSString *path = [installParts objectAtIndex: 2];
|
||||
|
||||
if (path != nil && [fileManager fileExistsAtPath: path]) {
|
||||
NSString *pathTitle;
|
||||
|
||||
if ([path rangeOfString: @"Data/Application"].location != NSNotFound) {
|
||||
pathTitle = @"Sandbox Location";
|
||||
}
|
||||
else if ([path rangeOfString: @"Bundle/Application"].location != NSNotFound) {
|
||||
pathTitle = @"Bundle Location";
|
||||
}
|
||||
else {
|
||||
pathTitle = @"???";
|
||||
}
|
||||
|
||||
[self addPath: path withTitle: pathTitle forBundleID: bundleID withDeviceInfo: inDeviceInfo];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
- (QSSimAppInfo *) appInfoWithBundleID: (NSString *) inBundleID
|
||||
{
|
||||
QSSimAppInfo *appInfo = nil;
|
||||
NSInteger appIndex;
|
||||
|
||||
appIndex = [self.appList indexOfObjectPassingTest: ^(id inObject, NSUInteger inIndex, BOOL *outStop) {
|
||||
QSSimAppInfo *appInfo = inObject;
|
||||
|
||||
*outStop = [appInfo.bundleID isEqualToString: inBundleID];
|
||||
|
||||
return *outStop;
|
||||
}];
|
||||
|
||||
if (appIndex == NSNotFound) {
|
||||
appInfo = [[QSSimAppInfo alloc] initWithBundleID: inBundleID];
|
||||
[self.appList addObject: appInfo];
|
||||
}
|
||||
else {
|
||||
appInfo = [self.appList objectAtIndex: appIndex];
|
||||
}
|
||||
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
#pragma mark - QSOutlineProvider
|
||||
|
||||
- (NSInteger) outlineChildCount
|
||||
{
|
||||
return [self.appList count];
|
||||
}
|
||||
|
||||
- (id) outlineChildAtIndex: (NSInteger) inIndex
|
||||
{
|
||||
return [self.appList objectAtIndex: inIndex];
|
||||
}
|
||||
|
||||
- (BOOL) outlineItemIsExpanable
|
||||
{
|
||||
return [self.appList count] ? YES : NO;
|
||||
}
|
||||
|
||||
- (NSString *) outlineItemValueForColumn: (NSTableColumn *) inTableColumn
|
||||
{
|
||||
return [inTableColumn.identifier isEqualToString: @"title"] ? self.title : nil;
|
||||
}
|
||||
|
||||
- (BOOL) outlineItemPerformAction
|
||||
{
|
||||
if (self.baseURL != nil) {
|
||||
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[ self.baseURL ]];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL) outlineItemPerformActionForChild: (id) inChild
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Setters / Getters
|
||||
|
||||
- (NSString *) title
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@: %@ %@", self.name, self.model, self.version];
|
||||
}
|
||||
|
||||
@end
|
||||
25
SimDirs/QSSimViewController.h
Normal file
25
SimDirs/QSSimViewController.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// QSSimViewController.h
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@protocol QSOutlineProvider <NSObject>
|
||||
|
||||
- (NSInteger) outlineChildCount;
|
||||
- (id) outlineChildAtIndex: (NSInteger) inIndex;
|
||||
- (BOOL) outlineItemIsExpanable;
|
||||
- (NSString *) outlineItemValueForColumn: (NSTableColumn *) inTableColumn;
|
||||
|
||||
- (BOOL) outlineItemPerformAction;
|
||||
- (BOOL) outlineItemPerformActionForChild: (id) inChild;
|
||||
|
||||
@end
|
||||
|
||||
@interface QSSimViewController : NSObject
|
||||
|
||||
@end
|
||||
144
SimDirs/QSSimViewController.m
Normal file
144
SimDirs/QSSimViewController.m
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// QSSimViewController.m
|
||||
// SimDirs
|
||||
//
|
||||
// Created by Casey Fleser on 10/31/14.
|
||||
// Copyright (c) 2014 Quiet Spark. All rights reserved.
|
||||
//
|
||||
|
||||
#import "QSSimViewController.h"
|
||||
#import "QSSimDeviceInfo.h"
|
||||
|
||||
|
||||
@interface QSSimViewController ()
|
||||
|
||||
@property (nonatomic, weak) IBOutlet NSOutlineView *locationOutline;
|
||||
@property (nonatomic, strong) NSArray *deviceList;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation QSSimViewController
|
||||
|
||||
- (void) awakeFromNib
|
||||
{
|
||||
[self reloadOutine];
|
||||
|
||||
[self.locationOutline setTarget: self];
|
||||
[self.locationOutline setDoubleAction: @selector(handleRowSelect:)];
|
||||
}
|
||||
|
||||
- (void) reloadOutine
|
||||
{
|
||||
NSArray *deviceList = [QSSimDeviceInfo gatherDeviceLocations];
|
||||
|
||||
self.deviceList = [deviceList sortedArrayUsingDescriptors: @[ [NSSortDescriptor sortDescriptorWithKey: @"title" ascending:YES ]]];
|
||||
[self.locationOutline reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - NSOutlineViewDataSource
|
||||
|
||||
- (NSInteger) outlineView: (NSOutlineView *) inOutlineView
|
||||
numberOfChildrenOfItem: (id) inItem
|
||||
{
|
||||
NSInteger childCount = 0;
|
||||
|
||||
if (inItem == nil) {
|
||||
childCount = [self.deviceList count];
|
||||
}
|
||||
else {
|
||||
if ([inItem conformsToProtocol: @protocol(QSOutlineProvider)]) {
|
||||
childCount = [inItem outlineChildCount];
|
||||
}
|
||||
}
|
||||
|
||||
return childCount;
|
||||
}
|
||||
|
||||
- (id) outlineView: (NSOutlineView *) inOutlineView
|
||||
child: (NSInteger) inIndex
|
||||
ofItem: (id) inItem
|
||||
{
|
||||
id child = nil;
|
||||
|
||||
if (inItem == nil) {
|
||||
child = [self.deviceList objectAtIndex: inIndex];
|
||||
}
|
||||
else {
|
||||
if ([inItem conformsToProtocol: @protocol(QSOutlineProvider)]) {
|
||||
child = [inItem outlineChildAtIndex: inIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
- (BOOL) outlineView: (NSOutlineView *) inOutlineView
|
||||
isItemExpandable: (id) inItem
|
||||
{
|
||||
BOOL expandable = NO;
|
||||
|
||||
if (inItem == nil) {
|
||||
expandable = [self.deviceList count] ? YES : NO;
|
||||
}
|
||||
else {
|
||||
if ([inItem conformsToProtocol: @protocol(QSOutlineProvider)]) {
|
||||
expandable = [inItem outlineItemIsExpanable];
|
||||
}
|
||||
}
|
||||
|
||||
return expandable;
|
||||
}
|
||||
|
||||
- (id) outlineView: (NSOutlineView *) inOutlineView
|
||||
objectValueForTableColumn: (NSTableColumn *) inTableColumn
|
||||
byItem: (id) inItem
|
||||
{
|
||||
if ([inItem conformsToProtocol: @protocol(QSOutlineProvider)]) {
|
||||
inItem = [inItem outlineItemValueForColumn: inTableColumn];
|
||||
}
|
||||
else if (![inTableColumn.identifier isEqualToString: @"title"]) {
|
||||
inItem = nil;
|
||||
}
|
||||
|
||||
return inItem;
|
||||
}
|
||||
|
||||
#pragma mark - Handlers
|
||||
|
||||
- (void) handleRowSelect: (id) inSender
|
||||
{
|
||||
if (inSender == self.locationOutline) {
|
||||
id item = [self.locationOutline itemAtRow: [self.locationOutline clickedRow]];
|
||||
BOOL handled = NO;
|
||||
|
||||
if (item != nil) {
|
||||
if ([item conformsToProtocol: @protocol(QSOutlineProvider)]) {
|
||||
handled = [item outlineItemPerformAction];
|
||||
}
|
||||
else {
|
||||
id parentItem = [self.locationOutline parentForItem: item];
|
||||
|
||||
if ([parentItem conformsToProtocol: @protocol(QSOutlineProvider)]) {
|
||||
handled = [parentItem outlineItemPerformActionForChild: item];
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
if ([self.locationOutline isItemExpanded: item]) {
|
||||
[self.locationOutline collapseItem: item];
|
||||
}
|
||||
else {
|
||||
[self.locationOutline expandItem: item];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction) handleUpdate: (id) inSender
|
||||
{
|
||||
[self reloadOutine];
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
Reference in a new issue