def nextTask(self): LogDebug("nextTask, currentTask == %@", self.currentTask) if self.currentTask: if self.currentTask["phases"]: for phase in self.currentTask["phases"]: if not phase.get("optional", False): details = NSString.stringWithFormat_( "Phases remaining: %@", self.currentTask["phases"]) self.fail_details_("Task finished prematurely", details) return if self.tasks: self.currentTask = self.tasks.pop(0) LogNotice("Starting task %@ with %d phases", self.currentTask["title"], len(self.currentTask["phases"])) self.nextPhase() LogDebug("Calling %@()", self.currentTask["title"]) self.currentTask["method"]() LogDebug("Returned from %@()", self.currentTask["title"]) else: LogNotice("Build finished successfully, image saved to %@", self.outputPath()) self.delegate.buildSucceeded() self.stop()
def getInstalledPkgSizeFromInstaller_(cls, pkgPath): pkgFileName = os.path.os.path.basename(pkgPath) tempdir = tempfile.mkdtemp() try: symlinkPath = os.path.join(tempdir, pkgFileName) os.symlink(pkgPath, symlinkPath) p = subprocess.Popen([ "/usr/sbin/installer", "-pkginfo", "-verbose", "-plist", "-pkg", symlinkPath ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() finally: try: shutil.rmtree(tempdir) except BaseException as e: LogWarning("Unable to remove tempdir: %@", str(e)) if p.returncode != 0: LogDebug( "/usr/sbin/installer failed to determine size requirements") return None outData = NSData.dataWithBytes_length_(out, len(out)) plist, format, error = NSPropertyListSerialization.propertyListWithData_options_format_error_( outData, NSPropertyListImmutable, None, None) if not plist: LogError("Error decoding plist: %@", error) return None LogDebug("Installer says %@ requires %@", pkgPath, cls.formatByteSize_(int(plist["Size"]) * 1024)) return int(plist["Size"]) * 1024
def hdiutilDetach_(self, args): try: dmgPath, target, selector = args LogDebug(u"Detaching %@", dmgPath) try: cmd = [u"/usr/bin/hdiutil", u"detach", self.dmgs[dmgPath]] except KeyError: target.performSelectorOnMainThread_withObject_waitUntilDone_( selector, { u"success": False, u"dmg-path": dmgPath, u"error-message": u"%s not mounted" % dmgPath }, False) return del self.dmgs[dmgPath] maxtries = 5 for tries in range(maxtries): LogDebug(u"Detaching %@, attempt %d/%d", dmgPath, tries + 1, maxtries) if tries == maxtries >> 1: LogDebug(u"Adding -force to detach arguments") cmd.append(u"-force") p = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode == 0: target.performSelectorOnMainThread_withObject_waitUntilDone_( selector, { u"success": True, u"dmg-path": dmgPath }, False) return elif tries == maxtries - 1: errstr = u"hdiutil detach failed with return code %d" % p.returncode if err: errstr += u": %s" % err.decode(u"utf-8") target.performSelectorOnMainThread_withObject_waitUntilDone_( selector, { u"success": False, u"dmg-path": dmgPath, u"error-message": errstr }, False) else: time.sleep(1) except Exception as e: try: exceptionInfo = traceback.format_exc() except: exceptionInfo = u"(no traceback available)" msg = u"Detach of %s crashed with exception %s:\n%s" % ( dmgPath, e, exceptionInfo) target.performSelectorOnMainThread_withObject_waitUntilDone_( selector, { u"success": False, u"dmg-path": dmgPath, u"error-message": msg }, False)
def filesystem(self): if IEDUtil.hostMajorVersion() < 13: LogDebug("Workflow filesystem is always hfs for this OS version") return "hfs" elif self._filesystem: LogDebug("Workflow filesystem is set to '%@'", self._filesystem) return self._filesystem else: LogDebug("Workflow filesystem defaults to apfs for this OS version") return "apfs"
def connection_willSendRequest_redirectResponse_(self, connection, request, response): try: if response: url = response.URL() code = response.statusCode() LogDebug("%d redirect to %@", code, url) except BaseException as e: LogDebug("Exception: %@", repr(e)) return request
def filesystem(self): osMajor = IEDUtil.hostVersionTuple()[1] if osMajor < 13: LogDebug("Workflow filesystem is always hfs for this OS version") return "hfs" elif self._filesystem: LogDebug("Workflow filesystem is set to '%@'", self._filesystem) return self._filesystem else: LogDebug( "Workflow filesystem defaults to apfs for this OS version") return "apfs"
def attachedDMGs(self): dmgMounts = dict() LogDebug(u"Finding already attached dmgs") p = subprocess.Popen([u"/usr/bin/hdiutil", u"info", u"-plist"], bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode != 0: errstr = u"hdiutil info failed with return code %d" % p.returncode if err: errstr += u": %s" % err.decode(u"utf-8") LogWarning("%@", errstr) return dmgMounts # Strip EULA text. outXML = out[out.find("<?xml"):] outData = NSData.dataWithBytes_length_(outXML, len(outXML)) plist, format, error = \ NSPropertyListSerialization.propertyListWithData_options_format_error_(outData, NSPropertyListImmutable, None, None) for dmgInfo in plist[u"images"]: for entity in dmgInfo.get(u"system-entities", []): try: image_path = dmgInfo[u"image-path"] alias_path = u"" bookmark = CFURLCreateBookmarkDataFromAliasRecord( kCFAllocatorDefault, dmgInfo[u"image-alias"]) if bookmark: url, stale, error = CFURLCreateByResolvingBookmarkData( None, bookmark, kCFBookmarkResolutionWithoutUIMask, None, None, None, None) if url: alias_path = url.path() else: LogDebug(u"Couldn't resolve bookmark: %@", error.localizedDescription()) for path in set(x for x in (image_path, alias_path) if x): dmgMounts[path] = entity[u"mount-point"] LogDebug(u"'%@' is already mounted at '%@'", path, entity[u"mount-point"]) break except IndexError: pass except KeyError: pass return dmgMounts
def listenOnSocket_withDelegate_(self, path, delegate): for oldsocket in glob.glob("%s.*" % path): LogDebug("Removing old socket %@", oldsocket) try: os.unlink(oldsocket) except: pass self.socketPath = NSString.stringWithFormat_("%@.%@", path, os.urandom(8).encode("hex")) LogDebug("Creating socket at %@", self.socketPath) self.delegate = delegate self.watchThread = NSThread.alloc().initWithTarget_selector_object_(self, "listenInBackground:", None) self.watchThread.start() return self.socketPath
def connection_didReceiveResponse_(self, connection, response): LogDebug("%@ status code %d", connection, response.statusCode()) if response.expectedContentLength() == NSURLResponseUnknownLength: LogDebug("unknown response length") else: LogDebug("Downloading profile with %d bytes", response.expectedContentLength()) if self.profileUpdateWindow: self.progressBar.setMaxValue_( float(response.expectedContentLength())) self.progressBar.setDoubleValue_( float(response.expectedContentLength())) self.progressBar.setIndeterminate_(False)
def handleSourceMountResult_(self, result): LogDebug("handleSourceMountResult:%@", result) if not result["success"]: self.delegate.sourceFailed_text_( "Failed to mount %s" % result["dmg-path"], result["error-message"]) return mountPoint = result["mount-point"] # Update the icon if we find an installer app. for path in glob.glob(os.path.join(mountPoint, "Install*.app")): self.delegate.foundSourceForIcon_(path) # Don't set this again since 10.9 mounts BaseSystem.dmg after InstallESD.dmg. if self.installerMountPoint is None: # Check if the source is an InstallESD, BaseSystem or system image. if os.path.exists( os.path.join(mountPoint, "Packages", "OSInstall.mpkg")): self.installerMountPoint = mountPoint self.sourceType = IEDWorkflow.INSTALL_ESD LogDebug("sourceType = INSTALL_ESD") elif glob.glob(os.path.join(mountPoint, "Install*.app")): self.sourceType = IEDWorkflow.INSTALL_INFO LogDebug("sourceType = INSTALL_INFO") else: self.installerMountPoint = mountPoint self.sourceType = IEDWorkflow.SYSTEM_IMAGE LogDebug("sourceType = SYSTEM_IMAGE") baseSystemPath = os.path.join(mountPoint, "BaseSystem.dmg") # If we find a SystemVersion.plist we proceed to the next step. if os.path.exists(os.path.join(mountPoint, IEDUtil.VERSIONPLIST_PATH)): self.checkVersion_(mountPoint) # Otherwise check if there's a BaseSystem.dmg that we need to examine. elif os.path.exists(baseSystemPath): self.baseSystemMountedFromPath = baseSystemPath self.dmgHelper.attach_selector_(baseSystemPath, self.handleSourceMountResult_) else: if (self.sourceType == IEDWorkflow.INSTALL_ESD) and \ (IEDUtil.hostMajorVersion() >= 13): self.delegate.sourceFailed_text_( "Invalid source", "InstallESD images aren't valid installers on 10.13+.") return self.delegate.sourceFailed_text_("Invalid source", "Couldn't find system version.")
def listenInBackground_(self, ignored): try: sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, (IEDSL_MAX_MSG_SIZE + 16) * IEDSL_MAX_MSG_COUNT) sock.bind(self.socketPath) except socket.error as e: LogError("Error creating datagram socket at %@: %@", self.socketPath, str(e)) return LogDebug("Listening to socket in background thread") while True: msg = sock.recv(IEDSL_MAX_MSG_SIZE, socket.MSG_WAITALL) if not msg: continue msgData = NSData.dataWithBytes_length_(msg, len(msg)) plist, format, error = NSPropertyListSerialization.propertyListWithData_options_format_error_( msgData, NSPropertyListImmutable, None, None) if not plist: LogError("Error decoding plist: %@", error) continue if self.delegate.respondsToSelector_("socketReceivedMessage:"): self.delegate.performSelectorOnMainThread_withObject_waitUntilDone_( "socketReceivedMessage:", plist, False)
def taskFinalize(self): LogNotice(u"Finalize task running") self.delegate.buildSetProgressMessage_( u"Scanning disk image for restore") # The script is wrapped with progresswatcher.py which parses script # output and sends it back as notifications to IEDSocketListener. args = [ NSBundle.mainBundle().pathForResource_ofType_( u"progresswatcher", u"py"), u"--socket", self.listenerPath, u"imagescan", self.outputPath(), ] LogInfo(u"Launching finalize with arguments:") for arg in args: LogInfo(u" '%@'", arg) try: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = p.communicate()[0].decode(u"utf-8") LogDebug(u"Finalize exited with status %d and output '%@'", p.returncode, out) if p.returncode != 0: errMsg = u"Finalize task failed with status %d" % p.returncode LogError(u"%@: %@", errMsg, out) self.fail_details_(errMsg, out) except BaseException as e: LogError(u"Failed to launch finalize task: %@", unicode(e)) self.fail_details_(u"Failed to launch finalize task", unicode(e))
def checkVersion_(self, mountPoint): LogDebug(u"checkVersion:%@", mountPoint) # We're now examining InstallESD.dmg for 10.7/10.8, BaseSystem.dmg for # 10.9, or a system image. name, version, build = IEDUtil.readSystemVersion_(mountPoint) if self.baseSystemMountedFromPath: self.dmgHelper.detach_selector_(self.baseSystemMountedFromPath, self.handleDetachResult_) installerVersion = tuple(int(x) for x in version.split(u".")) runningVersion = tuple( int(x) for x in platform.mac_ver()[0].split(u".")) LogNotice(u"Found source: %@ %@ %@", name, version, build) if installerVersion[:2] != runningVersion[:2]: self.delegate.ejectingSource() self.dmgHelper.detachAll_(self.rejectSource_) return LogNotice(u"Accepted source %@: %@ %@ %@", self.newSourcePath, name, version, build) self._source = self.newSourcePath self.installerName = name self.installerVersion = version self.installerBuild = build info = { u"name": name, u"version": version, u"build": build, u"template": self.loadImageTemplate_(mountPoint), u"sourceType": self.sourceType, } self.delegate.sourceSucceeded_(info) # There's no reason to keep the dmg mounted if it's not an installer. if self.sourceType == IEDWorkflow.SYSTEM_IMAGE: self.dmgHelper.detachAll_(self.ejectSystemImage_)
def stopListening(self): LogDebug("stopListening") self.watchThread.cancel() try: os.unlink(self.socketPath) except BaseException as e: LogWarning("Couldn't remove listener socket %@: %@", self.socketPath, str(e))
def continueSetSource_(self, failedUnmounts): LogDebug("continueSetSource:%@", failedUnmounts) self.alertFailedUnmounts_(failedUnmounts) pathCandidates = [ os.path.join(self.newSourcePath, "Contents/SharedSupport/BaseSystem.dmg"), os.path.join(self.newSourcePath, "Contents/SharedSupport/InstallESD.dmg"), self.newSourcePath, ] for path in pathCandidates: if os.path.exists(path): dmgPath = path break else: self.delegate.sourceFailed_text_( "Failed to mount %s" % self.newSourcePath, "No system dmg found") return self.delegate.examiningSource_(self.newSourcePath) self.installerMountPoint = None self.baseSystemMountedFromPath = None self.dmgHelper.attach_selector_(dmgPath, self.handleSourceMountResult_)
def awakeFromNib(self): LogDebug(u"awakeFromNib") # Initialize UI. self.buildProgressBar.setMaxValue_(100.0) self.buildProgressMessage.setStringValue_(u"") # We're a delegate for the drag and drop target, protocol: # (void)acceptInstaller:(NSString *)path self.sourceBox.setDelegate_(self) self.sourceImage.setDelegate_(self) self.sourceLabel.setDelegate_(self) # We're a delegate for the update controller, protocol: # (void)updateControllerChanged self.updateController.setDelegate_(self) # Main workflow logic. self.workflow = IEDWorkflow.alloc().initWithDelegate_(self) # Enabled state for main window. self.enabled = True # When busy is true quitting gets a confirmation prompt. self._busy = False # Currently loaded template. self.templateURL = None
def handleLaunchScriptError_(self, error): if isinstance(error, int): try: msg = { -60001: "The authorization rights are invalid.", -60002: "The authorization reference is invalid.", -60003: "The authorization tag is invalid.", -60004: "The returned authorization is invalid.", -60005: "The authorization was denied.", -60006: "The authorization was cancelled by the user.", -60007: "The authorization was denied since no user interaction was possible.", -60008: "Unable to obtain authorization for this operation.", -60009: "The authorization is not allowed to be converted to an external format.", -60010: "The authorization is not allowed to be created from an external format.", -60011: "The provided option flag(s) are invalid for this authorization operation.", -60031: "The specified program could not be executed.", -60032: "An invalid status was returned during execution of a privileged tool.", -60033: "The requested socket address is invalid (must be 0-1023 inclusive).", }[error] except KeyError: msg = "Unknown error (%d)." % error else: msg = error if error == -60006: LogDebug("User cancelled auth.") # User cancelled. self.stop() else: self.fail_details_("Build failed", msg)
def continueSetSource_(self, failedUnmounts): LogDebug("continueSetSource:%@", failedUnmounts) self.alertFailedUnmounts_(failedUnmounts) pathCandidates = [ os.path.join(self.newSourcePath, "Contents/SharedSupport/BaseSystem.dmg"), os.path.join(self.newSourcePath, "Contents/SharedSupport/InstallESD.dmg"), self.newSourcePath, ] for path in pathCandidates: if os.path.isfile(path): dmgPath = path break else: iaToolPath = os.path.join(self.newSourcePath, "Contents/Resources/InstallAssistantTool") baseName = os.path.basename(self.newSourcePath) if os.path.exists(iaToolPath): self.delegate.sourceFailed_text_("Incomplete installer", "Installation resources are missing from '%s'. " % baseName + "Try downloading a new installer from the App Store straight from " + "Apple without using an internal Software Update or caching server.") else: self.delegate.sourceFailed_text_("Failed to mount %s" % self.newSourcePath, "No system dmg found") return self.delegate.examiningSource_(self.newSourcePath) self.installerMountPoint = None self.baseSystemMountedFromPath = None self.dmgHelper.attach_selector_(dmgPath, self.handleSourceMountResult_)
def handleDetachAllResult_(self, result): LogDebug("handleDetachAllResult:%@", result) if not result["success"]: self.detachAllFailed[result["dmg-path"]] = result["error-message"] self.detachAllRemaining -= 1 if self.detachAllRemaining == 0: self.tellDelegate_message_(self.detachAllSelector, self.detachAllFailed)
def listApfsVolumes(cls): LogDebug("Listing APFS volumes") if cls.hostVersionTuple()[1] < 13: return {} p = subprocess.Popen(["/usr/sbin/diskutil", "apfs", "list", "-plist"], bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode != 0: LogError("diskutil apfs list failed with return code %d" % p.returncode) return None outData = NSData.dataWithBytes_length_(out, len(out)) plist, format, error = \ NSPropertyListSerialization.propertyListWithData_options_format_error_(outData, NSPropertyListImmutable, None, None) if "Containers" not in plist: LogError("diskutil apfs list does not have any Containers") return None volumes = {} for container in plist["Containers"]: for volume in container.get("Volumes", []): volumes[volume["DeviceIdentifier"]] = volume return volumes
def downloadNextUpdate(self): if self.updates: self.package = self.updates.pop(0) self.bytesReceived = 0 self.checksum = hashlib.sha1() self.delegate.downloadStarting_(self.package) path = self.cacheTmpPath_(self.package.sha1()) if not NSFileManager.defaultManager( ).createFileAtPath_contents_attributes_(path, None, None): error = "Couldn't create temporary file at %s" % path self.delegate.downloadFailed_withError_(self.package, error) return self.fileHandle = NSFileHandle.fileHandleForWritingAtPath_(path) if not self.fileHandle: error = "Couldn't open %s for writing" % path self.delegate.downloadFailed_withError_(self.package, error) return LogDebug("Downloading %@ from %@", self.package.name(), self.package.url()) url = NSURL.URLWithString_(self.package.url()) request = NSURLRequest.requestWithURL_(url) self.connection = NSURLConnection.connectionWithRequest_delegate_( request, self) if self.connection: self.delegate.downloadStarted_(self.package) else: self.delegate.downloadAllDone()
def saveTemplateToURL_(self, url): LogDebug("saveTemplateToURL:%@", url) self.templateURL = url NSDocumentController.sharedDocumentController( ).noteNewRecentDocumentURL_(url) # Create a template from the current state. template = IEDTemplate.alloc().init() if self.workflow.source(): template.setSourcePath_(self.workflow.source()) if self.updateController.packagesToInstall(): template.setApplyUpdates_(True) else: template.setApplyUpdates_(False) if self.volumeName.stringValue(): template.setVolumeName_(self.volumeName.stringValue()) if self.volumeSize.intValue(): template.setVolumeSize_(self.volumeSize.intValue()) if self.finalizeAsrImagescan.state() == NSOffState: template.setFinalizeAsrImagescan_(False) if IEDUtil.hostMajorVersion() >= 13: template.setFilesystem_( self.filesystem.selectedItem().representedObject()) template.setAdditionalPackages_( [x.path() for x in self.addPkgController.packagesToInstall()]) error = template.saveTemplateAndReturnError_(url.path()) if error: self.displayAlert_text_("Couldn't save template", error)
def getInstalledPkgSize_(cls, pkgPath): # For apps just return the size on disk. ext = os.path.splitext(pkgPath)[1].lower() if ext == ".app": return cls.getPackageSize_(pkgPath) elif ext in (".pkg", ".mpkg"): # For packages first try to get the size requirements with # installer. size = cls.getInstalledPkgSizeFromInstaller_(pkgPath) if size is None: # If this fails, manually extract the size requirements from # the package. return cls.calculateInstalledPkgSize_(pkgPath) else: return size elif os.path.basename(pkgPath) == "InstallInfo.plist": iedPath = os.path.join(os.path.dirname(pkgPath), "InstallESD.dmg") try: iedSize = os.stat(iedPath).st_size estimatedSize = int(iedSize * 2.72127696157) LogDebug( "Estimating size requirements of InstallInfo.plist to %@", cls.formatByteSize_(estimatedSize)) return estimatedSize except OSError: pass LogError("Don't know how to calculate installed size for '%@'", pkgPath) return None
def downloadAllDone(self): LogDebug("downloadAllDone") self.downloadWindow.orderOut_(self) self.countDownloads() self.enableControls() if self.delegate: self.delegate.updateControllerChanged()
def loadProfileForVersion_build_(self, version, build): LogDebug("loadProfileForVersion:%@ build:%@", version, build) self.version = version self.build = build self.updates = list() profile = self.profileController.profileForVersion_Build_( version, build) if profile is None: # No update profile for this build, try to figure out why. self.profileWarning = self.profileController.whyNoProfileForVersion_build_( version, build) else: if self.profileController.deprecatedOS: self.profileWarning = "No longer updated by Apple" else: self.profileWarning = None for update in profile: package = IEDPackage.alloc().init() package.setName_(update["name"]) package.setPath_(self.cache.updatePath_(update["sha1"])) package.setSize_(update["size"]) package.setUrl_(update["url"]) package.setSha1_(update["sha1"]) # Image is set by countDownloads(). self.updates.append(package) self.countDownloads()
def downloadStarting_(self, package): LogDebug("downloadStarting:") self.downloadProgressBar.setIndeterminate_(False) self.downloadProgressBar.setDoubleValue_(0.0) self.downloadProgressBar.setMaxValue_(package.size()) self.downloadCounter += 1 self.downloadLabel.setStringValue_("%s (%s)" % (package.name(), IEDUtil.formatByteSize_(package.size())))
def hdiutilAttach_(self, args): try: dmgPath, selector = args LogDebug(u"Attaching %@", dmgPath) p = subprocess.Popen([u"/usr/bin/hdiutil", u"attach", dmgPath, u"-mountRandom", u"/tmp", u"-nobrowse", u"-noverify", u"-plist"], bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate(u"Y\n") LogDebug(u"Checking result of attaching %@", dmgPath) if p.returncode != 0: errstr = u"hdiutil attach failed with return code %d" % p.returncode if err: errstr += u": %s" % err.decode(u"utf-8") self.tellDelegate_message_(selector, {u"success": False, u"dmg-path": dmgPath, u"error-message": errstr}) return # Strip EULA text. xmlStartIndex = out.find("<?xml") plist = plistlib.readPlistFromString(out[xmlStartIndex:]) for partition in plist[u"system-entities"]: if partition.get(u"potentially-mountable") == 1: if u"mount-point" in partition: self.dmgs[dmgPath] = partition[u"mount-point"] break else: self.tellDelegate_message_(selector, {u"success": False, u"dmg-path": dmgPath, u"error-message": u"No mounted filesystem in %s" % dmgPath}) return self.tellDelegate_message_(selector, {u"success": True, u"dmg-path": dmgPath, u"mount-point": self.dmgs[dmgPath]}) except Exception: exceptionInfo = traceback.format_exc() msg = u"Attach of %s crashed with exception:\n%s" % (dmgPath, exceptionInfo) self.tellDelegate_message_(selector, {u"success": False, u"dmg-path": dmgPath, u"error-message": msg})
def checkAppleBugWarning_(self, path): LogDebug("checkAppleBugWarning:%@", path) if (IEDUtil.hostMajorVersion() != 14): return True LogDebug("We're running on Mojave") if not path.endswith(".app"): return True LogDebug("The source is a .app") if IEDUtil.volumePathForPath_(path) != "/": return True LogError("The source is an installer app on a Mojave system volume") text = "A bug in the OS installer (radar 43296160) causes installs to fail " \ "if the Mojave installer is on the system volume. Copy it to an external " \ "volume or wrap it in a dmg before dropping it on AutoDMG." self.delegate.sourceFailed_text_("Install will fail due to Apple bug", text) return False
def connection_didReceiveResponse_(self, connection, response): LogDebug(u"%@ status code %d", self.package.name(), response.statusCode()) if response.statusCode() >= 400: connection.cancel() self.fileHandle.closeFile() error = u"%s failed with HTTP %d" % (self.package.name(), response.statusCode()) self.delegate.downloadFailed_withError_(self.package, error) self.delegate.downloadAllDone()
def setSource_(self, path): LogDebug(u"setSource:%@", path) self._source = None self.newSourcePath = path if self.installerMountPoint: self.delegate.ejectingSource() self.dmgHelper.detachAll_(self.continueSetSource_) else: self.continueSetSource_({})