Files
Fluxup_PAP/node_modules/expo-file-system/ios/Legacy/FileSystemLegacyModule.swift
2026-03-10 16:18:05 +00:00

317 lines
12 KiB
Swift

// Copyright 2023-present 650 Industries. All rights reserved.
import ExpoModulesCore
import Photos
private let EVENT_DOWNLOAD_PROGRESS = "expo-file-system.downloadProgress"
private let EVENT_UPLOAD_PROGRESS = "expo-file-system.uploadProgress"
public final class FileSystemLegacyModule: Module {
private var sessionTaskDispatcher: EXSessionTaskDispatcher!
private lazy var taskHandlersManager = EXTaskHandlersManager()
private lazy var resourceManager = PHAssetResourceManager()
private lazy var backgroundSession = createUrlSession(type: .background, delegate: sessionTaskDispatcher)
private lazy var foregroundSession = createUrlSession(type: .foreground, delegate: sessionTaskDispatcher)
private var documentDirectory: URL? {
return appContext?.config.documentDirectory
}
private var cacheDirectory: URL? {
return appContext?.config.cacheDirectory
}
public func definition() -> ModuleDefinition {
Name("ExponentFileSystem")
Constant("documentDirectory") {
return documentDirectory?.absoluteString
}
Constant("cacheDirectory") {
return cacheDirectory?.absoluteString
}
Constant("bundleDirectory") {
return Bundle.main.bundlePath
}
Events(EVENT_DOWNLOAD_PROGRESS, EVENT_UPLOAD_PROGRESS)
OnCreate {
Task { @MainActor in
sessionTaskDispatcher = EXSessionTaskDispatcher(
sessionHandler: ExpoAppDelegateSubscriberRepository.getSubscriberOfType(FileSystemBackgroundSessionHandler.self)
)
}
}
AsyncFunction("getInfoAsync") { (url: URL, options: InfoOptions, promise: Promise) in
let optionsDict = options.toDictionary(appContext: appContext)
switch url.scheme {
case "file":
EXFileSystemLocalFileHandler.getInfoForFile(url, withOptions: optionsDict, resolver: promise.resolver, rejecter: promise.legacyRejecter)
case "assets-library", "ph":
EXFileSystemAssetLibraryHandler.getInfoForFile(url, withOptions: optionsDict, resolver: promise.resolver, rejecter: promise.legacyRejecter)
default:
throw UnsupportedSchemeException(url.scheme)
}
}
AsyncFunction("readAsStringAsync") { (url: URL, options: ReadingOptions) -> String in
try ensurePathPermission(appContext, path: url.path, flag: .read)
if options.encoding == .base64 {
return try readFileAsBase64(path: url.path, options: options)
}
do {
return try String(contentsOfFile: url.path, encoding: options.encoding.toStringEncoding() ?? .utf8)
} catch {
throw FileNotReadableException(url.path)
}
}
AsyncFunction("writeAsStringAsync") { (url: URL, string: String, options: WritingOptions) in
try ensurePathPermission(appContext, path: url.path, flag: .write)
let data: Data?
if options.encoding == .base64 {
data = Data(base64Encoded: string, options: .ignoreUnknownCharacters)
} else {
data = string.data(using: options.encoding.toStringEncoding() ?? .utf8)
}
guard let data else {
throw FileNotWritableException(url.path)
}
do {
if options.append {
if !FileManager.default.fileExists(atPath: url.path) {
try data.write(to: url, options: .atomic)
} else {
let fileHandle = try FileHandle(forWritingTo: url)
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(data)
}
} else {
try data.write(to: url, options: .atomic)
}
} catch {
throw FileNotWritableException(url.path)
.causedBy(error)
}
}
AsyncFunction("deleteAsync") { (url: URL, options: DeletingOptions) in
guard url.isFileURL else {
throw InvalidFileUrlException(url)
}
try ensurePathPermission(appContext, path: url.appendingPathComponent("..").path, flag: .write)
try removeFile(path: url.path, idempotent: options.idempotent)
}
AsyncFunction("moveAsync") { (options: RelocatingOptions) in
let (fromUrl, toUrl) = try options.asTuple()
guard fromUrl.isFileURL else {
throw InvalidFileUrlException(fromUrl)
}
guard toUrl.isFileURL else {
throw InvalidFileUrlException(toUrl)
}
try ensurePathPermission(appContext, path: fromUrl.appendingPathComponent("..").path, flag: .write)
try ensurePathPermission(appContext, path: toUrl.path, flag: .write)
try removeFile(path: toUrl.path, idempotent: true)
try FileManager.default.moveItem(atPath: fromUrl.path, toPath: toUrl.path)
}
AsyncFunction("copyAsync") { (options: RelocatingOptions, promise: Promise) in
let (fromUrl, toUrl) = try options.asTuple()
if isPHAsset(path: fromUrl.absoluteString) {
copyPHAsset(fromUrl: fromUrl, toUrl: toUrl, with: resourceManager, promise: promise)
return
}
try ensurePathPermission(appContext, path: fromUrl.path, flag: .read)
try ensurePathPermission(appContext, path: toUrl.path, flag: .write)
if fromUrl.scheme == "file" {
EXFileSystemLocalFileHandler.copy(from: fromUrl, to: toUrl, resolver: promise.resolver, rejecter: promise.legacyRejecter)
} else if ["ph", "assets-library"].contains(fromUrl.scheme) {
EXFileSystemAssetLibraryHandler.copy(from: fromUrl, to: toUrl, resolver: promise.resolver, rejecter: promise.legacyRejecter)
} else {
throw InvalidFileUrlException(fromUrl)
}
}
AsyncFunction("makeDirectoryAsync") { (url: URL, options: MakeDirectoryOptions) in
guard url.isFileURL else {
throw InvalidFileUrlException(url)
}
try ensurePathPermission(appContext, path: url.path, flag: .write)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: options.intermediates, attributes: nil)
}
AsyncFunction("readDirectoryAsync") { (url: URL) -> [String] in
guard url.isFileURL else {
throw InvalidFileUrlException(url)
}
try ensurePathPermission(appContext, path: url.path, flag: .read)
return try FileManager.default.contentsOfDirectory(atPath: url.path)
}
AsyncFunction("downloadAsync") { (sourceUrl: URL, localUrl: URL, options: DownloadOptionsLegacy, promise: Promise) in
try ensureFileDirectoryExists(localUrl)
try ensurePathPermission(appContext, path: localUrl.path, flag: .write)
if sourceUrl.isFileURL {
try ensurePathPermission(appContext, path: sourceUrl.path, flag: .read)
EXFileSystemLocalFileHandler.copy(from: sourceUrl, to: localUrl, resolver: promise.resolver, rejecter: promise.legacyRejecter)
return
}
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let request = createUrlRequest(url: sourceUrl, headers: options.headers)
let downloadTask = session.downloadTask(with: request)
let taskDelegate = EXSessionDownloadTaskDelegate(
resolve: promise.resolver,
reject: promise.legacyRejecter,
localUrl: localUrl,
shouldCalculateMd5: options.md5
)
sessionTaskDispatcher.register(taskDelegate, for: downloadTask)
downloadTask.resume()
}
AsyncFunction("uploadAsync") { (targetUrl: URL, localUrl: URL, options: UploadOptions, promise: Promise) in
guard localUrl.isFileURL else {
throw InvalidFileUrlException(localUrl)
}
guard FileManager.default.fileExists(atPath: localUrl.path) else {
throw FileNotExistsException(localUrl.path)
}
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let task = try createUploadTask(session: session, targetUrl: targetUrl, sourceUrl: localUrl, options: options)
let taskDelegate = EXSessionUploadTaskDelegate(resolve: promise.resolver, reject: promise.legacyRejecter)
sessionTaskDispatcher.register(taskDelegate, for: task)
task.resume()
}
AsyncFunction("uploadTaskStartAsync") { (targetUrl: URL, localUrl: URL, uuid: String, options: UploadOptions, promise: Promise) in
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let task = try createUploadTask(session: session, targetUrl: targetUrl, sourceUrl: localUrl, options: options)
let onSend: EXUploadDelegateOnSendCallback = { [weak self] _, _, totalBytesSent, totalBytesExpectedToSend in
self?.sendEvent(EVENT_UPLOAD_PROGRESS, [
"uuid": uuid,
"data": [
"totalBytesSent": totalBytesSent,
"totalBytesExpectedToSend": totalBytesExpectedToSend
]
])
}
let taskDelegate = EXSessionCancelableUploadTaskDelegate(
resolve: promise.resolver,
reject: promise.legacyRejecter,
onSendCallback: onSend,
resumableManager: taskHandlersManager,
uuid: uuid
)
sessionTaskDispatcher.register(taskDelegate, for: task)
taskHandlersManager.register(task, uuid: uuid)
task.resume()
}
// swiftlint:disable:next line_length closure_body_length
AsyncFunction("downloadResumableStartAsync") { (sourceUrl: URL, localUrl: URL, uuid: String, options: DownloadOptionsLegacy, resumeDataString: String?, promise: Promise) in
try ensureFileDirectoryExists(localUrl)
try ensurePathPermission(appContext, path: localUrl.path, flag: .write)
let session = options.sessionType == .background ? backgroundSession : foregroundSession
let onWrite: EXDownloadDelegateOnWriteCallback = { [weak self] _, _, totalBytesWritten, totalBytesExpectedToWrite in
self?.sendEvent(EVENT_DOWNLOAD_PROGRESS, [
"uuid": uuid,
"data": [
"totalBytesWritten": totalBytesWritten,
"totalBytesExpectedToWrite": totalBytesExpectedToWrite
]
])
}
let task: URLSessionDownloadTask
if let resumeDataString, let resumeData = Data(base64Encoded: resumeDataString) {
task = session.downloadTask(withResumeData: resumeData)
} else {
let request = createUrlRequest(url: sourceUrl, headers: options.headers)
task = session.downloadTask(with: request)
}
let taskDelegate = EXSessionResumableDownloadTaskDelegate(
resolve: promise.resolver,
reject: promise.legacyRejecter,
localUrl: localUrl,
shouldCalculateMd5: options.md5,
onWriteCallback: onWrite,
resumableManager: taskHandlersManager,
uuid: uuid
)
sessionTaskDispatcher.register(taskDelegate, for: task)
taskHandlersManager.register(task, uuid: uuid)
task.resume()
}
AsyncFunction("downloadResumablePauseAsync") { (id: String) -> [String: String?] in
guard let task = taskHandlersManager.downloadTask(forId: id) else {
throw DownloadTaskNotFoundException(id)
}
let resumeData = await task.cancelByProducingResumeData()
return [
"resumeData": resumeData?.base64EncodedString()
]
}
AsyncFunction("networkTaskCancelAsync") { (id: String) in
taskHandlersManager.task(forId: id)?.cancel()
}
AsyncFunction("getFreeDiskStorageAsync") { () -> Int64 in
// Uses required reason API based on the following reason: E174.1 85F4.1
#if !os(tvOS)
let resourceValues = try getResourceValues(from: documentDirectory, forKeys: [.volumeAvailableCapacityForImportantUsageKey])
guard let availableCapacity = resourceValues?.volumeAvailableCapacityForImportantUsage else {
throw CannotDetermineDiskCapacity()
}
return availableCapacity
#else
let resourceValues = try getResourceValues(from: cacheDirectory, forKeys: [.volumeAvailableCapacityKey])
guard let availableCapacity = resourceValues?.volumeAvailableCapacity else {
throw CannotDetermineDiskCapacity()
}
return Int64(availableCapacity)
#endif
}
AsyncFunction("getTotalDiskCapacityAsync") { () -> Int in
// Uses required reason API based on the following reason: E174.1 85F4.1
let resourceValues = try getResourceValues(from: documentDirectory, forKeys: [.volumeTotalCapacityKey])
guard let totalCapacity = resourceValues?.volumeTotalCapacity else {
throw CannotDetermineDiskCapacity()
}
return totalCapacity
}
}
}