MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能: - ✅ Categories/Series双视图管理(category_view.rs + import_markdown.rs) - ✅ FUSE Multi-Volume支持(tree_type参数) - ✅ SSH/SFTP/SCP/rsync协议完整实现(4042行) - ✅ NFS/SMB Module Phase 1-3完成 - ✅ Archive Module Phase 1-4完成(2916行) - ✅ Download Center API完整实现 - ✅ S3兼容API实现(560行) Git配置修正: - ✅ 删除错误origin(gitea.momentry.ddns.net) - ✅ 删除m5max128(指向机器名) - ✅ 设置origin = m5max128gitea.momentry.ddns.net/admin/markbase - ✅ 设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase 数据清理: - ✅ 删除38个临时SQLite(保留accusys.sqlite、demo.sqlite) - ✅ 删除.bak、test_*.bin、调试脚本等临时文件 - ✅ 删除临时目录(build/、download files/、raid_test/等) - ✅ 更新.gitignore排除临时文件 架构优化: - 52个文件修改,2434行新增,4739行删除 - Workspace成员整合(16个crate) - 数据库状态:accusys.sqlite保留(主demo测试) 远程同步: - ✅ 准备推送到m5max128gitea(远程Gitea) - ✅ 准备推送到m4minigitea(本地Gitea)
This commit is contained in:
873
docs/FSKIT_FIX_TEMPLATE.md
Normal file
873
docs/FSKIT_FIX_TEMPLATE.md
Normal file
@@ -0,0 +1,873 @@
|
||||
# FSKit HelloFS Fix Template
|
||||
**Based on**: KhaosT/FSKitSample
|
||||
**Purpose**: Direct code templates for fixing HelloFS compilation errors
|
||||
|
||||
---
|
||||
|
||||
## 1. Entry Point Fix
|
||||
|
||||
### Current HelloFS (WRONG):
|
||||
```swift
|
||||
@main
|
||||
class HelloFSExtension: FSUnaryFileSystemExtension {
|
||||
static var shared: HelloFSExtension!
|
||||
|
||||
override init() {
|
||||
HelloFSExtension.shared = self
|
||||
super.init()
|
||||
}
|
||||
|
||||
var fileSystem: FSUnaryFileSystem {
|
||||
HelloFS()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixed HelloFS (CORRECT):
|
||||
```swift
|
||||
import Foundation
|
||||
import FSKit
|
||||
|
||||
@main
|
||||
struct HelloFSExtension: UnaryFileSystemExtension {
|
||||
|
||||
var fileSystem: FSUnaryFileSystem & FSUnaryFileSystemOperations {
|
||||
HelloFS()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Changes**:
|
||||
- `class` → `struct`
|
||||
- `FSUnaryFileSystemExtension` → `UnaryFileSystemExtension`
|
||||
- Add `FSUnaryFileSystemOperations` to return type
|
||||
- Remove `static var shared` and `override init()`
|
||||
|
||||
---
|
||||
|
||||
## 2. Filesystem Class Fix
|
||||
|
||||
### Current HelloFS (WRONG):
|
||||
```swift
|
||||
class HelloFS: FSUnaryFileSystemExtension {
|
||||
func probeResource(_ resource: FSResource) -> FSProbeResult {
|
||||
return FSProbeResult.usable(
|
||||
name: "HelloFS",
|
||||
containerID: FSContainerIdentifier(uuid: UUID())
|
||||
)
|
||||
}
|
||||
|
||||
func loadResource(_ resource: FSResource) -> FSVolume {
|
||||
return HelloFSVolume(resource: resource)
|
||||
}
|
||||
|
||||
func unloadResource(_ resource: FSResource) {
|
||||
// Cleanup
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixed HelloFS (CORRECT):
|
||||
```swift
|
||||
import Foundation
|
||||
import FSKit
|
||||
import os
|
||||
|
||||
final class HelloFS: FSUnaryFileSystem, FSUnaryFileSystemOperations {
|
||||
|
||||
private let logger = Logger(subsystem: "com.markbase.hellofs", category: "HelloFS")
|
||||
|
||||
func probeResource(
|
||||
_ resource: FSResource,
|
||||
replyHandler: @escaping (FSProbeResult?, (any Error)?) -> Void
|
||||
) {
|
||||
logger.debug("probeResource: \(resource, privacy: .public)")
|
||||
|
||||
replyHandler(
|
||||
FSProbeResult.usable(
|
||||
name: "HelloFS",
|
||||
containerID: FSContainerIdentifier(uuid: Constants.containerIdentifier)
|
||||
),
|
||||
nil
|
||||
)
|
||||
}
|
||||
|
||||
func loadResource(
|
||||
_ resource: FSResource,
|
||||
options: FSTaskOptions,
|
||||
replyHandler: @escaping (FSVolume?, (any Error)?) -> Void
|
||||
) {
|
||||
logger.debug("loadResource: \(resource, privacy: .public)")
|
||||
containerStatus = .ready
|
||||
replyHandler(HelloFSVolume(resource: resource), nil)
|
||||
}
|
||||
|
||||
func unloadResource(
|
||||
_ resource: FSResource,
|
||||
options: FSTaskOptions,
|
||||
replyHandler: @escaping ((any Error)?) -> Void
|
||||
) {
|
||||
logger.debug("unloadResource: \(resource, privacy: .public)")
|
||||
replyHandler(nil)
|
||||
}
|
||||
|
||||
func didFinishLoading() {
|
||||
logger.debug("didFinishLoading")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Changes**:
|
||||
- `class HelloFS: FSUnaryFileSystemExtension` → `final class HelloFS: FSUnaryFileSystem, FSUnaryFileSystemOperations`
|
||||
- Add `FSUnaryFileSystemOperations` protocol
|
||||
- Add `os.Logger` for debugging
|
||||
- Change all methods to async with `replyHandler`
|
||||
- Add `options: FSTaskOptions` parameter
|
||||
- Add `didFinishLoading()` method
|
||||
|
||||
---
|
||||
|
||||
## 3. Volume Class Fix
|
||||
|
||||
### Current HelloFS (WRONG):
|
||||
```swift
|
||||
class HelloFSVolume: FSVolume {
|
||||
init(resource: FSResource) {
|
||||
super.init(volumeID: FSVolume.Identifier(), volumeName: FSFileName(string: "HelloFS"))
|
||||
}
|
||||
|
||||
func activate() -> FSItem {
|
||||
return rootItem
|
||||
}
|
||||
|
||||
func deactivate() {
|
||||
// Cleanup
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixed HelloFS (CORRECT):
|
||||
```swift
|
||||
import Foundation
|
||||
import FSKit
|
||||
import os
|
||||
|
||||
final class HelloFSVolume: FSVolume {
|
||||
|
||||
private let resource: FSResource
|
||||
private let logger = Logger(subsystem: "com.markbase.hellofs", category: "HelloFSVolume")
|
||||
|
||||
private let root: HelloFSItem = {
|
||||
let item = HelloFSItem(name: FSFileName(string: "/"))
|
||||
item.attributes.parentID = .parentOfRoot
|
||||
item.attributes.fileID = .rootDirectory
|
||||
item.attributes.uid = 0
|
||||
item.attributes.gid = 0
|
||||
item.attributes.linkCount = 1
|
||||
item.attributes.type = .directory
|
||||
item.attributes.mode = UInt32(S_IFDIR | 0b111_000_000)
|
||||
item.attributes.allocSize = 1
|
||||
item.attributes.size = 1
|
||||
return item
|
||||
}()
|
||||
|
||||
init(resource: FSResource) {
|
||||
self.resource = resource
|
||||
|
||||
super.init(
|
||||
volumeID: FSVolume.Identifier(uuid: Constants.volumeIdentifier),
|
||||
volumeName: FSFileName(string: "HelloFS")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FSVolume.PathConfOperations
|
||||
extension HelloFSVolume: FSVolume.PathConfOperations {
|
||||
|
||||
var maximumLinkCount: Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
var maximumNameLength: Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
var restrictsOwnershipChanges: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var truncatesLongNames: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var maximumXattrSize: Int {
|
||||
return Int.max
|
||||
}
|
||||
|
||||
var maximumFileSize: UInt64 {
|
||||
return UInt64.max
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FSVolume.Operations
|
||||
extension HelloFSVolume: FSVolume.Operations {
|
||||
|
||||
var supportedVolumeCapabilities: FSVolume.SupportedCapabilities {
|
||||
logger.debug("supportedVolumeCapabilities")
|
||||
|
||||
let capabilities = FSVolume.SupportedCapabilities()
|
||||
capabilities.supportsHardLinks = true
|
||||
capabilities.supportsSymbolicLinks = true
|
||||
capabilities.supportsPersistentObjectIDs = true
|
||||
capabilities.doesNotSupportVolumeSizes = true
|
||||
capabilities.supportsHiddenFiles = true
|
||||
capabilities.supports64BitObjectIDs = true
|
||||
capabilities.caseFormat = .insensitiveCasePreserving
|
||||
return capabilities
|
||||
}
|
||||
|
||||
var volumeStatistics: FSStatFSResult {
|
||||
logger.debug("volumeStatistics")
|
||||
|
||||
let result = FSStatFSResult(fileSystemTypeName: "HelloFS")
|
||||
result.blockSize = 1024000
|
||||
result.ioSize = 1024000
|
||||
result.totalBlocks = 1024000
|
||||
result.availableBlocks = 1024000
|
||||
result.freeBlocks = 1024000
|
||||
result.totalFiles = 1024000
|
||||
result.freeFiles = 1024000
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func activate(options: FSTaskOptions) async throws -> FSItem {
|
||||
logger.debug("activate")
|
||||
return root
|
||||
}
|
||||
|
||||
func deactivate(options: FSDeactivateOptions = []) async throws {
|
||||
logger.debug("deactivate")
|
||||
}
|
||||
|
||||
func mount(options: FSTaskOptions) async throws {
|
||||
logger.debug("mount")
|
||||
}
|
||||
|
||||
func unmount() async {
|
||||
logger.debug("unmount")
|
||||
}
|
||||
|
||||
func synchronize(flags: FSSyncFlags) async throws {
|
||||
logger.debug("synchronize")
|
||||
}
|
||||
|
||||
func attributes(
|
||||
_ desiredAttributes: FSItem.GetAttributesRequest,
|
||||
of item: FSItem
|
||||
) async throws -> FSItem.Attributes {
|
||||
if let item = item as? HelloFSItem {
|
||||
logger.debug("getItemAttributes: \(item.name)")
|
||||
return item.attributes
|
||||
} else {
|
||||
logger.error("getItemAttributes: invalid item")
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
func setAttributes(
|
||||
_ newAttributes: FSItem.SetAttributesRequest,
|
||||
on item: FSItem
|
||||
) async throws -> FSItem.Attributes {
|
||||
logger.debug("setItemAttributes: \(item)")
|
||||
if let item = item as? HelloFSItem {
|
||||
mergeAttributes(item.attributes, request: newAttributes)
|
||||
return item.attributes
|
||||
} else {
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
func lookupItem(
|
||||
named name: FSFileName,
|
||||
inDirectory directory: FSItem
|
||||
) async throws -> (FSItem, FSFileName) {
|
||||
logger.debug("lookupItem: \(name.string ?? "nil")")
|
||||
|
||||
guard let directory = directory as? HelloFSItem else {
|
||||
throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue)
|
||||
}
|
||||
|
||||
if let item = directory.children[name] {
|
||||
return (item, name)
|
||||
} else {
|
||||
throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
func reclaimItem(_ item: FSItem) async throws {
|
||||
logger.debug("reclaimItem: \(item)")
|
||||
}
|
||||
|
||||
func readSymbolicLink(_ item: FSItem) async throws -> FSFileName {
|
||||
logger.debug("readSymbolicLink: \(item)")
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
|
||||
func createItem(
|
||||
named name: FSFileName,
|
||||
type: FSItem.ItemType,
|
||||
inDirectory directory: FSItem,
|
||||
attributes newAttributes: FSItem.SetAttributesRequest
|
||||
) async throws -> (FSItem, FSFileName) {
|
||||
logger.debug("createItem: \(name.string ?? "nil")")
|
||||
|
||||
guard let directory = directory as? HelloFSItem else {
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
|
||||
let item = HelloFSItem(name: name)
|
||||
mergeAttributes(item.attributes, request: newAttributes)
|
||||
item.attributes.parentID = directory.attributes.fileID
|
||||
item.attributes.type = type
|
||||
directory.addItem(item)
|
||||
|
||||
return (item, name)
|
||||
}
|
||||
|
||||
func createSymbolicLink(
|
||||
named name: FSFileName,
|
||||
inDirectory directory: FSItem,
|
||||
attributes newAttributes: FSItem.SetAttributesRequest,
|
||||
linkContents contents: FSFileName
|
||||
) async throws -> (FSItem, FSFileName) {
|
||||
logger.debug("createSymbolicLink: \(name)")
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
|
||||
func createLink(
|
||||
to item: FSItem,
|
||||
named name: FSFileName,
|
||||
inDirectory directory: FSItem
|
||||
) async throws -> FSFileName {
|
||||
logger.debug("createLink: \(name)")
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
|
||||
func removeItem(
|
||||
_ item: FSItem,
|
||||
named name: FSFileName,
|
||||
fromDirectory directory: FSItem
|
||||
) async throws {
|
||||
logger.debug("removeItem: \(name)")
|
||||
if let item = item as? HelloFSItem, let directory = directory as? HelloFSItem {
|
||||
directory.removeItem(item)
|
||||
} else {
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
func renameItem(
|
||||
_ item: FSItem,
|
||||
inDirectory sourceDirectory: FSItem,
|
||||
named sourceName: FSFileName,
|
||||
to destinationName: FSFileName,
|
||||
inDirectory destinationDirectory: FSItem,
|
||||
overItem: FSItem?
|
||||
) async throws -> FSFileName {
|
||||
logger.debug("renameItem: \(item)")
|
||||
throw fs_errorForPOSIXError(POSIXError.EIO.rawValue)
|
||||
}
|
||||
|
||||
func enumerateDirectory(
|
||||
_ directory: FSItem,
|
||||
startingAt cookie: FSDirectoryCookie,
|
||||
verifier: FSDirectoryVerifier,
|
||||
attributes: FSItem.GetAttributesRequest?,
|
||||
packer: FSDirectoryEntryPacker
|
||||
) async throws -> FSDirectoryVerifier {
|
||||
logger.debug("enumerateDirectory: \(directory)")
|
||||
|
||||
guard let directory = directory as? HelloFSItem else {
|
||||
throw fs_errorForPOSIXError(POSIXError.ENOENT.rawValue)
|
||||
}
|
||||
|
||||
for (idx, item) in directory.children.values.enumerated() {
|
||||
_ = packer.packEntry(
|
||||
name: item.name,
|
||||
itemType: item.attributes.type,
|
||||
itemID: item.attributes.fileID,
|
||||
nextCookie: FSDirectoryCookie(UInt64(idx)),
|
||||
attributes: attributes != nil ? item.attributes : nil
|
||||
)
|
||||
}
|
||||
|
||||
return FSDirectoryVerifier(0)
|
||||
}
|
||||
|
||||
private func mergeAttributes(_ existing: FSItem.Attributes, request: FSItem.SetAttributesRequest) {
|
||||
if request.isValid(FSItem.Attribute.uid) {
|
||||
existing.uid = request.uid
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.gid) {
|
||||
existing.gid = request.gid
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.type) {
|
||||
existing.type = request.type
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.mode) {
|
||||
existing.mode = request.mode
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.linkCount) {
|
||||
existing.linkCount = request.linkCount
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.flags) {
|
||||
existing.flags = request.flags
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.size) {
|
||||
existing.size = request.size
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.allocSize) {
|
||||
existing.allocSize = request.allocSize
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.fileID) {
|
||||
existing.fileID = request.fileID
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.parentID) {
|
||||
existing.parentID = request.parentID
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.accessTime) {
|
||||
let timespec = timespec()
|
||||
request.accessTime = timespec
|
||||
existing.accessTime = timespec
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.changeTime) {
|
||||
let timespec = timespec()
|
||||
request.changeTime = timespec
|
||||
existing.changeTime = timespec
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.modifyTime) {
|
||||
let timespec = timespec()
|
||||
request.modifyTime = timespec
|
||||
existing.modifyTime = timespec
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.addedTime) {
|
||||
let timespec = timespec()
|
||||
request.addedTime = timespec
|
||||
existing.addedTime = timespec
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.birthTime) {
|
||||
let timespec = timespec()
|
||||
request.birthTime = timespec
|
||||
existing.birthTime = timespec
|
||||
}
|
||||
|
||||
if request.isValid(FSItem.Attribute.backupTime) {
|
||||
let timespec = timespec()
|
||||
request.backupTime = timespec
|
||||
existing.backupTime = timespec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FSVolume.OpenCloseOperations
|
||||
extension HelloFSVolume: FSVolume.OpenCloseOperations {
|
||||
|
||||
func openItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws {
|
||||
if let item = item as? HelloFSItem {
|
||||
logger.debug("openItem: \(item.name)")
|
||||
} else {
|
||||
logger.debug("openItem: \(item)")
|
||||
}
|
||||
}
|
||||
|
||||
func closeItem(_ item: FSItem, modes: FSVolume.OpenModes) async throws {
|
||||
if let item = item as? HelloFSItem {
|
||||
logger.debug("closeItem: \(item.name)")
|
||||
} else {
|
||||
logger.debug("closeItem: \(item)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FSVolume.XattrOperations
|
||||
extension HelloFSVolume: FSVolume.XattrOperations {
|
||||
|
||||
func xattr(named name: FSFileName, of item: FSItem) async throws -> Data {
|
||||
logger.debug("xattr: \(item) - \(name.string ?? "NA")")
|
||||
|
||||
if let item = item as? HelloFSItem {
|
||||
return item.xattrs[name] ?? Data()
|
||||
} else {
|
||||
return Data()
|
||||
}
|
||||
}
|
||||
|
||||
func setXattr(named name: FSFileName, to value: Data?, on item: FSItem, policy: FSVolume.SetXattrPolicy) async throws {
|
||||
logger.debug("setXattr: \(item)")
|
||||
|
||||
if let item = item as? HelloFSItem {
|
||||
item.xattrs[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
func xattrs(of item: FSItem) async throws -> [FSFileName] {
|
||||
logger.debug("xattrs: \(item)")
|
||||
|
||||
if let item = item as? HelloFSItem {
|
||||
return Array(item.xattrs.keys)
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FSVolume.ReadWriteOperations
|
||||
extension HelloFSVolume: FSVolume.ReadWriteOperations {
|
||||
|
||||
func read(from item: FSItem, at offset: off_t, length: Int, into buffer: FSMutableFileDataBuffer) async throws -> Int {
|
||||
logger.debug("read: \(item) at \(offset)")
|
||||
|
||||
var bytesRead = 0
|
||||
|
||||
if let item = item as? HelloFSItem, let data = item.data {
|
||||
bytesRead = data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in
|
||||
let length = min(buffer.length, data.count)
|
||||
_ = buffer.withUnsafeMutableBytes { dst in
|
||||
memcpy(dst.baseAddress, ptr.baseAddress, length)
|
||||
}
|
||||
return length
|
||||
}
|
||||
}
|
||||
|
||||
return bytesRead
|
||||
}
|
||||
|
||||
func write(contents: Data, to item: FSItem, at offset: off_t) async throws -> Int {
|
||||
logger.debug("write: \(item) at \(offset)")
|
||||
|
||||
if let item = item as? HelloFSItem {
|
||||
item.data = contents
|
||||
item.attributes.size = UInt64(contents.count)
|
||||
item.attributes.allocSize = UInt64(contents.count)
|
||||
}
|
||||
|
||||
return contents.count
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Changes**:
|
||||
- Add `FSVolume.PathConfOperations` protocol
|
||||
- Add `FSVolume.Operations` protocol with all required methods
|
||||
- Add `FSVolume.OpenCloseOperations` protocol
|
||||
- Add `FSVolume.XattrOperations` protocol
|
||||
- Add `FSVolume.ReadWriteOperations` protocol
|
||||
- Change all methods to `async throws` syntax
|
||||
- Add `options: FSTaskOptions` parameter where required
|
||||
- Implement proper error handling with `fs_errorForPOSIXError`
|
||||
|
||||
---
|
||||
|
||||
## 4. FSItem Implementation
|
||||
|
||||
### HelloFSItem.swift (Complete Implementation):
|
||||
```swift
|
||||
import Foundation
|
||||
import FSKit
|
||||
|
||||
final class HelloFSItem: FSItem {
|
||||
|
||||
private static var id: UInt64 = FSItem.Identifier.rootDirectory.rawValue + 1
|
||||
static func getNextID() -> UInt64 {
|
||||
let current = id
|
||||
id += 1
|
||||
return current
|
||||
}
|
||||
|
||||
let name: FSFileName
|
||||
let id = HelloFSItem.getNextID()
|
||||
|
||||
var attributes = FSItem.Attributes()
|
||||
var xattrs: [FSFileName: Data] = [:]
|
||||
var data: Data?
|
||||
|
||||
private(set) var children: [FSFileName: HelloFSItem] = [:]
|
||||
|
||||
init(name: FSFileName) {
|
||||
self.name = name
|
||||
attributes.fileID = FSItem.Identifier(rawValue: id) ?? .invalid
|
||||
attributes.size = 0
|
||||
attributes.allocSize = 0
|
||||
attributes.flags = 0
|
||||
|
||||
var timespec = timespec()
|
||||
timespec_get(×pec, TIME_UTC)
|
||||
|
||||
attributes.addedTime = timespec
|
||||
attributes.birthTime = timespec
|
||||
attributes.changeTime = timespec
|
||||
attributes.modifyTime = timespec
|
||||
attributes.accessTime = timespec
|
||||
}
|
||||
|
||||
func addItem(_ item: HelloFSItem) {
|
||||
children[item.name] = item
|
||||
}
|
||||
|
||||
func removeItem(_ item: HelloFSItem) {
|
||||
children[item.name] = nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Constants.swift
|
||||
|
||||
```swift
|
||||
import Foundation
|
||||
|
||||
enum Constants {
|
||||
|
||||
static let containerIdentifier: UUID = UUID(uuidString: "8E055EB2-12FD-4EB8-A315-C082CBCFBDD3")!
|
||||
static let volumeIdentifier: UUID = UUID(uuidString: "CDCB994E-677C-482B-B1D2-E7BC1E07546E")!
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Info.plist Configuration
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>EXAppExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>EXExtensionPointIdentifier</key>
|
||||
<string>com.apple.fskit.fsmodule</string>
|
||||
|
||||
<key>FSShortName</key>
|
||||
<string>HelloFS</string>
|
||||
|
||||
<key>FSPersonalities</key>
|
||||
<dict>
|
||||
<key>HelloFSExtensionPersonality</key>
|
||||
<dict>
|
||||
<key>FSName</key>
|
||||
<string>HelloFS</string>
|
||||
<key>FSfileObjectsAreCaseSensitive</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
<key>FSSupportsBlockResources</key>
|
||||
<true/>
|
||||
|
||||
<key>FSSupportsGenericURLResources</key>
|
||||
<false/>
|
||||
|
||||
<key>FSSupportsPathURLs</key>
|
||||
<false/>
|
||||
|
||||
<key>FSSupportsServerURLs</key>
|
||||
<false/>
|
||||
|
||||
<key>FSRequiresSecurityScopedPathURLResources</key>
|
||||
<false/>
|
||||
|
||||
<key>FSMediaTypes</key>
|
||||
<dict/>
|
||||
|
||||
<key>FSActivateOptionSyntax</key>
|
||||
<dict>
|
||||
<key>shortOptions</key>
|
||||
<string>g:m:o:u:</string>
|
||||
</dict>
|
||||
|
||||
<key>FSCheckOptionSyntax</key>
|
||||
<dict>
|
||||
<key>shortOptions</key>
|
||||
<string>nqy</string>
|
||||
</dict>
|
||||
|
||||
<key>FSFormatOptionSyntax</key>
|
||||
<dict>
|
||||
<key>shortOptions</key>
|
||||
<string>v</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Entitlements Configuration
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.fskit.fsmodule</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Main App (for Extension Management)
|
||||
|
||||
### HelloFSApp.swift:
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct HelloFSApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ContentView.swift:
|
||||
```swift
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@State
|
||||
private var viewModel = ViewModel()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("HelloFS Extension Manager")
|
||||
.font(.title)
|
||||
|
||||
List {
|
||||
ForEach(viewModel.modules, id: \.self) { module in
|
||||
VStack(alignment: .leading) {
|
||||
Text(module.bundleIdentifier)
|
||||
.bold()
|
||||
Text(module.url.absoluteString)
|
||||
.font(.caption)
|
||||
Text("Enabled: \(module.isEnabled ? "Yes" : "No")")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ViewModel.swift:
|
||||
```swift
|
||||
import Foundation
|
||||
import FSKit
|
||||
import Observation
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
final class ViewModel {
|
||||
|
||||
private var client: FSClient?
|
||||
private(set) var modules: [FSModuleIdentity] = []
|
||||
|
||||
init() {
|
||||
client = FSClient.shared
|
||||
client?.fetchInstalledExtensions { modules, errors in
|
||||
if let modules {
|
||||
self.modules = modules
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Build Configuration
|
||||
|
||||
### Xcode Project Setup:
|
||||
|
||||
1. **Create macOS App Target** (HelloFSApp)
|
||||
- Platform: macOS Sequoia 15.4+
|
||||
- Language: Swift 5.7+
|
||||
- User Interface: SwiftUI
|
||||
- Add HelloFSApp.swift, ContentView.swift, ViewModel.swift
|
||||
|
||||
2. **Create File System Extension Target** (HelloFSExtension)
|
||||
- Platform: macOS Sequoia 15.4+
|
||||
- Language: Swift 5.7+
|
||||
- Add HelloFSExtension.swift, HelloFS.swift, HelloFSVolume.swift, HelloFSItem.swift, Constants.swift
|
||||
|
||||
3. **Configure Extension Info.plist**
|
||||
- Add EXAppExtensionAttributes dictionary
|
||||
- Configure FSKit settings
|
||||
|
||||
4. **Configure Entitlements**
|
||||
- Enable App Sandbox
|
||||
- Enable FSKit module capability
|
||||
|
||||
5. **Build & Run**
|
||||
- Build the app target
|
||||
- Run the app
|
||||
- Enable extension in System Settings
|
||||
- Test mounting
|
||||
|
||||
---
|
||||
|
||||
## 10. Testing Commands
|
||||
|
||||
```bash
|
||||
# Create dummy block device
|
||||
mkfile -n 100m hellofs_dummy
|
||||
|
||||
# Mount as block device
|
||||
hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount hellofs_dummy
|
||||
# Output: /dev/disk2
|
||||
|
||||
# Create mount point
|
||||
mkdir /tmp/HelloFS
|
||||
|
||||
# Mount filesystem
|
||||
mount -F -t HelloFS disk2 /tmp/HelloFS
|
||||
|
||||
# Test filesystem
|
||||
ls /tmp/HelloFS
|
||||
touch /tmp/HelloFS/test.txt
|
||||
echo "Hello World" > /tmp/HelloFS/test.txt
|
||||
cat /tmp/HelloFS/test.txt
|
||||
|
||||
# Unmount
|
||||
umount /tmp/HelloFS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-06-11
|
||||
**Status**: Complete Template Ready for Implementation
|
||||
**Next Steps**: Apply these templates to fix HelloFS compilation errors
|
||||
Reference in New Issue
Block a user