diff --git a/Disk ArbitratorUITests/Disk_ArbitratorUITests.m b/Disk ArbitratorUITests/Disk_ArbitratorUITests.m new file mode 100644 index 0000000..f082aa5 --- /dev/null +++ b/Disk ArbitratorUITests/Disk_ArbitratorUITests.m @@ -0,0 +1,47 @@ +// +// Disk_ArbitratorUITests.m +// Disk ArbitratorUITests +// +// Created by Danil Korotenko on 11/5/20. +// + +#import + +@interface Disk_ArbitratorUITests : XCTestCase + +@end + +@implementation Disk_ArbitratorUITests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + self.continueAfterFailure = NO; + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // UI tests must launch the application that they test. + XCUIApplication *app = [[XCUIApplication alloc] init]; + [app launch]; + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testLaunchPerformance { + if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { + // This measures how long it takes to launch your application. + [self measureWithMetrics:@[[[XCTApplicationLaunchMetric alloc] init]] block:^{ + [[[XCUIApplication alloc] init] launch]; + }]; + } +} + +@end diff --git a/Disk ArbitratorUITests/Info.plist b/Disk ArbitratorUITests/Info.plist new file mode 100644 index 0000000..64d65ca --- /dev/null +++ b/Disk ArbitratorUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/DiskArbitrator.xcodeproj/project.pbxproj b/DiskArbitrator.xcodeproj/project.pbxproj index 2176623..1e9c94b 100644 --- a/DiskArbitrator.xcodeproj/project.pbxproj +++ b/DiskArbitrator.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 96FCBC871140CBF400E5554C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96FCBC861140CBF400E5554C /* Security.framework */; }; 96FCCE46114491E700E5554C /* ToolbarItem Attach Disk Image.png in Resources */ = {isa = PBXBuildFile; fileRef = 96FCCE45114491E700E5554C /* ToolbarItem Attach Disk Image.png */; }; A5E4FC5C1CC99D02005B7675 /* ReleaseNotes.markdown in Resources */ = {isa = PBXBuildFile; fileRef = A5E4FC5A1CC99C64005B7675 /* ReleaseNotes.markdown */; }; + DA23B41B25540DAF00EAB4FD /* Disk_ArbitratorUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA23B41A25540DAF00EAB4FD /* Disk_ArbitratorUITests.m */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -81,6 +82,13 @@ remoteGlobalIDString = 8D1107260486CEB800E47090; remoteInfo = DiskArbitrator; }; + DA23B41D25540DAF00EAB4FD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D1107260486CEB800E47090; + remoteInfo = DiskArbitrator; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -127,6 +135,9 @@ 96FCBC861140CBF400E5554C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 96FCCE45114491E700E5554C /* ToolbarItem Attach Disk Image.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "ToolbarItem Attach Disk Image.png"; path = "Resources/ToolbarItem Attach Disk Image.png"; sourceTree = ""; }; A5E4FC5A1CC99C64005B7675 /* ReleaseNotes.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = ReleaseNotes.markdown; sourceTree = ""; }; + DA23B41825540DAF00EAB4FD /* Disk ArbitratorUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Disk ArbitratorUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DA23B41A25540DAF00EAB4FD /* Disk_ArbitratorUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Disk_ArbitratorUITests.m; sourceTree = ""; }; + DA23B41C25540DAF00EAB4FD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E48EB2D323EE60EC00C0B297 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; E48EB2D423EE60EC00C0B297 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MountOptions.xib; sourceTree = ""; }; E48EB2D523EE60ED00C0B297 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/DiskInfo.xib; sourceTree = ""; }; @@ -147,6 +158,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA23B41525540DAE00EAB4FD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -192,6 +210,7 @@ isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* Disk Arbitrator.app */, + DA23B41825540DAF00EAB4FD /* Disk ArbitratorUITests.xctest */, ); name = Products; sourceTree = ""; @@ -202,6 +221,7 @@ 080E96DDFE201D6D7F000001 /* Classes */, 29B97315FDCFA39411CA2CEA /* Other Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, + DA23B41925540DAF00EAB4FD /* Disk ArbitratorUITests */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); @@ -272,6 +292,15 @@ name = Nibs; sourceTree = ""; }; + DA23B41925540DAF00EAB4FD /* Disk ArbitratorUITests */ = { + isa = PBXGroup; + children = ( + DA23B41A25540DAF00EAB4FD /* Disk_ArbitratorUITests.m */, + DA23B41C25540DAF00EAB4FD /* Info.plist */, + ); + path = "Disk ArbitratorUITests"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -294,6 +323,24 @@ productReference = 8D1107320486CEB800E47090 /* Disk Arbitrator.app */; productType = "com.apple.product-type.application"; }; + DA23B41725540DAE00EAB4FD /* Disk ArbitratorUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DA23B42125540DAF00EAB4FD /* Build configuration list for PBXNativeTarget "Disk ArbitratorUITests" */; + buildPhases = ( + DA23B41425540DAE00EAB4FD /* Sources */, + DA23B41525540DAE00EAB4FD /* Frameworks */, + DA23B41625540DAE00EAB4FD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DA23B41E25540DAF00EAB4FD /* PBXTargetDependency */, + ); + name = "Disk ArbitratorUITests"; + productName = "Disk ArbitratorUITests"; + productReference = DA23B41825540DAF00EAB4FD /* Disk ArbitratorUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -301,12 +348,19 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 1130; + TargetAttributes = { + DA23B41725540DAE00EAB4FD = { + CreatedOnToolsVersion = 12.0.1; + TestTargetID = 8D1107260486CEB800E47090; + }; + }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "DiskArbitrator" */; compatibilityVersion = "Xcode 11.0"; developmentRegion = en; hasScannedForEncodings = 1; knownRegions = ( + English, en, Base, ); @@ -316,6 +370,7 @@ targets = ( 8D1107260486CEB800E47090 /* DiskArbitrator */, 9654B7A3111E46D600E9B0C4 /* Build Disk Arbitrator DMG */, + DA23B41725540DAE00EAB4FD /* Disk ArbitratorUITests */, ); }; /* End PBXProject section */ @@ -345,6 +400,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA23B41625540DAE00EAB4FD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -382,6 +444,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA23B41425540DAE00EAB4FD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DA23B41B25540DAF00EAB4FD /* Disk_ArbitratorUITests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -390,6 +460,11 @@ target = 8D1107260486CEB800E47090 /* DiskArbitrator */; targetProxy = 9654B7A7111E470600E9B0C4 /* PBXContainerItemProxy */; }; + DA23B41E25540DAF00EAB4FD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8D1107260486CEB800E47090 /* DiskArbitrator */; + targetProxy = DA23B41D25540DAF00EAB4FD /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -466,18 +541,22 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_OBJC_INTERFACE_IVARS = YES_ERROR; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = QBPW9LPK8E; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/DiskArbitrator_Prefix.pch; INFOPLIST_FILE = "Resources/DiskArbitrator-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DiskArbitrator; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INSTALL_PATH = "$(HOME)/Applications"; - PRODUCT_BUNDLE_IDENTIFIER = "us.burghardt.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_BUNDLE_IDENTIFIER = com.danilkorotenko.DiskArbitrator; PRODUCT_NAME = "Disk Arbitrator"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; @@ -487,16 +566,20 @@ ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_OBJC_INTERFACE_IVARS = YES_ERROR; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = QBPW9LPK8E; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/DiskArbitrator_Prefix.pch; INFOPLIST_FILE = "Resources/DiskArbitrator-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DiskArbitrator; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; INSTALL_PATH = "$(HOME)/Applications"; - PRODUCT_BUNDLE_IDENTIFIER = "us.burghardt.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_BUNDLE_IDENTIFIER = com.danilkorotenko.DiskArbitrator; PRODUCT_NAME = "Disk Arbitrator"; - PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; @@ -533,7 +616,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.15; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; @@ -570,11 +653,95 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.15; SDKROOT = macosx; }; name = Release; }; + DA23B41F25540DAF00EAB4FD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = XQKX38U9F4; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "Disk ArbitratorUITests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.GTB.Disk-ArbitratorUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_TARGET_NAME = DiskArbitrator; + }; + name = Debug; + }; + DA23B42025540DAF00EAB4FD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = XQKX38U9F4; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "Disk ArbitratorUITests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.GTB.Disk-ArbitratorUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_TARGET_NAME = DiskArbitrator; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -605,6 +772,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DA23B42125540DAF00EAB4FD /* Build configuration list for PBXNativeTarget "Disk ArbitratorUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DA23B41F25540DAF00EAB4FD /* Debug */, + DA23B42025540DAF00EAB4FD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; diff --git a/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/Disk ArbitratorUITests.xcscheme b/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/Disk ArbitratorUITests.xcscheme new file mode 100644 index 0000000..50c58cc --- /dev/null +++ b/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/Disk ArbitratorUITests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/DiskArbitrator.xcscheme b/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/DiskArbitrator.xcscheme index 3f63103..1d3edb6 100644 --- a/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/DiskArbitrator.xcscheme +++ b/DiskArbitrator.xcodeproj/xcshareddata/xcschemes/DiskArbitrator.xcscheme @@ -37,6 +37,16 @@ + + + + [[NSUserDefaults standardUserDefaults] integerForKey:AppLogLevelDefaultsKey]) + { return; + } va_start(args, format); @@ -29,8 +31,10 @@ void Log(NSInteger level, NSString *format, ...) BOOL shouldUseSyslog = [[NSUserDefaults standardUserDefaults] boolForKey:AppShouldEnableSyslogDefaultsKey]; - if (shouldUseSyslog) + if (shouldUseSyslog) + { syslog((int)level, "%s\n", utfFormattedError); + } os_log(OS_LOG_DEFAULT, "%s\n", utfFormattedError); } diff --git a/Source/Arbitrator.m b/Source/Arbitrator.m index 91d5834..1bd4a55 100644 --- a/Source/Arbitrator.m +++ b/Source/Arbitrator.m @@ -29,7 +29,7 @@ @implementation Arbitrator + (void)initialize { InitializeDiskArbitration(); - + NSMutableDictionary *defaults = [NSMutableDictionary dictionary]; [defaults setObject:[NSNumber numberWithBool:YES] forKey:ArbitratorIsEnabled]; [defaults setObject:[NSNumber numberWithInteger:0] forKey:ArbitratorMountMode]; @@ -39,8 +39,10 @@ + (void)initialize + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { if ([key isEqual:@"wholeDisks"]) + { return [NSSet setWithObject:@"disks"]; - + } + return [super keyPathsForValuesAffectingValueForKey:key]; } @@ -52,8 +54,10 @@ - (id)init disks = [NSMutableSet new]; [self registerSession]; - if ([[NSUserDefaults standardUserDefaults] boolForKey:ArbitratorIsEnabled]) { - if ([self activate] == NO) { + if ([[NSUserDefaults standardUserDefaults] boolForKey:ArbitratorIsEnabled]) + { + if ([self activate] == NO) + { [self release]; return nil; } @@ -65,18 +69,20 @@ - (id)init - (void)dealloc { if (approvalSession) + { [self deactivate]; - + } + [self unregisterSession]; - + [disks release]; - [super dealloc]; + [super dealloc]; } - (BOOL)registerSession { NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - + [nc addObserver:self selector:@selector(diskDidAppear:) name:DADiskDidAppearNotification object:nil]; [nc addObserver:self selector:@selector(diskDidDisappear:) name:DADiskDidDisappearNotification object:nil]; [nc addObserver:self selector:@selector(diskDidChange:) name:DADiskDidChangeNotification object:nil]; @@ -111,21 +117,23 @@ - (void)diskDidChange:(NSNotification *)notif - (BOOL)registerApprovalSession { approvalSession = DAApprovalSessionCreate(kCFAllocatorDefault); - if (!approvalSession) { + if (!approvalSession) + { Log(LOG_CRIT, @"Failed to create Disk Arbitration approval session."); return NO; } - + DAApprovalSessionScheduleWithRunLoop(approvalSession, CFRunLoopGetMain(), kCFRunLoopCommonModes); DARegisterDiskMountApprovalCallback(approvalSession, NULL, DiskMountApprovalCallback, self); - + return YES; } - (void)unregisterApprovalSession { - if (approvalSession) { + if (approvalSession) + { DAUnregisterApprovalCallback(approvalSession, DiskMountApprovalCallback, self); DAApprovalSessionUnscheduleFromRunLoop(approvalSession, CFRunLoopGetMain(), kCFRunLoopCommonModes); @@ -137,22 +145,27 @@ - (void)unregisterApprovalSession - (void)mountApprovedDisk:(Disk *)disk { NSAssert(self.isActivated, @"bug"); - + NSArray *args = disk.mountArgs; NSString *path = disk.mountPath; - if (!args || !args.count) { + if (!args || !args.count) + { NSAssert(self.mountMode == MM_READONLY, @"Unknown mount mode"); - + // Arguments will be passed via the -o flag of mount. If the file system specific mount, e.g. mount_hfs, // supports additional flags that mount(8) doesn't, they can be passed to -o. That feature is used to // pass -j to mount_hfs, which instructs HFS to ignore journal. Normally, an HFS volume that // has a dirty journal will fail to mount read-only because the file system is inconsistent. "-j" is // a work-around. - + if (disk.isHFS) + { args = [NSArray arrayWithObjects:@"-j", @"rdonly", nil]; + } else + { args = [NSArray arrayWithObjects:@"rdonly", nil]; + } path = nil; } [disk mountAtPath:path withArguments:args]; @@ -170,21 +183,26 @@ - (DADissenterRef)defaultDissenter __attribute__((cf_returns_retained)) - (DADissenterRef)approveMount:(Disk *)disk __attribute__((cf_returns_retained)) { - if (self.isActivated) { + if (self.isActivated) + { // Block mode prevents everything from mounting, unless this disk is being mounted from our GUI - if (self.mountMode == MM_BLOCK && !disk.isMounting) { + if (self.mountMode == MM_BLOCK && !disk.isMounting) + { return [self defaultDissenter]; } // When an approve mount callback is received, we have no idea if this approval was from // a mount that belongs to us, or someone else. So we track whether we have rejected a // mount request, and only allow mounts after we have rejected the initial request. - if (!disk.rejectedMount) { + if (!disk.rejectedMount) + { disk.rejectedMount = YES; // Do the mount after a slight delay to allow time for this approval to finish [self performSelector:@selector(mountApprovedDisk:) withObject:disk afterDelay:0.1]; return [self defaultDissenter]; - } else { + } + else + { // Allow the mount since we previously rejected it NSAssert(disk.isMounting == YES, @"invalid state"); disk.isMounting = NO; @@ -200,12 +218,12 @@ - (DADissenterRef)approveMount:(Disk *)disk __attribute__((cf_returns_retained)) - (BOOL)activate { BOOL success; - + [self willChangeValueForKey:@"isActivated"]; success = [self registerApprovalSession]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:ArbitratorIsEnabled]; [self didChangeValueForKey:@"isActivated"]; - + return success; } @@ -225,19 +243,25 @@ - (BOOL)isActivated - (void)setIsActivated:(BOOL)shouldActivate { if (shouldActivate && !self.isActivated) + { [self activate]; - + } else if (!shouldActivate && self.isActivated) + { [self deactivate]; + } } -- (NSInteger)mountMode { +- (NSInteger)mountMode +{ return [[NSUserDefaults standardUserDefaults] integerForKey:ArbitratorMountMode]; } -- (void)setMountMode:(NSInteger)mountMode { +- (void)setMountMode:(NSInteger)mountMode +{ NSInteger currentMode = [[NSUserDefaults standardUserDefaults] integerForKey:ArbitratorMountMode]; - if (currentMode != mountMode) { + if (currentMode != mountMode) + { [self willChangeValueForKey:@"mountMode"]; [[NSUserDefaults standardUserDefaults] setInteger:mountMode forKey:ArbitratorMountMode]; [self didChangeValueForKey:@"mountMode"]; @@ -249,9 +273,13 @@ - (NSSet *)wholeDisks NSMutableSet *wholeDisks = [NSMutableSet new]; for (Disk *disk in disks) + { if (disk.isWholeDisk) + { [wholeDisks addObject:disk]; - + } + } + return [wholeDisks autorelease]; } @@ -284,7 +312,8 @@ - (void)addDisks:(NSSet *)objects - (void)removeDisksObject:(Disk *)anObject { - if (anObject) { + if (anObject) + { [disks removeObject:anObject]; } } @@ -304,7 +333,7 @@ DADissenterRef __attribute__((cf_returns_retained)) DiskMountApprovalCallback(DA Log(LOG_DEBUG, @"\t claimed: %s", DADiskIsClaimed(diskRef) ? "Yes" : "No"); Disk *disk = [Disk uniqueDiskForDADisk:diskRef create:YES]; - + Log(LOG_DEBUG, @"%@", disk.diskDescription); DADissenterRef dissenter = [(Arbitrator*)arbitrator approveMount:disk]; diff --git a/Source/AttachDiskImageController.m b/Source/AttachDiskImageController.m index 092c625..35a1803 100644 --- a/Source/AttachDiskImageController.m +++ b/Source/AttachDiskImageController.m @@ -39,7 +39,9 @@ + (NSArray *)diskImageFileExtensions; static NSArray *diskImageFileExtensions = nil; if (!diskImageFileExtensions) + { diskImageFileExtensions = [[NSArray alloc] initWithObjects:@"img", @"dmg", @"sparseimage", @"sparsebundle", @"iso", @"cdr", nil]; + } return diskImageFileExtensions; } @@ -60,29 +62,30 @@ - (void)dealloc - (NSTask *)hdiutilTaskWithCommand:(NSString *)command path:(NSString *)path options:(NSArray *)options password:(NSString *)password { Log(LOG_DEBUG, @"%s command: %@ path: %@ options: %@", __func__, command, path, options); - + NSTask *newTask; NSFileHandle *stdinHandle; - + newTask = [[NSTask new] autorelease]; newTask.launchPath = @"/usr/bin/hdiutil"; - + NSMutableArray *arguments = [NSMutableArray arrayWithObject:command]; [arguments addObject:path]; [arguments addObjectsFromArray:options]; - + newTask.standardOutput = NSPipe.pipe; newTask.standardError = NSPipe.pipe; newTask.standardInput = NSPipe.pipe; - - if (password) { + + if (password) + { stdinHandle = [newTask.standardInput fileHandleForWriting]; [stdinHandle writeData:[password dataUsingEncoding:NSUTF8StringEncoding]]; [stdinHandle writeData:[NSData dataWithBytes:"" length:1]]; - + [arguments addObject:@"-stdinpass"]; } - + newTask.arguments = arguments; return newTask; @@ -103,25 +106,30 @@ - (BOOL)getDiskImagePropertyList:(id *)outPlist atPath:(NSString *)path command: outputData = [[newTask.standardOutput fileHandleForReading] readDataToEndOfFile]; - if (newTask.terminationStatus == 0) { + if (newTask.terminationStatus == 0) + { NSError *plistErr = nil; *outPlist = [NSPropertyListSerialization propertyListWithData:outputData options:NSPropertyListImmutable format:nil error:&plistErr]; - if (plistErr) { + if (plistErr) + { failureReason = plistErr.localizedDescription; } - if (!*outPlist) { + if (!*outPlist) + { Log(LOG_ERR, @"Plist deserialization error: %@", failureReason); failureReason = NSLocalizedString(@"hdiutil output is not a property list.", nil); retval = NO; } } - else { + else + { Log(LOG_ERR, @"hdiutil termination status: %d", newTask.terminationStatus); failureReason = NSLocalizedString(@"hdiutil ended abnormally.", nil); retval = NO; } - - if (retval == NO && outError) { + + if (retval == NO && outError) + { info = [NSMutableDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Error executing hdiutil command", nil), NSLocalizedDescriptionKey, failureReason, NSLocalizedFailureReasonErrorKey, @@ -129,7 +137,7 @@ - (BOOL)getDiskImagePropertyList:(id *)outPlist atPath:(NSString *)path command: nil]; *outError = [NSError errorWithDomain:AppErrorDomain code:-1 userInfo:info]; } - + return retval; } @@ -140,18 +148,22 @@ - (BOOL)getDiskImageEncryptionStatus:(BOOL *)outFlag atPath:(NSString *)path err id value; isOK = [self getDiskImagePropertyList:&plist atPath:path command:@"isencrypted" password:nil error:outError]; - if (isOK) { + if (isOK) + { value = [plist objectForKey:@"encrypted"]; - if (value) { + if (value) + { *outFlag = [value boolValue]; } - else { + else + { NSMutableDictionary *info = [NSMutableDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Failed to get encryption property", nil), NSLocalizedDescriptionKey, NSLocalizedString(@"Check that \"/usr/bin/hdiutil isencrypted\" is functioning correctly.", nil), NSLocalizedRecoverySuggestionErrorKey, nil]; - if (outError != nil) { + if (outError != nil) + { *outError = [NSError errorWithDomain:AppErrorDomain code:-1 userInfo:info]; } isOK = NO; @@ -165,20 +177,24 @@ - (BOOL)getDiskImageSLAStatus:(BOOL *)outFlag atPath:(NSString *)path password:( { BOOL isOK = YES; NSMutableDictionary *plist; - + isOK = [self getDiskImagePropertyList:&plist atPath:path command:@"imageinfo" password:password error:outError]; - if (isOK) { + if (isOK) + { id value = [plist valueForKeyPath:@"Properties.Software License Agreement"]; - if (value) { + if (value) + { *outFlag = [value boolValue]; } - else { + else + { NSMutableDictionary *info = [NSMutableDictionary dictionaryWithObjectsAndKeys: NSLocalizedString(@"Failed to get SLA property", nil), NSLocalizedDescriptionKey, NSLocalizedString(@"Check that \"/usr/bin/hdiutil imageinfo\" is functioning correctly.", nil), NSLocalizedRecoverySuggestionErrorKey, nil]; - if (outError != nil) { + if (outError != nil) + { *outError = [NSError errorWithDomain:AppErrorDomain code:-1 userInfo:info]; } isOK = NO; @@ -192,11 +208,11 @@ - (BOOL)getDiskImageSLAStatus:(BOOL *)outFlag atPath:(NSString *)path password:( - (void)hdiutilAttachDidTerminate:(NSNotification *)notif { [self.window orderOut:self]; - + NSTask *theTask = notif.object; - - if (!canceled && theTask.terminationStatus != 0) { - + + if (!canceled && theTask.terminationStatus != 0) + { NSMutableDictionary *info = [NSMutableDictionary dictionary]; [info setObject:[NSString stringWithFormat:@"%@: %@.", NSLocalizedString(@"Error running hdiutil", nil), self.errorMessage] @@ -204,7 +220,7 @@ - (void)hdiutilAttachDidTerminate:(NSNotification *)notif [info setObject:self.errorMessage forKey:NSLocalizedFailureReasonErrorKey]; - + [info setObject:NSLocalizedString(@"Check the system log for details.", nil) forKey:NSLocalizedRecoverySuggestionErrorKey]; @@ -226,10 +242,10 @@ - (NSString *)promptUserForPasswordAtPath:(NSString *)path error:(NSError **)out AuthorizationItemSet *info; OSStatus status; NSString *password; - + NSString *fileName = path.lastPathComponent; NSString *prompt = [NSString stringWithFormat:NSLocalizedString(@"Enter password to access %@", nil), fileName]; - + AuthorizationItem rightsItems[1] = { { "com.apple.builtin.generic-unlock", 0, NULL, 0 } }; rights.count = sizeof(rightsItems) / sizeof(AuthorizationItem);; rights.items = rightsItems; @@ -242,33 +258,40 @@ - (NSString *)promptUserForPasswordAtPath:(NSString *)path error:(NSError **)out env.items = envItems; flags = kAuthorizationFlagDefaults| kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize; - + authorization = [SFAuthorization authorization]; if (![authorization obtainWithRights:&rights flags:flags environment:&env authorizedRights:NULL error:outError]) { if (outError && [*outError code] != errAuthorizationCanceled) + { Log(LOG_ERR, @"Authorization error: %@", *outError); + } return nil; } - + password = nil; status = AuthorizationCopyInfo([authorization authorizationRef], kAuthorizationEnvironmentPassword, &info); - - if (status == noErr) { + + if (status == noErr) + { if (info->count > 0 && info->items[0].valueLength > 0) + { password = [NSString stringWithUTF8String:info->items[0].value]; + } } - else { - if (outError) { + else + { + if (outError) + { NSDictionary *info; info = [NSDictionary dictionaryWithObject:NSLocalizedString(@"Authorization did not return a password.", nil) forKey:NSLocalizedDescriptionKey]; *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:info]; } } - + AuthorizationFreeItemSet(info); return password; @@ -277,45 +300,54 @@ - (NSString *)promptUserForPasswordAtPath:(NSString *)path error:(NSError **)out - (BOOL)attachDiskImageAtPath:(NSString *)path options:(NSArray *)options password:(NSString *)password error:(NSError **)outError { Log(LOG_DEBUG, @"%s path: %@ options: %@", __func__, path, options); - + BOOL isEncrypted = NO, hasSLA = NO; NSTask *newTask; - if (!password) { - if ([self getDiskImageEncryptionStatus:&isEncrypted atPath:path error:outError]) { - if (isEncrypted) { + if (!password) + { + if ([self getDiskImageEncryptionStatus:&isEncrypted atPath:path error:outError]) + { + if (isEncrypted) + { password = [self promptUserForPasswordAtPath:path error:outError]; if (!password) + { return [*outError code] == errAuthorizationCanceled ? YES : NO; + } } } - else { + else + { return NO; // get encryption status failed } } - + if ([self getDiskImageSLAStatus:&hasSLA atPath:path password:password error:outError] == NO) + { return NO; - + } + self.title = [NSString stringWithFormat:@"Attaching \"%@\" ...", path.lastPathComponent]; - + NSMutableArray *arguments = [NSMutableArray array]; [arguments addObject:@"-plist"]; [arguments addObject:@"-puppetstrings"]; [arguments addObjectsFromArray:options]; - + newTask = [self hdiutilTaskWithCommand:@"attach" path:path options:arguments password:password]; - if (hasSLA) { + if (hasSLA) + { [[newTask.standardInput fileHandleForWriting] writeData:[NSData dataWithBytes:"Y\n" length:3]]; } - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processStandardOutput:) name:NSFileHandleReadCompletionNotification object:[newTask.standardOutput fileHandleForReading]]; [[newTask.standardOutput fileHandleForReading] readInBackgroundAndNotify]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processStandardError:) name:NSFileHandleReadCompletionNotification @@ -332,51 +364,68 @@ - (BOOL)attachDiskImageAtPath:(NSString *)path options:(NSArray *)options passwo - (void)attachDiskImageOptionsSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo; { if (sheet.isSheet) + { [sheet orderOut:self]; - + } + NSString *password = nil; BOOL isOK; NSError *error; NSMutableArray *attachOptions = [NSMutableArray array]; - - if (returnCode == NSModalResponseOK) { + + if (returnCode == NSModalResponseOK) + { NSDictionary *options = self.userInfo; - + if ([[options objectForKey:@"readOnly"] boolValue] == YES) + { [attachOptions addObject:@"-readonly"]; - + } + if ([[options objectForKey:@"noVerify"] boolValue] == YES) + { [attachOptions addObject:@"-noverify"]; + } else + { [attachOptions addObject:@"-verify"]; - + } + if ([[options objectForKey:@"attemptMount"] boolValue] == YES) { [attachOptions addObject:@"-mount"]; [attachOptions addObject:@"optional"]; - - if ([[options objectForKey:@"noOwners"] boolValue] == YES) { + + if ([[options objectForKey:@"noOwners"] boolValue] == YES) + { [attachOptions addObject:@"-owners"]; [attachOptions addObject:@"off"]; } - + if ([[options objectForKey:@"noBrowse"] boolValue] == YES) + { [attachOptions addObject:@"-nobrowse"]; - + } + NSString *rootPath = [options objectForKey:@"rootPath"]; - if (rootPath && [rootPath length]) { + if (rootPath && [rootPath length]) + { [attachOptions addObject:@"-mountroot"]; [attachOptions addObject:rootPath]; } } - else { + else + { [attachOptions addObject:@"-nomount"]; } - + password = [options objectForKey:@"password"]; - + isOK = [self attachDiskImageAtPath:[options objectForKey:@"filePath"] options:attachOptions password:password error:&error]; - if (!isOK) [NSApp presentError:error]; + if (!isOK) + { + [NSApp presentError:error]; + } } } @@ -389,18 +438,19 @@ - (void)performAttachDiskImage panel.canChooseDirectories = NO; panel.allowsMultipleSelection = NO; panel.message = NSLocalizedString(@"Select a disk image to attach:", nil); - + panel.allowedFileTypes = [self.class diskImageFileExtensions]; - + [self.userInfo setObject:[NSNumber numberWithBool:YES] forKey:@"readOnly"]; - + panel.accessoryView = self.view; panel.delegate = self; - + // This is a little strange, but left over from an initial implementation which used cascading sheets on // the main window. The code sheetDidEnd code is usable for this variation, though - - if ([panel runModal] == NSModalResponseOK) { + + if ([panel runModal] == NSModalResponseOK) + { [self.userInfo setObject:panel.URL.path forKey:@"filePath"]; [self attachDiskImageOptionsSheetDidEnd:panel returnCode:NSModalResponseOK contextInfo:self]; } @@ -428,39 +478,45 @@ - (void)panelSelectionDidChange:(id)sender NSString *filename; NSFileHandle *handle; NSData *header; - + Log(LOG_DEBUG, @"%s ", __func__); - + filename = [sender filename]; - - if (!filename) { + + if (!filename) + { filename = [[[sender directoryURL] path] stringByAppendingPathComponent:@"token"]; // SparseBundle } - + Log(LOG_DEBUG, @"filename: %@\n", filename); - + handle = [NSFileHandle fileHandleForReadingAtPath:filename]; - - if (handle) { - + + if (handle) + { header = [handle readDataOfLength:8]; [handle closeFile]; - + // This check only works for Version 2 encrypted disk images, the default in Tiger and beyond // We check only to remind the user if a password is needed, so do not need to catch every case. // // http://lorenzo.yellowspace.net/corrupt-sparseimage.html // - - if (header && [header length] == 8) { - + + if (header && [header length] == 8) + { if (memcmp("encrcdsa", [header bytes], 8) == 0) + { [self.userInfo setObject:[NSNumber numberWithBool:YES] forKey:@"needPassword"]; + } else + { [self.userInfo setObject:[NSNumber numberWithBool:NO] forKey:@"needPassword"]; + } } } - else { + else + { [self.userInfo setObject:[NSNumber numberWithBool:NO] forKey:@"needPassword"]; } } @@ -474,23 +530,29 @@ - (NSString *)_parseNextMessage:(NSMutableString **)bufferRef newData:(NSData *) NSString *newString; // If data, append to buffer - - if (data && [data length] > 0) { + + if (data && [data length] > 0) + { newString = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding]; - - if (newString) { + + if (newString) + { if (*buffer) + { [*buffer appendString:newString]; + } else + { *buffer = [newString mutableCopy]; + } [newString release]; } } - + // Parse either a plist or a single-line message - + NSString *endOfMessage = [*buffer hasPrefix:@"\n" : @"\n"; - + NSRange range = [*buffer rangeOfString:endOfMessage]; if (range.location != NSNotFound) { @@ -508,20 +570,23 @@ - (void)processStandardOutput:(NSNotification *)notif NSString *mymessage; NSFileHandle *stdoutHandle = notif.object; double percentage; - + // NSData *data = [stdoutHandle availableData]; NSData *data = [[notif userInfo] objectForKey:NSFileHandleNotificationDataItem]; - + while ((mymessage = [self _parseNextMessage:&stdoutBuffer newData:data])) { data = nil; - - if ([mymessage hasPrefix:@"PERCENT:"]) { + + if ([mymessage hasPrefix:@"PERCENT:"]) + { percentage = [[mymessage substringFromIndex:[@"PERCENT:" length]] doubleValue]; Log(LOG_DEBUG, @"Percent: %f", percentage); - - if (percentage > 0.0) { - if (!self.isVerifying) { + + if (percentage > 0.0) + { + if (!self.isVerifying) + { self.isVerifying = YES; [self showWindow:self]; [NSApp unhide:self]; @@ -530,58 +595,64 @@ - (void)processStandardOutput:(NSNotification *)notif self.progress = percentage; } } - - else if ([mymessage hasPrefix:@"MESSAGE:"]) { + else if ([mymessage hasPrefix:@"MESSAGE:"]) + { mymessage = [mymessage substringFromIndex:[@"MESSAGE:" length]]; Log(LOG_DEBUG, @"Message: %@", mymessage); - + self.message = mymessage; } - - else if ([mymessage hasPrefix:@"hdiutil:"]) { + else if ([mymessage hasPrefix:@"hdiutil:"]) + { mymessage = [mymessage substringFromIndex:[@"hdiutil:" length]]; // error? Log(LOG_ERR, @"Error: %@", mymessage); } - - else if ([mymessage hasPrefix:@" %s", __func__, diskRef, DADiskGetBSDName(diskRef)); - + if (DADiskValidate(diskRef)) { Disk *disk = [Disk uniqueDiskForDADisk:diskRef create:YES]; @@ -89,33 +105,42 @@ void DiskAppearedCallback(DADiskRef diskRef, void *context) void DiskDisappearedCallback(DADiskRef diskRef, void *context) { - if (context != [Disk class]) return; - + if (context != [Disk class]) + { + return; + } + Log(LOG_DEBUG, @"%s <%p> %s", __func__, diskRef, DADiskGetBSDName(diskRef)); - + Disk *tmpDisk = [Disk uniqueDiskForDADisk:diskRef create:NO]; - if (!tmpDisk) { + if (!tmpDisk) + { return; } - + [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidDisappearNotification object:tmpDisk]; - + [tmpDisk diskDidDisappear]; } void DiskDescriptionChangedCallback(DADiskRef diskRef, CFArrayRef keys, void *context) { - if (context != [Disk class]) return; - + if (context != [Disk class]) + { + return; + } + Log(LOG_DEBUG, @"%s <%p> %s, keys changed:", __func__, diskRef, DADiskGetBSDName(diskRef)); Log(LOG_DEBUG, @"%@", keys); - - for (Disk *disk in uniqueDisks) { - if (CFHash(diskRef) == disk.hash) { + + for (Disk *disk in uniqueDisks) + { + if (CFHash(diskRef) == disk.hash) + { CFDictionaryRef desc = DADiskCopyDescription(diskRef); disk.diskDescription = (NSDictionary *)desc; SafeCFRelease(desc); - + [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidChangeNotification object:disk]; } } @@ -160,13 +185,16 @@ void DiskMountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *contex void DiskUnmountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *context) { NSDictionary *info = nil; - - if (dissenter) { + + if (dissenter) + { DAReturn status = DADissenterGetStatus(dissenter); NSString *statusString = (NSString *) DADissenterGetStatusString(dissenter); if (!statusString) + { statusString = [NSString stringWithFormat:@"Error code: %d", status]; + } Log(LOG_DEBUG, @"%s disk %@ dissenter: (%d) %@", __func__, context, status, statusString); @@ -176,10 +204,11 @@ void DiskUnmountCallback(DADiskRef diskRef, DADissenterRef dissenter, void *cont statusString, NSLocalizedRecoverySuggestionErrorKey, nil]; } - else { + else + { Log(LOG_DEBUG, @"%s disk %@ unmounted", __func__, context); } - + [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptUnmountNotification object:context userInfo:info]; } @@ -187,30 +216,33 @@ void DiskEjectCallback(DADiskRef diskRef, DADissenterRef dissenter, void *contex { [(id)context autorelease]; // got retained when passed in NSDictionary *info = nil; - - if (dissenter) { + + if (dissenter) + { DAReturn status = DADissenterGetStatus(dissenter); - + NSString *statusString = (NSString *) DADissenterGetStatusString(dissenter); if (!statusString) + { statusString = [NSString stringWithFormat:@"Error code: %d", status]; - + } + Log(LOG_INFO, @"%s disk: %@ dissenter: (%d) %@", __func__, context, status, statusString); - + info = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:status], DAStatusErrorKey, statusString, NSLocalizedFailureReasonErrorKey, statusString, NSLocalizedRecoverySuggestionErrorKey, nil]; } - else { + else + { Log(LOG_DEBUG, @"%s disk ejected: %@ ", __func__, context); } - + [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptEjectNotification object:context userInfo:info]; } - NSString * const DADiskDidAppearNotification = @"DADiskDidAppearNotification"; NSString * const DADiskDidDisappearNotification = @"DADiskDidDisppearNotification"; NSString * const DADiskDidChangeNotification = @"DADiskDidChangeNotification"; diff --git a/Source/DiskArbitratorAppController+Toolbar.m b/Source/DiskArbitratorAppController+Toolbar.m index d242394..41e21fd 100644 --- a/Source/DiskArbitratorAppController+Toolbar.m +++ b/Source/DiskArbitratorAppController+Toolbar.m @@ -16,16 +16,16 @@ - (NSImage *)attachDiskImageIcon { NSImage *dmgIcon = [[NSWorkspace sharedWorkspace] iconForFileType:@"dmg"]; NSImage *plugImage = [NSImage imageNamed:@"ToolbarItem Attach Disk Plug"]; - + NSBitmapImageRep *compositedImage = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:32 pixelsHigh:32 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bytesPerRow:0 bitsPerPixel:0]; [NSGraphicsContext saveGraphicsState]; [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:compositedImage]]; [[NSGraphicsContext currentContext] setShouldAntialias:NO]; - + [dmgIcon drawInRect:NSMakeRect(0, 0, 32, 32) fromRect:NSZeroRect operation:NSCompositingOperationCopy fraction:1.0]; [plugImage drawInRect:NSMakeRect(2, 16, 16, 16) fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; - + [NSGraphicsContext restoreGraphicsState]; return [[NSImage alloc] initWithData:[compositedImage TIFFRepresentation]]; @@ -34,16 +34,20 @@ - (NSImage *)attachDiskImageIcon - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag { NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier]; - - if ([itemIdentifier isEqual:ToolbarItemInfoIdentifier]) { // Info/Inspect + + if ([itemIdentifier isEqual:ToolbarItemInfoIdentifier]) + { + // Info/Inspect item.label = NSLocalizedString(@"Info", nil); item.paletteLabel = NSLocalizedString(@"Info", nil); item.image = [NSImage imageNamed:@"ToolbarItem Info"]; // NSImageNameInfo]]; item.target = self; item.action = @selector(performGetInfo:); item.toolTip = NSLocalizedString(@"Show detailed disk info", nil); - } - else if ([itemIdentifier isEqual:ToolbarItemEjectIdentifier]) { // Eject + } + else if ([itemIdentifier isEqual:ToolbarItemEjectIdentifier]) + { + // Eject item.label = NSLocalizedString(@"Eject", nil); item.paletteLabel = NSLocalizedString(@"Eject", nil); item.image = [NSImage imageNamed:@"ToolbarItem Eject"]; @@ -51,7 +55,9 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString item.action = @selector(performEject:); item.toolTip = NSLocalizedString(@"Eject removable media.", nil); } - else if ([itemIdentifier isEqual:ToolbarItemMountIdentifier]) { // Mount + else if ([itemIdentifier isEqual:ToolbarItemMountIdentifier]) + { + // Mount item.label = NSLocalizedString(@"Mount", nil); item.paletteLabel = NSLocalizedString(@"Mount/Unmount", nil); item.image = [NSImage imageNamed:@"ToolbarItem Mount"]; @@ -59,7 +65,9 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString item.action = @selector(performMountOrUnmount:); item.toolTip = NSLocalizedString(@"Select a volume, then click to mount or unmount.", nil); } - else if ([itemIdentifier isEqual:ToolbarItemAttachDiskImageIdentifier]) { // Attach Disk Image + else if ([itemIdentifier isEqual:ToolbarItemAttachDiskImageIdentifier]) + { + // Attach Disk Image item.label = NSLocalizedString(@"Attach", nil); item.paletteLabel = NSLocalizedString(@"Attach Disk Image", nil); // item.image = [NSImage imageNamed:@"ToolbarItem Attach Disk Image"]; @@ -71,9 +79,9 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString return item; } - //---------------------------------------------------------- -- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { +- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar +{ return [NSArray arrayWithObjects: NSToolbarSeparatorItemIdentifier, NSToolbarSpaceItemIdentifier, @@ -86,9 +94,9 @@ - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { nil]; } - //---------------------------------------------------------- -- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { +- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar +{ return [NSArray arrayWithObjects: ToolbarItemInfoIdentifier, ToolbarItemEjectIdentifier, @@ -102,34 +110,39 @@ - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { //---------------------------------------------------------- --(BOOL)validateToolbarItem:(NSToolbarItem*)toolbarItem { +-(BOOL)validateToolbarItem:(NSToolbarItem*)toolbarItem +{ BOOL enabled = YES; - + if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemInfoIdentifier]) + { enabled = YES; - - else if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemMountIdentifier]) { - + } + else if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemMountIdentifier]) + { // Enable the item if the disk is mountable // Set the label "Unmount" if mounted, otherwise to "Mount" - - if (self.canUnmountSelectedDisk) { + + if (self.canUnmountSelectedDisk) + { [toolbarItem setLabel:NSLocalizedString(@"Unmount", nil)]; enabled = YES; } - else { + else + { [toolbarItem setLabel:NSLocalizedString(@"Mount", nil)]; if (!self.canMountSelectedDisk) + { enabled = NO; + } } } - - else if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemEjectIdentifier]) { - + else if ([[toolbarItem itemIdentifier] isEqual:ToolbarItemEjectIdentifier]) + { enabled = self.canEjectSelectedDisk; } - + return enabled; } @@ -145,12 +158,9 @@ void SetupToolbar(NSWindow *window, id delegate) [window setToolbar:toolbar]; } - - // Toolbar Item Identifier constants NSString * const ToolbarItemMainIdentifier = @"ToolbarItemMainIdentifier"; NSString * const ToolbarItemInfoIdentifier = @"ToolbarItemInfoIdentifier"; NSString * const ToolbarItemMountIdentifier = @"ToolbarItemMountIdentifier"; NSString * const ToolbarItemEjectIdentifier = @"ToolbarItemEjectIdentifier"; NSString * const ToolbarItemAttachDiskImageIdentifier = @"ToolbarItemAttachDiskImageIdentifier"; - diff --git a/Source/DiskArbitratorAppController.m b/Source/DiskArbitratorAppController.m index 26f793d..c8dab1c 100644 --- a/Source/DiskArbitratorAppController.m +++ b/Source/DiskArbitratorAppController.m @@ -42,86 +42,101 @@ @implementation AppController - (void)dealloc { if (arbitrator.isActivated) + { [arbitrator deactivate]; + } } - (void)setStatusItemIconWithName:(NSString *)name { NSString *iconPath = [[NSBundle mainBundle] pathForResource:name ofType:@"png"]; NSImage *statusIcon = [[NSImage alloc] initWithContentsOfFile:iconPath]; - statusItem.image = statusIcon; + statusItem.button.image = statusIcon; } - (void)refreshStatusItemIcon { if (arbitrator.isActivated == NO) + { [self setStatusItemIconWithName:@"StatusItem Disabled 1"]; - + } else if (arbitrator.mountMode == MM_BLOCK) + { [self setStatusItemIconWithName:@"StatusItem Green"]; - + } else if (arbitrator.mountMode == MM_READONLY) + { [self setStatusItemIconWithName:@"StatusItem Orange"]; - + } else + { NSAssert1(NO, @"Invalid mount mode: %ld\n", (long)arbitrator.mountMode); + } } - (void)applicationDidFinishLaunching:(NSNotification *)notification { [self refreshLaunchAgentStatus]; - + NSMutableDictionary *defaults = [NSMutableDictionary dictionary]; [defaults setObject:[NSNumber numberWithInt:LOG_INFO] forKey:AppLogLevelDefaultsKey]; [defaults setObject:[NSNumber numberWithBool:YES] forKey:@"ShowMainWindowAtStartup"]; [defaults setObject:[NSNumber numberWithBool:NO] forKey:@"ShowMainWindowAtActivate"]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; - + displayErrorQueue = [NSMutableArray new]; - + NSStatusBar *bar = [NSStatusBar systemStatusBar]; self.statusItem = [bar statusItemWithLength:NSSquareStatusItemLength]; NSImage *altImage = [[NSImage imageNamed:@"StatusItem Disabled 1.png"] copy]; [altImage setTemplate:YES]; - self.statusItem.alternateImage = altImage; - [self.statusItem setHighlightMode:YES]; + self.statusItem.button.alternateImage = altImage; + [(NSButtonCell *)self.statusItem.button.cell setHighlightsBy:NSPushInCellMask]; [self setStatusItemIconWithName:@"StatusItem Disabled 1"]; statusItem.menu = statusMenu; - + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(diskDidChange:) name:DADiskDidChangeNotification object:nil]; [center addObserver:self selector:@selector(didAttemptEject:) name:DADiskDidAttemptEjectNotification object:nil]; [center addObserver:self selector:@selector(didAttemptMount:) name:DADiskDidAttemptMountNotification object:nil]; [center addObserver:self selector:@selector(didAttemptUnmount:) name:DADiskDidAttemptUnmountNotification object:nil]; - + self.arbitrator = [Arbitrator new]; [arbitrator addObserver:self forKeyPath:@"isActivated" options:0 context:NULL]; [arbitrator addObserver:self forKeyPath:@"mountMode" options:0 context:NULL]; [self refreshStatusItemIcon]; // arbitrator status initial state is taken from user defaults, which was initialized before KVO initialized self.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"BSDNameNumber" ascending:YES]]; - + SetupToolbar(window, self); window.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces; window.worksWhenModal = YES; - - [tableView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; - + + [tableView registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowMainWindowAtStartup"]) + { [window makeKeyAndOrderFront:self]; + } } - (void)applicationDidBecomeActive:(NSNotification *)notification { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"ShowMainWindowAtActivate"]) + { [window makeKeyAndOrderFront:self]; + } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == arbitrator) + { if ([keyPath isEqual:@"isActivated"] || [keyPath isEqual:@"mountMode"]) + { [self refreshStatusItemIcon]; + } + } } - (IBAction)showAboutPanel:(id)sender @@ -141,7 +156,9 @@ - (IBAction)showPreferences:(id)sender static NSWindowController *controller = nil; if (!controller) + { controller = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"]; + } [controller.window makeKeyAndOrderFront:self]; [NSApp activateIgnoringOtherApps:YES]; @@ -160,9 +177,13 @@ - (IBAction)performDeactivation:(id)sender - (IBAction)toggleActivation:(id)sender { if (arbitrator.isActivated) + { [self performDeactivation:sender]; + } else + { [self performActivation:sender]; + } } - (IBAction)performSetMountBlockMode:(id)sender @@ -179,24 +200,33 @@ - (void)performMountSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode sheetController:(SheetController *)controller; { [sheet orderOut:self]; - + Disk *selectedDisk = self.selectedDisk; NSMutableArray *arguments = [NSMutableArray array]; - - if (returnCode == NSModalResponseOK) { + + if (returnCode == NSModalResponseOK) + { NSDictionary *options = controller.userInfo; - + if ([[options objectForKey:@"readOnly"] boolValue] == YES) + { [arguments addObject:@"rdonly"]; + } if ([[options objectForKey:@"noOwners"] boolValue] == YES) + { [arguments addObject:@"noowners"]; + } if ([[options objectForKey:@"noBrowse"] boolValue] == YES) + { [arguments addObject:@"nobrowse"]; + } if ([[options objectForKey:@"ignoreJournal"] boolValue] == YES) + { [arguments addObject:@"-j"]; + } NSString *path = [options objectForKey:@"path"]; @@ -213,11 +243,11 @@ - (IBAction)performMount:(id)sender SheetController *controller = [[SheetController alloc] initWithWindowNibName:@"MountOptions"]; [controller window]; // triggers controller to load the NIB - + [[controller userInfo] setObject:[NSNumber numberWithBool:YES] forKey:@"readOnly"]; [[controller userInfo] setObject:[NSNumber numberWithBool:selectedDisk.isHFS] forKey:@"ignoreJournal"]; [[controller userInfo] setObject:[NSNumber numberWithBool:selectedDisk.isHFS] forKey:@"isHFS"]; - + [window makeKeyAndOrderFront:self]; [window beginSheet:controller.window completionHandler:^(NSModalResponse returnCode) @@ -230,20 +260,27 @@ - (IBAction)performMount:(id)sender - (IBAction)performUnmount:(id)sender { Disk *theDisk = self.selectedDisk; - - if (!theDisk) return; - + + if (!theDisk) + { + return; + } + [theDisk unmountWithOptions: theDisk.isWholeDisk ? kDiskUnmountOptionWhole : kDiskUnmountOptionDefault]; } - (IBAction)performMountOrUnmount:(id)sender { Disk *theDisk = self.selectedDisk; - + if (theDisk.isMounted) + { [self performUnmount:sender]; + } else + { [self performMount:sender]; + } } - (void)_childDidAttemptUnmountBeforeEject:(NSNotification *)notif @@ -251,59 +288,71 @@ - (void)_childDidAttemptUnmountBeforeEject:(NSNotification *)notif Disk *disk = notif.object; // Disk may be a mountable whole disk that we were waiting on, so the parent may be the disk itself - + Disk *parent = disk.isWholeDisk ? disk : disk.parent; - + Log(LOG_DEBUG, @"%s disk: %@ child: %@", __func__, parent, disk); - + [[NSNotificationCenter defaultCenter] removeObserver:self name:DADiskDidAttemptUnmountNotification object:disk]; - + // Confirm the child unmounted - - if (disk.isMounted) { + + if (disk.isMounted) + { // Unmount of child failed - + NSMutableDictionary *info = [[notif userInfo] mutableCopy]; - + Log(LOG_INFO, @"%s eject disk: %@ canceled due to mounted child: %@", __func__, disk, info); - + NSString *statusString = [NSString stringWithFormat:@"%@:\n\n%@", NSLocalizedString(@"Failed to unmount child", nil), [info objectForKey:NSLocalizedFailureReasonErrorKey]]; - + [info setObject:statusString forKey:NSLocalizedFailureReasonErrorKey]; [info setObject:statusString forKey:NSLocalizedRecoverySuggestionErrorKey]; - + [[NSNotificationCenter defaultCenter] postNotificationName:DADiskDidAttemptEjectNotification object:disk userInfo:info]; } - + // Child from notification is unmounted, check for remaining children to unmount - - for (Disk *child in parent.children) { + + for (Disk *child in parent.children) + { if (child.isMounted) + { return; // Still waiting for child + } } - + // Need to test if parent is ejectable because we enable "Eject" for a disk // that has children that can be unmounted (ala Disk Utility) - + if (parent.isEjectable) + { [parent eject]; + } } - (IBAction)performEject:(id)sender { Disk *selectedDisk = self.selectedDisk; BOOL waitForChildren = NO; - + NSSet *disks; if (selectedDisk.isWholeDisk && selectedDisk.isLeaf) + { disks = [NSSet setWithObject:selectedDisk]; + } else + { disks = selectedDisk.children; - - for (Disk *disk in disks) { - if (disk.isMountable && disk.isMounted) { + } + + for (Disk *disk in disks) + { + if (disk.isMountable && disk.isMounted) + { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_childDidAttemptUnmountBeforeEject:) name:DADiskDidAttemptUnmountNotification @@ -313,9 +362,12 @@ - (IBAction)performEject:(id)sender } } - if (!waitForChildren) { + if (!waitForChildren) + { if (selectedDisk.isEjectable) + { [selectedDisk eject]; + } } } @@ -325,7 +377,8 @@ - (IBAction)performGetInfo:(id)sender controller.disk = self.selectedDisk; [controller showWindow:self]; [controller refreshDiskInfo]; - if (!diskInfoControllers) { + if (!diskInfoControllers) + { diskInfoControllers = [[NSMutableArray alloc] init]; } [diskInfoControllers addObject:controller]; @@ -335,13 +388,16 @@ - (IBAction)performGetInfo:(id)sender - (void)diskInfoWindowDidClose:(NSNotification *)notif { DiskInfoController *controllerToRemove = nil; - for (DiskInfoController *controller in diskInfoControllers) { - if ([controller window] == [notif object]) { + for (DiskInfoController *controller in diskInfoControllers) + { + if ([controller window] == [notif object]) + { controllerToRemove = controller; break; } } - if (controllerToRemove) { + if (controllerToRemove) + { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:[notif object]]; [diskInfoControllers removeObject:controllerToRemove]; } @@ -356,33 +412,41 @@ - (IBAction)performAttachDiskImage:(id)sender #pragma mark Launch Agent -- (NSString *)userLaunchAgentPath { - +- (NSString *)userLaunchAgentPath +{ NSString *path = nil; - + CFStringRef identifier = CFBundleGetIdentifier(CFBundleGetMainBundle()); - if (identifier) { + if (identifier) + { path = [NSString stringWithFormat:@"%@/%@/%@.plist", NSHomeDirectory(), @"Library/LaunchAgents", identifier]; } return path; } -- (void)refreshLaunchAgentStatus { +- (void)refreshLaunchAgentStatus +{ NSFileManager *fm = [NSFileManager defaultManager]; - + self.hasUserLaunchAgent = [fm fileExistsAtPath:self.userLaunchAgentPath]; if (self.hasUserLaunchAgent) + { self.installUserLaunchAgentMenuTitle = @"Uninstall User Launch Agent..."; + } else + { self.installUserLaunchAgentMenuTitle = @"Install User Launch Agent..."; + } } -- (BOOL)canInstallLaunchAgent { +- (BOOL)canInstallLaunchAgent +{ return (self.hasUserLaunchAgent == NO); } -- (IBAction)installUserLaunchAgent:(id)sender { +- (IBAction)installUserLaunchAgent:(id)sender +{ NSError *error; NSAlert *alert = nil; NSString *dstPath; @@ -390,44 +454,55 @@ - (IBAction)installUserLaunchAgent:(id)sender { [NSApp activateIgnoringOtherApps:YES]; - if (self.hasUserLaunchAgent) { - + if (self.hasUserLaunchAgent) + { dstPath = self.userLaunchAgentPath; - - if ([fm removeItemAtPath:dstPath error:&error] == YES) { + + if ([fm removeItemAtPath:dstPath error:&error] == YES) + { alert = [[NSAlert alloc] init]; alert.messageText = @"Launch Agent removed. Changes will take effect on next login."; } - else { + else + { alert = [NSAlert alertWithError:error]; } } - else { + else + { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:@"us.burghardt.Disk-Arbitrator" forKey:@"Label"]; [dict setObject:[NSArray arrayWithObject:[[NSBundle mainBundle] executablePath]] forKey:@"ProgramArguments"]; [dict setObject:[NSNumber numberWithBool:NO] forKey:@"Disabled"]; [dict setObject:[NSNumber numberWithBool:YES] forKey:@"EnableTransactions"]; [dict setObject:[NSNumber numberWithBool:YES] forKey:@"KeepAlive"]; - + dstPath = [self userLaunchAgentPath]; - + NSData *plistData; plistData = [NSPropertyListSerialization dataWithPropertyList:dict format:NSPropertyListXMLFormat_v1_0 options:0 error:&error]; - if (!plistData) { + if (!plistData) + { alert = [NSAlert alertWithError:error]; - } else { - if (![plistData writeToFile:dstPath options:NSDataWritingAtomic error:&error]) { + } + else + { + if (![plistData writeToFile:dstPath options:NSDataWritingAtomic error:&error]) + { alert = [NSAlert alertWithError:error]; - } else { + } + else + { alert = [[NSAlert alloc] init]; alert.messageText = @"Launch Agent installed. Changes will take effect on next login."; } } } if (alert) + { [alert runModal]; - + } + [self refreshLaunchAgentStatus]; } @@ -436,11 +511,15 @@ - (IBAction)installUserLaunchAgent:(id)sender { - (Disk *)selectedDisk { NSIndexSet *indexes = [disksArrayController selectionIndexes]; - + if (indexes.count == 1) + { return [disksArrayController.arrangedObjects objectAtIndex:indexes.lastIndex]; + } else + { return nil; + } } - (BOOL)canEjectSelectedDisk @@ -452,10 +531,14 @@ - (BOOL)canEjectSelectedDisk Disk *selectedDisk = self.selectedDisk; BOOL canEject = [selectedDisk isEjectable]; - if (!canEject) { - for (Disk *child in selectedDisk.children) { + if (!canEject) + { + for (Disk *child in selectedDisk.children) + { if (child.isMountable && child.isMounted) + { canEject = YES; + } } } return canEject; @@ -464,11 +547,15 @@ - (BOOL)canEjectSelectedDisk - (BOOL)canMountSelectedDisk { Disk *disk = self.selectedDisk; - + if (disk.isMountable && !disk.isMounted) + { return YES; + } else + { return NO; + } } - (BOOL)canUnmountSelectedDisk @@ -494,12 +581,14 @@ - (BOOL)canUnmountSelectedDisk - (id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)column row:(int)rowIndex { Disk *disk; - + NSParameterAssert(rowIndex >= 0 && rowIndex < arbitrator.disks.count); disk = [disksArrayController.arrangedObjects objectAtIndex:rowIndex]; if ([column.identifier isEqual:@"BSDName"]) + { return disk.BSDName; + } // fprintf(stdout, "getting value: %s\n", disk.BSDName.UTF8String); return disk; @@ -511,14 +600,17 @@ - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id ) NSPasteboard* pboard = [info draggingPasteboard]; - if (op == NSDragOperationCopy && [pboard.types containsObject:NSFilenamesPboardType]) { - - NSArray *files = [pboard propertyListForType:NSFilenamesPboardType]; + if (op == NSDragOperationCopy && [pboard.types containsObject:NSPasteboardTypeFileURL]) + { + NSArray *files = [pboard propertyListForType:NSPasteboardTypeFileURL]; NSArray *extensions = [AttachDiskImageController diskImageFileExtensions]; - for (NSString *file in files) { + for (NSString *file in files) + { if ([extensions containsObject:[file pathExtension]] == NO) + { return NSDragOperationNone; + } } return NSDragOperationCopy; } @@ -527,17 +619,21 @@ - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id ) - (void)doAttachDiskImageAtPath:(NSString *)path { - NSError *error; - + NSError *error = nil; + AttachDiskImageController *controller = [[AttachDiskImageController alloc] initWithWindowNibName:@"AttachDiskImageAccessory"]; [controller window]; NSArray *options; if (arbitrator.isActivated) + { options = [NSArray arrayWithObjects:@"-readonly", @"-nomount", nil]; + } else + { options = [NSArray arrayWithObjects:@"-readonly", @"-mount", @"optional", nil]; - + } + if (![controller attachDiskImageAtPath:path options:options password:nil error:&error]) { [NSApp presentError:error]; @@ -548,16 +644,19 @@ - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )info row:(int)row dropOperation:(NSTableViewDropOperation)operation { NSPasteboard* pboard = [info draggingPasteboard]; - + Log(LOG_DEBUG, @"%s", __func__); - if (operation == NSDragOperationCopy && [pboard.types containsObject:NSFilenamesPboardType] ) { - NSArray *files = [pboard propertyListForType:NSFilenamesPboardType]; + if (operation == NSDragOperationCopy && [pboard.types containsObject:NSPasteboardTypeFileURL] ) + { + NSArray *files = [pboard propertyListForType:NSPasteboardTypeFileURL]; Log(LOG_DEBUG, @"files: %@", files); - + for (NSString *file in files) + { [self performSelector:@selector(doAttachDiskImageAtPath:) withObject:file afterDelay:0.01]; + } } return YES; } @@ -574,17 +673,18 @@ - (void)windowWillClose:(NSNotification *)notification - (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)contextInfo { // If another sheet has unexpected been displayed, recover gracefully - - if (window.attachedSheet) { + + if (window.attachedSheet) + { Log(LOG_INFO, @"Discarding pending errors: %@", displayErrorQueue); [displayErrorQueue removeAllObjects]; return; } - + if (displayErrorQueue.count > 0) { NSError *nextError = [displayErrorQueue objectAtIndex:0]; - + [window makeKeyAndOrderFront:self]; [NSApp presentError:nextError modalForWindow:window @@ -593,13 +693,13 @@ - (void)didPresentErrorWithRecovery:(BOOL)didRecover contextInfo:(void *)context contextInfo:NULL]; [displayErrorQueue removeObjectAtIndex:0]; - } + } } - (void)diskDidChange:(NSNotification *)notif { NSUInteger row = [disksArrayController.arrangedObjects indexOfObject:notif.object]; - + [tableView setNeedsDisplayInRect:[tableView rectOfRow:row]]; [window.toolbar validateVisibleItems]; } @@ -608,31 +708,36 @@ - (void)didAttemptMount:(NSNotification *)notif { Disk *disk = notif.object; NSMutableDictionary *info; - - if (disk.isMounted) { + + if (disk.isMounted) + { Log(LOG_DEBUG, @"%s: Mounted: %@", __func__, disk.BSDName); } - else { + else + { // If the mount failed, the notification userInfo will have keys/values that correspond to an NSError - + info = [[notif userInfo] mutableCopy]; - + NSString *reason = [info objectForKey:NSLocalizedFailureReasonErrorKey]; Log(LOG_ERR, @"Mount failed: %@ (%@) %@", disk.BSDName, [info objectForKey:DAStatusErrorKey], reason); - + [info setObject:[NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"Mount rejected", nil), disk.BSDName] forKey:NSLocalizedDescriptionKey]; - + NSError *error = [NSError errorWithDomain:AppErrorDomain code:[[info objectForKey:DAStatusErrorKey] intValue] userInfo:info]; // Don't show our internal dissenter error - if (![reason isEqualToString:arbitrator.dissenterMessage]) { - if (window.attachedSheet) { + if (![reason isEqualToString:arbitrator.dissenterMessage]) + { + if (window.attachedSheet) + { [displayErrorQueue addObject:error]; } - else { + else + { [window makeKeyAndOrderFront:self]; [NSApp presentError:error modalForWindow:window @@ -652,24 +757,26 @@ - (void)didAttemptUnmount:(NSNotification *)notif Log(LOG_DEBUG, @"%s: Unmount %@: %@", __func__, (disk.isMounted ? @"failed" : @"succeeded"), disk.BSDName); - if (disk.isMounted) { + if (disk.isMounted) + { // If the unmount failed, the notification userInfo will have keys/values that correspond to an NSError - + info = [[notif userInfo] mutableCopy]; - + Log(LOG_INFO, @"Unmount %@ failed: (%@) %@", disk.BSDName, [info objectForKey:DAStatusErrorKey], [info objectForKey:NSLocalizedFailureReasonErrorKey]); - + [info setObject:NSLocalizedString(@"Unmount failed", nil) forKey:NSLocalizedDescriptionKey]; - + NSError *error = [NSError errorWithDomain:AppErrorDomain code:[[info objectForKey:DAStatusErrorKey] intValue] userInfo:info]; - if (window.attachedSheet) { + if (window.attachedSheet) + { [displayErrorQueue addObject:error]; } - else { - + else + { [window makeKeyAndOrderFront:self]; [NSApp presentError:error modalForWindow:window @@ -684,26 +791,27 @@ - (void)didAttemptUnmount:(NSNotification *)notif - (void)didAttemptEject:(NSNotification *)notif { Disk *disk = notif.object; - - if ([notif userInfo]) { - + + if ([notif userInfo]) + { NSMutableDictionary *info = [[notif userInfo] mutableCopy]; - + // If the eject failed, the notification userInfo will have keys/values that correspond to an NSError - + Log(LOG_INFO, @"Ejecting %@ failed: (%@) %@", disk.BSDName, [info objectForKey:DAStatusErrorKey], [info objectForKey:NSLocalizedFailureReasonErrorKey]); - + [info setObject:NSLocalizedString(@"Eject failed", nil) forKey:NSLocalizedDescriptionKey]; - + NSError *error = [NSError errorWithDomain:AppErrorDomain code:[[info objectForKey:DAStatusErrorKey] intValue] userInfo:info]; - if (window.attachedSheet) { + if (window.attachedSheet) + { [displayErrorQueue addObject:error]; } - else { - + else + { [window makeKeyAndOrderFront:self]; [NSApp presentError:error modalForWindow:window @@ -712,7 +820,8 @@ - (void)didAttemptEject:(NSNotification *)notif contextInfo:NULL]; } } - else { + else + { Log(LOG_DEBUG, @"%s: Ejected: %@", __func__, disk); } [window.toolbar validateVisibleItems]; diff --git a/Source/DiskCell.m b/Source/DiskCell.m index 9ad3765..21c11d9 100644 --- a/Source/DiskCell.m +++ b/Source/DiskCell.m @@ -31,13 +31,19 @@ @implementation DiskCell - (id)copyWithZone:(NSZone *)zone { id obj = [super copyWithZone:zone]; - if (iconCell) iconCell = [iconCell copyWithZone:zone]; - if (textCell) textCell = [textCell copyWithZone:zone]; + if (iconCell) + { + iconCell = [iconCell copyWithZone:zone]; + } + if (textCell) + { + textCell = [textCell copyWithZone:zone]; + } [BSDName retain]; [mediaName retain]; [mediaSize retain]; [volumeName retain]; - + return obj; } @@ -49,7 +55,7 @@ - (void)dealloc [mediaName release]; [mediaSize release]; [volumeName release]; - + [super dealloc]; } @@ -70,36 +76,53 @@ - (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView // impression that a disk is completely protected, display a transparent, faint lock when the // disk is R/W, and display a solid black lock when the disk and the FS are both R/O. - if (mountable && mounted && !isFileSystemWritable) { - + if (mountable && mounted && !isFileSystemWritable) + { NSImage *lockImage = [NSImage imageNamed:NSImageNameLockLockedTemplate]; CGFloat scale; if (frame.size.height <= 16.0) + { scale = 0.5; + } else if (frame.size.height <= 32.0) + { scale = 0.75; + } else if (frame.size.height <= 64.0) + { scale = 2.0; + } else if (frame.size.height <= 128.0) + { scale = 4.0; + } else if (frame.size.height <= 256.0) + { scale = 8.0; + } else if (frame.size.height <= 512.0) + { scale = 16.0; + } else if (frame.size.height <= 1024.0) + { scale = 32.0; + } else + { scale = 2.0; + } CGFloat opacity = isDiskWritable ? 0.40 : 1.0; CGFloat scaledWidth = lockImage.size.width * scale; CGFloat scaledHeight = lockImage.size.height * scale; NSRect rect = NSMakeRect(NSMaxX(iconFrame) - scaledWidth, NSMaxY(iconFrame) - scaledHeight, scaledWidth, scaledHeight); - + [NSGraphicsContext saveGraphicsState]; - if ([controlView isFlipped]) { + if ([controlView isFlipped]) + { NSAffineTransform *transform = [NSAffineTransform transform]; [transform translateXBy:NSMidX(rect) yBy:NSMidY(rect)]; [transform rotateByDegrees:180]; @@ -108,7 +131,7 @@ - (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView } [lockImage drawInRect:rect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:opacity]; - + [NSGraphicsContext restoreGraphicsState]; } @@ -122,12 +145,12 @@ - (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView [textCell drawWithFrame:textFrame inView:controlView]; } - - (void)setObjectValue:(id)value { Disk *disk = (Disk *)value; - if (disk) { + if (disk) + { // Log(LOG_DEBUG, @"%s self: %p disk: %p", __func__, self, disk); self.BSDName = disk.BSDName; @@ -140,39 +163,53 @@ - (void)setObjectValue:(id)value self.mediaName = (NSString *)[disk.diskDescription objectForKey: (NSString *)kDADiskDescriptionMediaNameKey]; self.mediaSize = (NSNumber *)[disk.diskDescription objectForKey: (NSString *)kDADiskDescriptionMediaSizeKey]; self.volumeName = (NSString *)[disk.diskDescription objectForKey: (NSString *)kDADiskDescriptionVolumeNameKey]; - + // Create Text description cell - + NSString *sizeDisplayValue = nil; - if (mediaSize) { + if (mediaSize) + { double size = [mediaSize doubleValue]; if (size > 999 && size < 1000000) + { sizeDisplayValue = [NSString stringWithFormat:@"%03.02f KB ", (size / 1000.0)]; + } else if (size > 999999 && size < 1000000000) + { sizeDisplayValue = [NSString stringWithFormat:@"%03.02f MB ", (size / 1000000.0)]; + } else if (size > 999999999 && size < 1000000000000) + { sizeDisplayValue = [NSString stringWithFormat:@"%03.02f GB ", (size / 1000000000.0)]; + } else if (size > 999999999999) + { sizeDisplayValue = [NSString stringWithFormat:@"%03.02f TB ", (size / 1000000000000.0)]; + } } NSMutableString *desc = sizeDisplayValue ? [sizeDisplayValue mutableCopy] : [NSMutableString new]; if (volumeName) + { [desc appendString:volumeName]; - else if (mediaName) + } + else if (mediaName) + { [desc appendString:mediaName]; + } self.textCell = [[[NSCell alloc] initTextCell:desc] autorelease]; self.textCell.lineBreakMode = NSLineBreakByTruncatingTail; [desc release]; - + // Create Icon cell - + self.iconCell = [[[NSImageCell alloc] initImageCell:disk.icon] autorelease]; iconCell.imageScaling = NSImageScaleProportionallyDown; iconCell.alignment = NSTextAlignmentLeft; } - else { + else + { self.textCell = nil; self.iconCell = nil; } diff --git a/Source/DiskInfoController.m b/Source/DiskInfoController.m index 794f28a..22874c5 100644 --- a/Source/DiskInfoController.m +++ b/Source/DiskInfoController.m @@ -21,7 +21,9 @@ @implementation DiskInfoController + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { if ([key isEqual:@"diskDescription"]) + { return [NSSet setWithObject:@"disk.diskDescription"]; + } return [super keyPathsForValuesAffectingValueForKey:key]; } @@ -96,17 +98,27 @@ - (NSString *)formattedSizeDescriptionFromNumber:(NSNumber *)sizeValue NSString *formattedValue; double size = [sizeValue doubleValue]; - + if (size > 999.0 && size < 1000000.0) + { formattedValue = [NSString stringWithFormat:@"%03.02f KB (%@ bytes)", (size / 1000.0), sizeValue]; + } else if (size > 999999.0 && size < 1000000000.0) + { formattedValue = [NSString stringWithFormat:@"%03.02f MB (%@ bytes)", (size / 1000000.0), sizeValue]; + } else if (size > 999999999.0 && size < 1000000000000.0) + { formattedValue = [NSString stringWithFormat:@"%03.02f GB (%@ bytes)", (size / 1000000000.0), sizeValue]; + } else if (size > 999999999999.0) + { formattedValue = [NSString stringWithFormat:@"%03.02f TB (%@ bytes)", (size / 1000000000000.0), sizeValue]; + } else + { formattedValue = sizeValue.stringValue; + } return formattedValue; } @@ -116,7 +128,9 @@ - (NSString *)localizedValueStringForDADiskKey:(NSString *)key value:(id)value CFStringRef keyRef = (__bridge CFStringRef) key; if (CFEqual(keyRef, kDADiskDescriptionVolumeKindKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionVolumeMountableKey) || /* ( CFBoolean ) */ CFEqual(keyRef, kDADiskDescriptionVolumeNetworkKey) || @@ -127,15 +141,20 @@ - (NSString *)localizedValueStringForDADiskKey:(NSString *)key value:(id)value CFEqual(keyRef, kDADiskDescriptionMediaWritableKey) || CFEqual(keyRef, kDADiskDescriptionDeviceInternalKey) ) + { return [value boolValue] ? @"Yes" : @"No"; - - + } + if (CFEqual(keyRef, kDADiskDescriptionVolumeNameKey)) /* ( CFString ) */ + { return value; - + } + if (CFEqual(keyRef, kDADiskDescriptionVolumePathKey)) /* ( CFURL ) */ + { return [(NSURL *)value path]; - + } + if (CFEqual(keyRef, kDADiskDescriptionVolumeUUIDKey) || /* ( CFUUID ) */ CFEqual(keyRef, kDADiskDescriptionMediaUUIDKey)) { @@ -145,73 +164,117 @@ - (NSString *)localizedValueStringForDADiskKey:(NSString *)key value:(id)value } if (CFEqual(keyRef, kDADiskDescriptionMediaBlockSizeKey)) /* ( CFNumber ) */ + { return [value stringValue]; - + } + if (CFEqual(keyRef, kDADiskDescriptionMediaBSDMajorKey)) /* ( CFNumber ) */ + { return [value stringValue]; - + } + if (CFEqual(keyRef, kDADiskDescriptionMediaBSDMinorKey)) /* ( CFNumber ) */ + { return [value stringValue]; + } if (CFEqual(keyRef, kDADiskDescriptionMediaBSDNameKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionMediaBSDUnitKey)) /* ( CFNumber ) */ + { return [value stringValue]; + } if (CFEqual(keyRef, kDADiskDescriptionMediaContentKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionMediaIconKey)) /* ( CFDictionary ) */ + { return [value description]; + } if (CFEqual(keyRef, kDADiskDescriptionMediaKindKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionMediaNameKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionMediaPathKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionMediaSizeKey)) /* ( CFNumber ) */ + { return [self formattedSizeDescriptionFromNumber:(NSNumber *)value]; + } if (CFEqual(keyRef, kDADiskDescriptionMediaTypeKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionDeviceGUIDKey)) /* ( CFData ) */ + { return [value description]; + } if (CFEqual(keyRef, kDADiskDescriptionDeviceModelKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionDevicePathKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionDeviceProtocolKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionDeviceRevisionKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionDeviceUnitKey)) /* ( CFNumber ) */ + { return [value stringValue]; - + } + if (CFEqual(keyRef, kDADiskDescriptionDeviceVendorKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionBusNameKey)) /* ( CFString ) */ + { return value; + } if (CFEqual(keyRef, kDADiskDescriptionBusPathKey)) /* ( CFString ) */ + { return value; - + } + if (CFEqual(keyRef, CFSTR("DAAppearanceTime"))) + { return [[NSDate dateWithTimeIntervalSinceReferenceDate:[value doubleValue]] description]; + } Log(LOG_INFO, @"Unknown disk description key: %@", keyRef); - + return @"N/A"; } @@ -225,25 +288,27 @@ - (NSString *)stringForDADiskKey:(NSString *)key value:(id)value - (void)refreshDiskInfo { self.diskDescription = self.disk.diskDescription; - + NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@""]; NSFont *font = [NSFont fontWithName:@"Helvetica Bold" size:12.0]; NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]; - + NSArray *keys = [self.disk.diskDescription allKeys]; keys = [keys sortedArrayUsingSelector:@selector(compare:)]; - + for (NSString *key in keys) { // Ignore certain keys if ([key isEqual: (NSString *)kDADiskDescriptionMediaIconKey]) + { continue; - + } + id value = [self.disk.diskDescription objectForKey:key]; - - NSString *string; - NSAttributedString *attrString; + + NSString *string = nil; + NSAttributedString *attrString = nil; string = [NSString stringWithFormat:@"\t%@\t", [self localizedStringForDADiskKey:key]]; attrString = [[NSAttributedString alloc] initWithString:string attributes:attrs]; @@ -254,14 +319,13 @@ - (void)refreshDiskInfo [text appendAttributedString:attrString]; } - NSMutableParagraphStyle *style = [NSMutableParagraphStyle new]; NSMutableArray *tabStops = [NSMutableArray array]; [tabStops addObject:[[NSTextTab alloc] initWithType:NSRightTabStopType location:2.0 * 72.0]]; [tabStops addObject:[[NSTextTab alloc] initWithType:NSLeftTabStopType location:2.125 * 72.0]]; style.tabStops = tabStops; style.headIndent = (2.125 * 72.0); - + attrs = [NSDictionary dictionaryWithObjectsAndKeys:style, NSParagraphStyleAttributeName, nil]; [text addAttributes:attrs range:NSMakeRange(0, [text length])]; @@ -275,7 +339,8 @@ - (void)diskDidChange:(NSNotification *)notif - (void)setDisk:(Disk *)newDisk { - if (newDisk != _disk) { + if (newDisk != _disk) + { [[NSNotificationCenter defaultCenter] removeObserver:self name:DADiskDidChangeNotification object:_disk]; _disk = newDisk;