Cleanup files after each iteration of compression and downloading (#3599)

* delete image on each iteration of compression

* replace a few other instances of `unlink()`

* ensure that moving to the permanent path will succeed

* use `cacheDirectory`

* missing file extension?

* assert

* Remove extra .

* Extract safeDeleteAsync, fix normalization

* Normalize everywhere

* Use safeDeleteAsync in more places

* Delete .bin too

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
zio/stable
Hailey 2024-04-24 17:12:36 -07:00 committed by GitHub
parent 90c3ec8749
commit c3fcd486b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 29 deletions

View File

@ -1,4 +1,3 @@
import {deleteAsync} from 'expo-file-system'
import { import {
AppBskyEmbedExternal, AppBskyEmbedExternal,
AppBskyEmbedImages, AppBskyEmbedImages,
@ -20,6 +19,7 @@ import {shortenLinks} from 'lib/strings/rich-text-manip'
import {isNative, isWeb} from 'platform/detection' import {isNative, isWeb} from 'platform/detection'
import {ImageModel} from 'state/models/media/image' import {ImageModel} from 'state/models/media/image'
import {LinkMeta} from '../link-meta/link-meta' import {LinkMeta} from '../link-meta/link-meta'
import {safeDeleteAsync} from '../media/manip'
export interface ExternalEmbedDraft { export interface ExternalEmbedDraft {
uri: string uri: string
@ -119,15 +119,9 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
const {width, height} = image.compressed || image const {width, height} = image.compressed || image
logger.debug(`Uploading image`) logger.debug(`Uploading image`)
const res = await uploadBlob(agent, path, 'image/jpeg') const res = await uploadBlob(agent, path, 'image/jpeg')
if (isNative) { if (isNative) {
try { safeDeleteAsync(path)
deleteAsync(path)
} catch (e) {
console.error(e)
} }
}
images.push({ images.push({
image: res.data.blob, image: res.data.blob,
alt: image.altText ?? '', alt: image.altText ?? '',
@ -182,13 +176,8 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
encoding, encoding,
) )
thumb = thumbUploadRes.data.blob thumb = thumbUploadRes.data.blob
try {
if (isNative) { if (isNative) {
deleteAsync(opts.extLink.localThumb.path) safeDeleteAsync(opts.extLink.localThumb.path)
}
} catch (e) {
console.error(e)
} }
} }
} }

View File

@ -1,7 +1,7 @@
import {Image as RNImage, Share as RNShare} from 'react-native' import {Image as RNImage, Share as RNShare} from 'react-native'
import * as RNFS from 'react-native-fs'
import {Image} from 'react-native-image-crop-picker' import {Image} from 'react-native-image-crop-picker'
import uuid from 'react-native-uuid' import uuid from 'react-native-uuid'
import {cacheDirectory, copyAsync, deleteAsync} from 'expo-file-system'
import * as MediaLibrary from 'expo-media-library' import * as MediaLibrary from 'expo-media-library'
import * as Sharing from 'expo-sharing' import * as Sharing from 'expo-sharing'
import ImageResizer from '@bam.tech/react-native-image-resizer' import ImageResizer from '@bam.tech/react-native-image-resizer'
@ -24,7 +24,10 @@ export async function compressIfNeeded(
mode: 'stretch', mode: 'stretch',
maxSize, maxSize,
}) })
const finalImageMovedPath = await moveToPermanentPath(resizedImage.path) const finalImageMovedPath = await moveToPermanentPath(
resizedImage.path,
'.jpg',
)
const finalImg = { const finalImg = {
...resizedImage, ...resizedImage,
path: finalImageMovedPath, path: finalImageMovedPath,
@ -69,13 +72,10 @@ export async function downloadAndResize(opts: DownloadAndResizeOpts) {
return return
} }
let localUri = downloadRes.path() const localUri = normalizePath(downloadRes.path(), true)
if (!localUri.startsWith('file://')) {
localUri = `file://${localUri}`
}
return await doResize(localUri, opts) return await doResize(localUri, opts)
} finally { } finally {
// TODO Whenever we remove `rn-fetch-blob`, we will need to replace this `flush()` with a `deleteAsync()` -hailey
if (downloadRes) { if (downloadRes) {
downloadRes.flush() downloadRes.flush()
} }
@ -111,7 +111,8 @@ export async function shareImageModal({uri}: {uri: string}) {
UTI: 'image/png', UTI: 'image/png',
}) })
} }
RNFS.unlink(imagePath)
safeDeleteAsync(imagePath)
} }
export async function saveImageToMediaLibrary({uri}: {uri: string}) { export async function saveImageToMediaLibrary({uri}: {uri: string}) {
@ -128,6 +129,7 @@ export async function saveImageToMediaLibrary({uri}: {uri: string}) {
// save // save
await MediaLibrary.createAssetAsync(imagePath) await MediaLibrary.createAssetAsync(imagePath)
safeDeleteAsync(imagePath)
} }
export function getImageDim(path: string): Promise<Dimensions> { export function getImageDim(path: string): Promise<Dimensions> {
@ -174,6 +176,8 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
width: resizeRes.width, width: resizeRes.width,
height: resizeRes.height, height: resizeRes.height,
} }
} else {
safeDeleteAsync(resizeRes.path)
} }
} }
throw new Error( throw new Error(
@ -181,7 +185,7 @@ async function doResize(localUri: string, opts: DoResizeOpts): Promise<Image> {
) )
} }
async function moveToPermanentPath(path: string, ext = ''): Promise<string> { async function moveToPermanentPath(path: string, ext = 'jpg'): Promise<string> {
/* /*
Since this package stores images in a temp directory, we need to move the file to a permanent location. Since this package stores images in a temp directory, we need to move the file to a permanent location.
Relevant: IOS bug when trying to open a second time: Relevant: IOS bug when trying to open a second time:
@ -189,14 +193,33 @@ async function moveToPermanentPath(path: string, ext = ''): Promise<string> {
*/ */
const filename = uuid.v4() const filename = uuid.v4()
const destinationPath = joinPath( // cacheDirectory will not ever be null on native, but it could be on web. This function only ever gets called on
RNFS.TemporaryDirectoryPath, // native so we assert as a string.
`${filename}${ext}`, const destinationPath = joinPath(cacheDirectory as string, filename + ext)
) await copyAsync({
await RNFS.moveFile(path, destinationPath) from: normalizePath(path),
to: normalizePath(destinationPath),
})
safeDeleteAsync(path)
return normalizePath(destinationPath) return normalizePath(destinationPath)
} }
export async function safeDeleteAsync(path: string) {
// Normalize is necessary for Android, otherwise it doesn't delete.
const normalizedPath = normalizePath(path)
try {
await Promise.allSettled([
deleteAsync(normalizedPath, {idempotent: true}),
// HACK: Try this one too. Might exist due to api-polyfill hack.
deleteAsync(normalizedPath.replace(/\.jpe?g$/, '.bin'), {
idempotent: true,
}),
])
} catch (e) {
console.error('Failed to delete file', e)
}
}
function joinPath(a: string, b: string) { function joinPath(a: string, b: string) {
if (a.endsWith('/')) { if (a.endsWith('/')) {
if (b.startsWith('/')) { if (b.startsWith('/')) {