XuqmGroup-RNSDK/packages/common/src/file.ts

164 行
4.8 KiB
TypeScript

import { Platform } from 'react-native'
import { fileExtension, sanitizeFileName } from './fileName'
export {
fileExtension,
fileNameFromUrl,
parseContentDispositionFileName,
sanitizeFileName,
} from './fileName'
export type FileConflictPolicy = 'rename' | 'overwrite'
export interface FileTransferProgress {
bytesTransferred: number
totalBytes: number | null
percent: number | null
}
export interface FileDownloadTask {
cancel(): void
result: Promise<{ path: string }>
}
const MIME_BY_EXTENSION: Readonly<Record<string, string>> = {
pdf: 'application/pdf',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
webp: 'image/webp',
bmp: 'image/bmp',
svg: 'image/svg+xml',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
zip: 'application/zip',
rar: 'application/vnd.rar',
txt: 'text/plain',
csv: 'text/csv',
json: 'application/json',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
}
function blobUtil(): typeof import('react-native-blob-util').default {
// Lazy loading avoids touching the native module until a file operation is requested.
// eslint-disable-next-line @typescript-eslint/no-require-imports
return (
require('react-native-blob-util') as {
default: typeof import('react-native-blob-util').default
}
).default
}
export function inferMimeType(pathOrName: string): string {
return MIME_BY_EXTENSION[fileExtension(pathOrName)] ?? 'application/octet-stream'
}
export function getFileDirectories() {
return blobUtil().fs.dirs
}
export async function fileExists(path: string): Promise<boolean> {
return blobUtil().fs.exists(path)
}
export async function ensureDirectory(path: string): Promise<void> {
if (!(await fileExists(path))) await blobUtil().fs.mkdir(path)
}
export async function resolveAvailableFilePath(
directory: string,
fileName: string,
conflict: FileConflictPolicy = 'rename',
): Promise<string> {
const safeName = sanitizeFileName(fileName)
const initial = `${directory.replace(/\/$/, '')}/${safeName}`
if (conflict === 'overwrite' || !(await fileExists(initial))) return initial
const dot = safeName.lastIndexOf('.')
const base = dot > 0 ? safeName.slice(0, dot) : safeName
const extension = dot > 0 ? safeName.slice(dot) : ''
for (let index = 1; ; index += 1) {
const candidate = `${directory.replace(/\/$/, '')}/${base}(${index})${extension}`
if (!(await fileExists(candidate))) return candidate
}
}
export async function writeBase64File(path: string, base64: string): Promise<void> {
await blobUtil().fs.writeFile(path, base64, 'base64')
}
export async function readFileAsBase64(path: string): Promise<string> {
return blobUtil().fs.readFile(path, 'base64')
}
export async function deleteFile(path: string): Promise<void> {
if (await fileExists(path)) await blobUtil().fs.unlink(path)
}
export async function openLocalFile(
path: string,
mimeType = inferMimeType(path),
): Promise<boolean> {
try {
if (Platform.OS === 'android') {
const result = await blobUtil().android.actionViewIntent(path, mimeType)
return result !== false
}
await blobUtil().ios.openDocument(path)
return true
} catch {
return false
}
}
export function downloadFileToPath(
url: string,
path: string,
options: {
headers?: Record<string, string>
onProgress?: (progress: FileTransferProgress) => void
} = {},
): FileDownloadTask {
const task = blobUtil().config({ path }).fetch('GET', url, options.headers)
task.progress({ interval: 300 }, (received: number, total: number) => {
const bytesTransferred = received
const totalValue = total
const totalBytes = Number.isFinite(totalValue) && totalValue > 0 ? totalValue : null
options.onProgress?.({
bytesTransferred,
totalBytes,
percent:
totalBytes === null
? null
: Math.min(100, Math.round((bytesTransferred / totalBytes) * 100)),
})
})
return {
cancel: () => task.cancel(),
result: task.then(response => ({ path: response.path() })),
}
}
export async function registerAndroidDownloadedFile(options: {
path: string
title: string
description?: string
mimeType?: string
showNotification?: boolean
}): Promise<void> {
if (Platform.OS !== 'android') return
await blobUtil().android.addCompleteDownload({
path: options.path,
title: options.title,
description: options.description ?? options.title,
mime: options.mimeType ?? inferMimeType(options.path),
showNotification: options.showNotification ?? true,
})
}