Skip to content

Commit

Permalink
Add ApplicationCache.
Browse files Browse the repository at this point in the history
  • Loading branch information
wigl committed Dec 1, 2018
1 parent 57f6bf9 commit e515991
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 99 deletions.
4 changes: 4 additions & 0 deletions iSimulator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
3198339721A7F5FF004ADEE3 /* Crashlytics.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3198338921A7F5FE004ADEE3 /* Crashlytics.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
319833E021A94EDF004ADEE3 /* CrashlyticsReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319833DF21A94EDF004ADEE3 /* CrashlyticsReport.swift */; };
31CD6D0221B2817900E49DC0 /* RootLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CD6D0121B2817900E49DC0 /* RootLink.swift */; };
31CD6D0421B2942300E49DC0 /* ApplicationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CD6D0321B2942300E49DC0 /* ApplicationCache.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -99,6 +100,7 @@
3198338921A7F5FE004ADEE3 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = Carthage/Build/Mac/Crashlytics.framework; sourceTree = "<group>"; };
319833DF21A94EDF004ADEE3 /* CrashlyticsReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsReport.swift; sourceTree = "<group>"; };
31CD6D0121B2817900E49DC0 /* RootLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootLink.swift; sourceTree = "<group>"; };
31CD6D0321B2942300E49DC0 /* ApplicationCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationCache.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -148,6 +150,7 @@
29157C5D1F4D66220069BDB6 /* Device.swift */,
29157C611F4D7EB90069BDB6 /* Application.swift */,
29AFDFAF1FBBDA6E00544CE5 /* Pair.swift */,
31CD6D0321B2942300E49DC0 /* ApplicationCache.swift */,
31CD6D0121B2817900E49DC0 /* RootLink.swift */,
);
path = Models;
Expand Down Expand Up @@ -330,6 +333,7 @@
319833E021A94EDF004ADEE3 /* CrashlyticsReport.swift in Sources */,
29157C5E1F4D66220069BDB6 /* Device.swift in Sources */,
291157AE1FB5501C000EE94F /* DevcieMenuItem.swift in Sources */,
31CD6D0421B2942300E49DC0 /* ApplicationCache.swift in Sources */,
29B591981F581513006CDEC0 /* FileWatch.swift in Sources */,
292EADB61F458F5C009FC1ED /* BarManager.swift in Sources */,
);
Expand Down
23 changes: 23 additions & 0 deletions iSimulator/Models/ApplicationCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// ApplicationCache.swift
// iSimulator
//
// Created by Peng Jin 靳朋 on 2018/12/1.
// Copyright © 2018 niels.jin. All rights reserved.
//

import Foundation

class ApplicationCache {

var urlAndAppDic: [URL: Application] = [:]

var sandboxURLs: Set<URL> = []

/// 需要忽略的 boundleURL/sandboxURL:如无效的文件夹
var ignoreURLs: Set<URL> = []

init() {

}
}
137 changes: 68 additions & 69 deletions iSimulator/Models/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ import Foundation
import ObjectMapper

class Device: Mappable {

static let url: URL = {
let userLibraryURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
return userLibraryURL.appendingPathComponent("Developer/CoreSimulator/Devices")
}()

static let setURL: URL = {
return url.appendingPathComponent("device_set.plist")
}()

enum State: String {
case booted = "Booted"
case shutdown = "Shutdown"
Expand Down Expand Up @@ -38,14 +48,14 @@ class Device: Mappable {
var infoURL: URL {
return Device.url.appendingPathComponent("\(self.udid)/device.plist")
}
/// 暂未使用
fileprivate var iconStateURL: URL {
return Device.url.appendingPathComponent("\(self.udid)/data/Library/SpringBoard/IconState.plist")
}

init() { }
init() {

}

required init?(map: Map) { }
required init?(map: Map) {

}

func mapping(map: Map) {
state <- (map["state"], EnumTransform())
Expand All @@ -59,22 +69,16 @@ class Device: Mappable {
}
}

// MARK: - device 操作
// MARK: - device Action
extension Device {
func boot() throws {
// shell("/usr/bin/xcrun", arguments: "simctl", "boot", self.udid)
// var xcode = TotalModel.default.lastXcodePath
// if xcode.hasSuffix("\n"){
// xcode = String(xcode.dropLast())
// }
// let simPath = xcode.appending("/Applications/Simulator.app")
// shell("/usr/bin/xcrun", arguments: "open", simPath)
// shell("/usr/bin/xcrun", arguments: "simctl", "boot", self.udid)
try? FBSimTool.default.boot(self.udid)
}

func shutdown() throws {
// shell("/usr/bin/xcrun", arguments: "simctl", "shutdown", self.udid)
try? FBSimTool.default.shutdown(self.udid)
// shell("/usr/bin/xcrun", arguments: "simctl", "shutdown", self.udid)
}

func erase() throws {
Expand All @@ -88,7 +92,6 @@ extension Device {
DispatchQueue.main.asyncAfter(deadline: .now() + afterTime) {
shell("/usr/bin/xcrun", arguments: "simctl", "erase", self.udid)
}

}

func delete() throws {
Expand All @@ -113,89 +116,84 @@ extension Device {
func unpair() {
if let udid = self.pairUDID{
shell("/usr/bin/xcrun", arguments: "simctl", "unpair", udid)
BarManager.default.refresh()
}
}
}


// MARK: - 获取APP:方式1
extension Device {
/// 以app的boundleURL为key,缓存
static var bundleURLAppsCache: [URL: Application] = [:]
/// 缓存的sandboxURL
static var sandboxURLs: Set<URL> = []
/// 需要忽略的 boundleURL/sandboxURL:如无效的文件夹
private static var ignorePaths: Set<URL> = []

func updateApps() {
self.applications = []
let idAndBundleUrlDic = identifierAndUrl(with: bundleURL)
var idAndSandboxUrlDic = identifierAndUrl(with: sandboxURL)

func updateApps(with cache: ApplicationCache) {
let bundleContents = (try? FileManager.default.contentsOfDirectory(at: bundleURL, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants, .skipsSubdirectoryDescendants])) ?? []

let sandboxContents = (try? FileManager.default.contentsOfDirectory(at: sandboxURL, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants, .skipsSubdirectoryDescendants])) ?? []

var apps: [Application] = []

// 优先从缓存中取
var newBundles = [URL]()
bundleContents.enumerated().forEach { (offset, url) in
if let app = cache.urlAndAppDic[url] {
app.device = self
apps.append(app)
} else if cache.ignoreURLs.contains(url) {
//
} else {
newBundles.append(url)
}
}

var newSandboxs = [URL]()
sandboxContents.enumerated().forEach { (offset, url) in
if cache.sandboxURLs.contains(url) || cache.ignoreURLs.contains(url) {
//
} else {
newSandboxs.append(url)
}
}

let idAndBundleUrlDic = identifierAndUrl(with: newBundles)
var idAndSandboxUrlDic = identifierAndUrl(with: newSandboxs)

idAndBundleUrlDic.forEach { (bundleID, bundleDirUrl) in
guard let sandboxDirUrl = idAndSandboxUrlDic[bundleID] else {
guard let sandboxDirUrl = idAndSandboxUrlDic.removeValue(forKey: bundleID) else {
return
}
if let app = Application(bundleID: bundleID, bundleDirUrl: bundleDirUrl, sandboxDirUrl: sandboxDirUrl){
app.device = self
self.applications.append(app)
idAndSandboxUrlDic.removeValue(forKey: bundleID)
}else{
Device.ignorePaths.insert(bundleDirUrl)
apps.append(app)
} else {
cache.ignoreURLs.insert(bundleDirUrl)
}
}
idAndSandboxUrlDic.forEach({ (_, url) in
Device.ignorePaths.insert(url)
cache.ignoreURLs.insert(url)
})
// ⚠️⚠️所有app赋值成功后,再创建linkDir,否则无法判断app.bundleDisplayName是否重复⚠️⚠️
self.applications.forEach{$0.createLinkDir()}
self.applications = apps
self.applications.forEach{ $0.createLinkDir() }
}

private func identifierAndUrl(with url: URL) -> [String: URL] {
let contents = try? FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants, .skipsSubdirectoryDescendants])
guard let contentArr = contents, !contentArr.isEmpty else {
return [:]
}
private func identifierAndUrl(with urls: [URL]) -> [String: URL] {
var dic: [String: URL] = [:]
contentArr.forEach { (url) in
if Device.ignorePaths.contains(url) {
return
}
if let app = Device.bundleURLAppsCache[url] {
app.device = self
self.applications.append(app)
return
}
if Device.sandboxURLs.contains(url) {
return
}
urls.forEach { (url) in
if let contents = NSDictionary(contentsOf: url.appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist")),
let identifier = contents["MCMMetadataIdentifier"] as? String {
dic[identifier] = url
}else{
Device.ignorePaths.insert(url)
}
}
return dic
}
}

// MARK: - Device目录
extension Device {
static let url: URL = {
let userLibraryURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
return userLibraryURL.appendingPathComponent("Developer/CoreSimulator/Devices")
}()
/// 暂未使用
private static let setURL: URL = {
return url.appendingPathComponent("device_set.plist")
}()

}

// MARK: - 获取安装的APP:方式2,暂未使用
extension Device {
//⚠️⚠️还有问题没解决:simctl appinfo返回的数据格式不是jison⚠️⚠️

private var iconStateURL: URL {
return Device.url.appendingPathComponent("\(self.udid)/data/Library/SpringBoard/IconState.plist")
}

private func allApps2() -> [Application] {
var apps: [Application] = []
guard let dic = NSDictionary(contentsOf: iconStateURL) as? [String: Any] else {
Expand All @@ -217,8 +215,9 @@ extension Device {
return apps
}

//⚠️⚠️simctl appinfo返回的数据格式不是json, 且device必须是booted的,否则获取不到信息⚠️⚠️
private func fetchAppInfo(deviceId: String, appBunldId: String) -> [String: Any] {
let jsonStr = shell("/usr/bin/xcrun", arguments: "simctl", "appinfo", deviceId, appBunldId).0
let jsonStr = shell("/usr/bin/xcrun", arguments: "simctl", "appinfo", deviceId, appBunldId).outStr
let data = jsonStr.data(using: .utf8, allowLossyConversion: true)
if let data = data {
let parsedJSON = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
Expand Down
51 changes: 27 additions & 24 deletions iSimulator/Models/TotalModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class TotalModel: Mappable {
var isForceUpdate = true
var lastXcodePath = ""
var isXcode9OrGreater = false
var appCache = ApplicationCache()

func updateXcodeVersion() {
var url = URL.init(fileURLWithPath: lastXcodePath)
Expand All @@ -33,16 +34,15 @@ class TotalModel: Mappable {

///该方法: 耗时 && 阻塞
func update() {
let xcodePath = shell("/usr/bin/xcrun", arguments: "xcode-select", "-p").0
let xcodePath = shell("/usr/bin/xcrun", arguments: "xcode-select", "-p").outStr
if lastXcodePath != xcodePath{
isForceUpdate = true
lastXcodePath = xcodePath
updateXcodeVersion()
}
if isForceUpdate {
isForceUpdate = false
Device.bundleURLAppsCache = [:]
Device.sandboxURLs = []
appCache = ApplicationCache()
let contents = try? FileManager.default.contentsOfDirectory(at: RootLink.url, includingPropertiesForKeys: [.isHiddenKey], options: [.skipsPackageDescendants, .skipsSubdirectoryDescendants])
if let contents = contents{
for url in contents {
Expand All @@ -56,7 +56,7 @@ class TotalModel: Mappable {
try? FileManager.default.createDirectory(at: RootLink.url, withIntermediateDirectories: true)
NSWorkspace.shared.setIcon(#imageLiteral(resourceName: "linkDirectory"), forFile: RootLink.url.path, options:[])
}
let jsonStr = shell("/usr/bin/xcrun", arguments: "simctl", "list", "-j").0
let jsonStr = shell("/usr/bin/xcrun", arguments: "simctl", "list", "-j").outStr
_ = Mapper().map(JSONString: jsonStr, toObject: TotalModel.default)
}

Expand Down Expand Up @@ -109,7 +109,7 @@ class TotalModel: Mappable {
r.devices.forEach{
$0.runtime = r
//⚠️⚠️关联之后,再更新device的APP,否则取不到device的runtime⚠️⚠️
$0.updateApps()
$0.updateApps(with: appCache)
}
}
// 关联pair
Expand Down Expand Up @@ -141,28 +141,31 @@ class TotalModel: Mappable {
}
}
// 更新缓存
Device.sandboxURLs = []
var bundleURLAppsCacheTemp: [URL: Application] = [:]
var sandboxURLsTemp: Set<URL> = []
devices.forEach { (_, arr) in
arr.forEach({ (d) in
d.applications.forEach({ (app) in
//添加至临时缓存
bundleURLAppsCacheTemp[app.bundleDirUrl] = app
sandboxURLsTemp.insert(app.sandboxDirUrl)
//从旧的缓存中移除
Device.bundleURLAppsCache.removeValue(forKey: app.bundleDirUrl)
Device.sandboxURLs.remove(app.sandboxDirUrl)
})
})
self.updateCache()
// 更新log状态
LogReport.default.logSimctlList()
}

func updateCache() {
let applications = runtimes.flatMap { $0.devices }.flatMap { $0.applications }

var urlAndAppDicCache: [URL: Application] = [:]
var sandboxURLsCache: Set<URL> = []

applications.forEach { (app) in
//添加至临时缓存
urlAndAppDicCache[app.bundleDirUrl] = app
sandboxURLsCache.insert(app.sandboxDirUrl)
//从旧的缓存中移除
self.appCache.urlAndAppDic.removeValue(forKey: app.bundleDirUrl)
self.appCache.sandboxURLs.remove(app.sandboxDirUrl)
}
// 缓存中剩余的App删除linkDir
// 删除不存在的app虚拟文件夹
// 不在app deinit 方法里面 removeLinkDir, 因为deinit方法调用有延迟
Device.bundleURLAppsCache.forEach{$0.value.removeLinkDir()}
Device.bundleURLAppsCache = bundleURLAppsCacheTemp
Device.sandboxURLs = sandboxURLsTemp
self.appCache.urlAndAppDic.forEach { $0.value.removeLinkDir() }

LogReport.default.logSimctlList()
self.appCache.urlAndAppDic = urlAndAppDicCache
self.appCache.sandboxURLs = sandboxURLsCache
}

var dataReportDic: [String: Any] {
Expand Down
2 changes: 1 addition & 1 deletion iSimulator/Tools/Shell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

@discardableResult
func shell(_ launchPath: String, arguments: String...) -> (String, String) {
func shell(_ launchPath: String, arguments: String...) -> (outStr: String, err: String) {
let process = Process()
process.launchPath = launchPath
process.arguments = arguments
Expand Down
1 change: 1 addition & 0 deletions iSimulator/UI/DevcieMenuItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class DeviceUnpairAction: DeviceActionable {

@objc func perform() {
device.unpair()
BarManager.default.refresh()
}

}
Expand Down
Loading

0 comments on commit e515991

Please sign in to comment.