mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 04:20:30 +00:00
144 lines
5 KiB
Swift
144 lines
5 KiB
Swift
import Foundation
|
|
|
|
struct ToolInfo: Decodable {
|
|
struct Capabilities: Decodable {
|
|
let internetAccess: Bool?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case internetAccess = "internet_access"
|
|
}
|
|
}
|
|
|
|
let author: String
|
|
let desc: String
|
|
let capabilities: Capabilities?
|
|
let requiredIDeviceFunctions: [String]?
|
|
|
|
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case author
|
|
case desc
|
|
case capabilities
|
|
case requiredIDeviceFunctions = "required_idevice_functions"
|
|
}
|
|
}
|
|
|
|
|
|
class MiniToolBundle: ObservableObject, Identifiable {
|
|
static func == (lhs: MiniToolBundle, rhs: MiniToolBundle) -> Bool {
|
|
return lhs.name == rhs.name
|
|
}
|
|
|
|
@Published var url: URL
|
|
|
|
init(url: URL) {
|
|
self.url = url
|
|
}
|
|
|
|
var id: String { url.lastPathComponent }
|
|
var name: String { url.deletingPathExtension().lastPathComponent }
|
|
var indexURL: URL { url.appendingPathComponent("index.html") }
|
|
var backgroundURL: URL { url.appendingPathComponent("background.js") }
|
|
|
|
var isValid: Bool {
|
|
let fm = FileManager.default
|
|
return fm.fileExists(atPath: indexURL.path) && fm.fileExists(atPath: backgroundURL.path)
|
|
}
|
|
|
|
func getHostName() -> String {
|
|
let d = name.data(using: .utf8)
|
|
let b = d!.base64EncodedString().replacingOccurrences(of:"+", with:"").replacingOccurrences(of:"/", with:"").replacingOccurrences(of:"=", with:"")
|
|
|
|
return "\(b).stiktool"
|
|
}
|
|
}
|
|
|
|
final class MiniToolStore: ObservableObject {
|
|
@Published private(set) var tools: [MiniToolBundle] = []
|
|
@Published var isBusy: Bool = false
|
|
@Published var lastError: String?
|
|
|
|
func refresh() {
|
|
let directory = toolsDirectory()
|
|
let contents = (try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)) ?? []
|
|
let bundles = contents
|
|
.filter { url in
|
|
let ext = url.pathExtension.lowercased()
|
|
return ext == "stiktool" || url.hasDirectoryPath
|
|
}
|
|
.map { MiniToolBundle(url: $0) }
|
|
.filter { $0.isValid }
|
|
.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
|
|
DispatchQueue.main.async {
|
|
self.tools = bundles
|
|
}
|
|
}
|
|
|
|
func delete(_ tool: MiniToolBundle) {
|
|
do {
|
|
try FileManager.default.removeItem(at: tool.url)
|
|
refresh()
|
|
} catch {
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func importTool(from externalURL: URL) {
|
|
isBusy = true
|
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
defer { DispatchQueue.main.async { self.isBusy = false } }
|
|
do {
|
|
let accessing = externalURL.startAccessingSecurityScopedResource()
|
|
defer { if accessing { externalURL.stopAccessingSecurityScopedResource() } }
|
|
let destination = self.toolsDirectory().appendingPathComponent(externalURL.lastPathComponent)
|
|
let fm = FileManager.default
|
|
if fm.fileExists(atPath: destination.path) {
|
|
try fm.removeItem(at: destination)
|
|
}
|
|
try fm.copyItem(at: externalURL, to: destination)
|
|
DispatchQueue.main.async { self.refresh() }
|
|
} catch {
|
|
DispatchQueue.main.async { self.lastError = error.localizedDescription }
|
|
}
|
|
}
|
|
}
|
|
|
|
func toolsDirectory() -> URL {
|
|
let dir = URL.documentsDirectory.appendingPathComponent("tools", isDirectory: true)
|
|
var isDir: ObjCBool = false
|
|
let fm = FileManager.default
|
|
if fm.fileExists(atPath: dir.path, isDirectory: &isDir) {
|
|
if !isDir.boolValue {
|
|
try? fm.removeItem(at: dir)
|
|
}
|
|
}
|
|
if !fm.fileExists(atPath: dir.path) {
|
|
try? fm.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
static func toolsDataDirectory() -> URL {
|
|
let dir = URL.documentsDirectory.appendingPathComponent("MiniToolData", isDirectory: true)
|
|
var isDir: ObjCBool = false
|
|
let fm = FileManager.default
|
|
if fm.fileExists(atPath: dir.path, isDirectory: &isDir) {
|
|
if !isDir.boolValue {
|
|
try? fm.removeItem(at: dir)
|
|
}
|
|
}
|
|
if !fm.fileExists(atPath: dir.path) {
|
|
try? fm.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
}
|
|
return dir
|
|
}
|
|
|
|
private func sanitizedToolName(_ raw: String) -> String {
|
|
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_"))
|
|
let filtered = trimmed.unicodeScalars.map { allowed.contains($0) ? Character($0) : "_" }
|
|
let name = String(filtered).replacingOccurrences(of: "__+", with: "_", options: .regularExpression)
|
|
return name.isEmpty ? "Untitled" : name
|
|
}
|
|
}
|