first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,373 @@
// Copyright 2024-present 650 Industries. All rights reserved.
import ExpoModulesCore
@available(iOS 14, tvOS 14, *)
public final class FileSystemModule: Module {
#if os(iOS)
private lazy var filePickingHandler = FilePickingHandler(module: self)
#endif
var documentDirectory: URL? {
return appContext?.config.documentDirectory
}
var cacheDirectory: URL? {
return appContext?.config.cacheDirectory
}
var totalDiskSpace: Int64? {
guard let path = documentDirectory?.path,
let attributes = try? FileManager.default.attributesOfFileSystem(forPath: path) else {
return nil
}
return attributes[.systemFreeSize] as? Int64
}
var availableDiskSpace: Int64? {
guard let path = documentDirectory?.path,
let attributes = try? FileManager.default.attributesOfFileSystem(forPath: path) else {
return nil
}
return attributes[.systemFreeSize] as? Int64
}
public func definition() -> ModuleDefinition {
Name("FileSystem")
Constant("documentDirectory") {
return documentDirectory?.absoluteString
}
Constant("cacheDirectory") {
return cacheDirectory?.absoluteString
}
Constant("bundleDirectory") {
return Bundle.main.bundlePath
}
Constant("appleSharedContainers") {
return getAppleSharedContainers()
}
Property("totalDiskSpace") {
return totalDiskSpace
}
Property("availableDiskSpace") {
return availableDiskSpace
}
// swiftlint:disable:next closure_body_length
AsyncFunction("downloadFileAsync") { (url: URL, to: FileSystemPath, options: DownloadOptions?, promise: Promise) in
try to.validatePermission(.write)
var request = URLRequest(url: url)
if let headers = options?.headers {
headers.forEach { key, value in
request.addValue(value, forHTTPHeaderField: key)
}
}
let downloadTask = URLSession.shared.downloadTask(with: request) { urlOrNil, responseOrNil, errorOrNil in
guard errorOrNil == nil else {
return promise.reject(UnableToDownloadException(errorOrNil?.localizedDescription ?? "unspecified error"))
}
guard let httpResponse = responseOrNil as? HTTPURLResponse else {
return promise.reject(UnableToDownloadException("no response"))
}
guard httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 else {
return promise.reject(UnableToDownloadException("response has status \(httpResponse.statusCode)"))
}
guard let fileURL = urlOrNil else {
return promise.reject(UnableToDownloadException("no file url"))
}
do {
let destination: URL
if let to = to as? FileSystemDirectory {
let filename = httpResponse.suggestedFilename ?? url.lastPathComponent
destination = to.url.appendingPathComponent(filename)
} else {
destination = to.url
}
if FileManager.default.fileExists(atPath: destination.path) {
if options?.idempotent == true {
try FileManager.default.removeItem(at: destination)
} else {
throw DestinationAlreadyExistsException()
}
}
try FileManager.default.moveItem(at: fileURL, to: destination)
// TODO: Remove .url.absoluteString once returning shared objects works
promise.resolve(destination.absoluteString)
} catch {
promise.reject(error)
}
}
downloadTask.resume()
}
AsyncFunction("pickDirectoryAsync") { (initialUri: URL?, promise: Promise) in
#if os(iOS)
filePickingHandler.presentDocumentPicker(
picker: createDirectoryPicker(initialUri: initialUri),
isDirectory: true,
initialUri: initialUri,
mimeType: nil,
promise: promise
)
#else
promise.reject(FeatureNotAvailableOnPlatformException())
#endif
}.runOnQueue(.main)
AsyncFunction("pickFileAsync") { (initialUri: URL?, mimeType: String?, promise: Promise) in
#if os(iOS)
filePickingHandler.presentDocumentPicker(
picker: createFilePicker(initialUri: initialUri, mimeType: mimeType),
isDirectory: false,
initialUri: initialUri,
mimeType: mimeType,
promise: promise
)
#else
promise.reject(FeatureNotAvailableOnPlatformException())
#endif
}.runOnQueue(.main)
Function("info") { (url: URL) in
let output = PathInfo()
output.exists = false
output.isDirectory = nil
guard let fileSystemManager = appContext?.fileSystem else {
return output
}
if fileSystemManager.getPathPermissions(url.path).contains(.read) {
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) {
output.exists = true
output.isDirectory = isDirectory.boolValue
return output
}
}
return output
}
// swiftlint:disable:next closure_body_length
Class(FileSystemFile.self) {
Constructor { (url: URL) in
return FileSystemFile(url: url.standardizedFileURL)
}
// we can't throw in a constructor, so this is a workaround
Function("validatePath") { file in
try file.validatePath()
}
// maybe asString, readAsString, readAsText, readText, ect.
AsyncFunction("text") { file in
return try file.text()
}
Function("textSync") { file in
return try file.text()
}
AsyncFunction("base64") { file in
return try file.base64()
}
Function("base64Sync") { file in
return try file.base64()
}
AsyncFunction("bytes") { file in
return try file.bytes()
}
Function("bytesSync") { file in
return try file.bytes()
}
Function("open") { file in
return try FileSystemFileHandle(file: file)
}
Function("info") { (file: FileSystemFile, options: InfoOptions?) in
return try file.info(options: options ?? InfoOptions())
}
Function("write") { (file: FileSystemFile, content: Either<String, TypedArray>, options: WriteOptions?) in
let append = options?.append ?? false
if let content: String = content.get() {
if options?.encoding == WriteEncoding.base64 {
guard let data = Data(base64Encoded: content, options: .ignoreUnknownCharacters) else {
throw UnableToWriteBase64DataException(file.url.absoluteString)
}
try file.write(data, append: append)
} else {
try file.write(content, append: append)
}
}
if let content: TypedArray = content.get() {
try file.write(content, append: append)
}
}
Property("size") { file in
try? file.size
}
Property("md5") { file in
try? file.md5
}
Property("modificationTime") { file in
try? file.modificationTime
}
Property("creationTime") { file in
try? file.creationTime
}
Property("type") { file in
file.type
}
Function("delete") { file in
try file.delete()
}
Property("exists") { file in
return file.exists
}
Function("create") { (file, options: CreateOptions?) in
try file.create(options ?? CreateOptions())
}
Function("copy") { (file, to: FileSystemPath) in
try file.copy(to: to)
}
Function("move") { (file, to: FileSystemPath) in
try file.move(to: to)
}
Function("rename") { (file, newName: String) in
try file.rename(newName)
}
Property("uri") { file in
return file.url.absoluteString
}
}
Class(FileSystemFileHandle.self) {
Function("readBytes") { (fileHandle, bytes: Int) in
try fileHandle.read(bytes)
}
Function("writeBytes") { (fileHandle, bytes: Data) in
try fileHandle.write(bytes)
}
Function("close") { fileHandle in
try fileHandle.close()
}
Property("offset") { fileHandle in
fileHandle.offset
}.set { (fileHandle, volume: UInt64) in
fileHandle.offset = volume
}
Property("size") { fileHandle in
fileHandle.size
}
}
// swiftlint:disable:next closure_body_length
Class(FileSystemDirectory.self) {
Constructor { (url: URL) in
return FileSystemDirectory(url: url.standardizedFileURL)
}
Function("info") { directory in
try directory.info()
}
// we can't throw in a constructor, so this is a workaround
Function("validatePath") { directory in
try directory.validatePath()
}
Function("delete") { directory in
try directory.delete()
}
Property("exists") { directory in
return directory.exists
}
Function("create") { (directory, options: CreateOptions?) in
try directory.create(options ?? CreateOptions())
}
Function("copy") { (directory, to: FileSystemPath) in
try directory.copy(to: to)
}
Function("move") { (directory, to: FileSystemPath) in
try directory.move(to: to)
}
Function("rename") { (directory, newName: String) in
try directory.rename(newName)
}
// this function is internal and will be removed in the future (when returning arrays of shared objects is supported)
Function("listAsRecords") { directory in
try directory.listAsRecords()
}
Function("createFile") { (directory, name: String, content: String?) in
let file = FileSystemFile(url: directory.url.appendingPathComponent(name))
try file.create(CreateOptions())
return file
}
Function("createDirectory") { (directory, name: String) in
let newDirectory = FileSystemDirectory(url: directory.url.appendingPathComponent(name))
try newDirectory.create(CreateOptions())
return newDirectory
}
Property("uri") { directory in
return directory.url.absoluteString
}
Property("size") { directory in
return try? directory.size
}
}
}
private func getAppleSharedContainers() -> [String: String] {
guard let appContext else {
return [:]
}
var result: [String: String] = [:]
for appGroup in appContext.appCodeSignEntitlements.appGroups ?? [] {
if let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) {
result[appGroup] = directory.standardizedFileURL.path
}
}
return result
}
}