Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 272 additions & 0 deletions assets/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,38 @@
}
}
},
"Cancel": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Cancel"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "取消"
}
}
}
},
"Canceling…": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Canceling…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "正在取消…"
}
}
}
},
"Catalog Unavailable": {
"localizations": {
"en": {
Expand Down Expand Up @@ -577,6 +609,22 @@
}
}
},
"Downloading…": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Downloading…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "正在下载…"
}
}
}
},
"Every app in the catalog is already installed on this simulator.": {
"localizations": {
"en": {
Expand All @@ -593,6 +641,22 @@
}
}
},
"Install": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Install"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "安装"
}
}
}
},
"Install Xcode to add iPhone and iPad simulators.": {
"localizations": {
"en": {
Expand Down Expand Up @@ -625,6 +689,22 @@
}
}
},
"Installing…": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Installing…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "正在安装…"
}
}
}
},
"License": {
"localizations": {
"en": {
Expand Down Expand Up @@ -801,6 +881,22 @@
}
}
},
"Open": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Open"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "打开"
}
}
}
},
"Open %@": {
"localizations": {
"en": {
Expand Down Expand Up @@ -897,6 +993,38 @@
}
}
},
"Opening…": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Opening…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "正在打开…"
}
}
}
},
"Preparing App…": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Preparing App…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "正在准备应用…"
}
}
}
},
"Productivity": {
"localizations": {
"en": {
Expand Down Expand Up @@ -1313,6 +1441,22 @@
}
}
},
"The app must contain only arm64 iOS Simulator binaries.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The app must contain only arm64 iOS Simulator binaries."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "应用必须仅包含 arm64 iOS 模拟器二进制文件。"
}
}
}
},
"The app repository returned HTTP %lld.": {
"localizations": {
"en": {
Expand Down Expand Up @@ -1345,6 +1489,118 @@
}
}
},
"The download must contain exactly one app bundle with safe file paths.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The download must contain exactly one app bundle with safe file paths."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载包必须仅包含一个应用包,且文件路径不能越界。"
}
}
}
},
"The download server returned HTTP %lld.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The download server returned HTTP %lld."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载服务器返回了 HTTP %lld。"
}
}
}
},
"The download server returned an invalid response.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The download server returned an invalid response."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载服务器返回了无效响应。"
}
}
}
},
"The downloaded app bundle is invalid.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The downloaded app bundle is invalid."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载的应用包无效。"
}
}
}
},
"The downloaded app does not match the selected app and version.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The downloaded app does not match the selected app and version."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载的应用与所选应用或版本不符。"
}
}
}
},
"The downloaded file failed its SHA-256 check. Try downloading it again.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The downloaded file failed its SHA-256 check. Try downloading it again."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载的文件未通过 SHA-256 校验,请重新下载。"
}
}
}
},
"The downloaded file has the wrong size. Try downloading it again.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "The downloaded file has the wrong size. Try downloading it again."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "下载的文件大小不符,请重新下载。"
}
}
}
},
"The simulator returned an invalid app list.": {
"localizations": {
"en": {
Expand Down Expand Up @@ -1457,6 +1713,22 @@
}
}
},
"Verifying…": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Verifying…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "正在校验…"
}
}
}
},
"Version": {
"localizations": {
"en": {
Expand Down
6 changes: 6 additions & 0 deletions docs/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ Disk cleanup runs on the first disk access and after a successful write. Modific
Downloads use a dedicated ephemeral `URLSession` with its HTTP cache disabled and a 30-second request timeout. At most four downloads run concurrently. Requests for the same key share one task; canceling one view does not cancel that shared task. Canceled callers discard its result, while the completed download remains available to other views and future cache lookups.

Corrupt disk images are removed and fetched again. Failed requests and invalid response bodies are not persisted or retained as successful entries. There is no automatic retry loop; a later view request can retry. While no image is available, the existing placeholder remains visible. Refreshing the catalog does not clear icon caches; only a changed key, eviction, or missing/corrupt data requires another download.

## Installation downloads

App archives are streamed to a separate, randomly named directory under `URL.temporaryDirectory` for each installation. The download session has its HTTP cache disabled, a 60-second inactivity timeout, and a one-hour resource timeout. Archives are not retained or shared as a persistent download cache; retrying an installation starts a new download.

The archive's size and SHA-256 must match the selected catalog version before extraction. The temporary archive and extracted app are removed when the operation succeeds, fails, or finishes canceling. Cancellation during preparation waits for any running archive command to finish before cleanup; once `simctl install` starts, it is allowed to finish. A process exit can leave temporary files for the operating system to reclaim.
2 changes: 1 addition & 1 deletion src/AppCatalog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ extension CatalogVersion: Decodable {
}
}

private struct CatalogVersionNumber: Comparable {
struct CatalogVersionNumber: Comparable {
let components: [Int]

init?(_ text: String) {
Expand Down
Loading