Thumbnails shouldn't be revoked
So make the face crops behave the same too
This commit is contained in:
@@ -64,34 +64,19 @@ export const ItemCard: React.FC<React.PropsWithChildren<ItemCardProps>> = ({
|
||||
if (!coverFile) return;
|
||||
|
||||
let didCancel = false;
|
||||
let thisObjectURL: string | undefined;
|
||||
|
||||
const go = async () => {
|
||||
if (coverFaceID) {
|
||||
const blob = await faceCrop(coverFaceID, coverFile);
|
||||
if (!didCancel) {
|
||||
thisObjectURL = blob
|
||||
? URL.createObjectURL(blob)
|
||||
: undefined;
|
||||
setCoverImageURL(thisObjectURL);
|
||||
}
|
||||
} else {
|
||||
const url = await downloadManager.getThumbnailForPreview(
|
||||
coverFile,
|
||||
isScrolling,
|
||||
);
|
||||
if (!didCancel) {
|
||||
thisObjectURL = url;
|
||||
setCoverImageURL(thisObjectURL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void go();
|
||||
if (coverFaceID) {
|
||||
void faceCrop(coverFaceID, coverFile).then(
|
||||
(url) => !didCancel && setCoverImageURL(url),
|
||||
);
|
||||
} else {
|
||||
void downloadManager
|
||||
.getThumbnailForPreview(coverFile, isScrolling)
|
||||
.then((url) => !didCancel && setCoverImageURL(url));
|
||||
}
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
if (thisObjectURL) URL.revokeObjectURL(thisObjectURL);
|
||||
};
|
||||
}, [coverFile, coverFaceID, isScrolling]);
|
||||
|
||||
|
||||
@@ -216,25 +216,22 @@ const FaceCropImageView: React.FC<FaceCropImageViewProps> = ({
|
||||
enteFile,
|
||||
placeholderDimension,
|
||||
}) => {
|
||||
const [objectURL, setObjectURL] = useState<string | undefined>();
|
||||
const [url, setURL] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
let thisObjectURL: string | undefined;
|
||||
|
||||
void faceCrop(faceID, enteFile).then((blob) => {
|
||||
if (blob && !didCancel)
|
||||
setObjectURL((thisObjectURL = URL.createObjectURL(blob)));
|
||||
});
|
||||
void faceCrop(faceID, enteFile).then(
|
||||
(url) => !didCancel && setURL(url),
|
||||
);
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
if (thisObjectURL) URL.revokeObjectURL(thisObjectURL);
|
||||
};
|
||||
}, [faceID, enteFile]);
|
||||
|
||||
return objectURL ? (
|
||||
<img style={{ objectFit: "cover" }} src={objectURL} />
|
||||
return url ? (
|
||||
<img style={{ objectFit: "cover" }} src={url} />
|
||||
) : (
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
|
||||
@@ -145,7 +145,7 @@ class DownloadManagerImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves with an object URL that points to the file's thumbnail.
|
||||
* Resolves with an URL that points to the file's thumbnail.
|
||||
*
|
||||
* The thumbnail will be downloaded (unless {@link localOnly} is true) and
|
||||
* cached.
|
||||
@@ -155,6 +155,9 @@ class DownloadManagerImpl {
|
||||
* to download the file but should instead fulfill the request from the
|
||||
* cache. This avoids an unbounded flurry of requests on scroll, only
|
||||
* downloading when the position has quiescized.
|
||||
*
|
||||
* The returned URL is actually an object URL, but it should not be revoked
|
||||
* since the download manager caches it for future use.
|
||||
*/
|
||||
async getThumbnailForPreview(
|
||||
file: EnteFile,
|
||||
|
||||
@@ -95,6 +95,13 @@ class MLState {
|
||||
* whose faces we are regenerating.
|
||||
*/
|
||||
inFlightFaceCropRegens = new Map<number, Promise<void>>();
|
||||
|
||||
/**
|
||||
* Cached object URLs to face crops that we have previously vended out.
|
||||
*
|
||||
* The cache is only cleared on logout.
|
||||
*/
|
||||
faceCropObjectURLCache = new Map<string, string>();
|
||||
}
|
||||
|
||||
/** State shared by the functions in this module. See {@link MLState}. */
|
||||
@@ -187,6 +194,9 @@ export const logoutML = async () => {
|
||||
// execution contexts], it gets called first in the logout sequence, and
|
||||
// then this function (`logoutML`) gets called at a later point in time.
|
||||
|
||||
[..._state.faceCropObjectURLCache.values()].forEach((url) =>
|
||||
URL.revokeObjectURL(url),
|
||||
);
|
||||
_state = new MLState();
|
||||
await clearMLDB();
|
||||
};
|
||||
@@ -646,7 +656,10 @@ export const getAnnotatedFacesForFile = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the cached face crop for the given face, regenerating it if needed.
|
||||
* Return a URL to the face crop for the given face, regenerating it if needed.
|
||||
*
|
||||
* The resultant URL is cached (both the object URL itself, and the underlying
|
||||
* file crop blob used to generete it).
|
||||
*
|
||||
* @param faceID The id of the face whose face crop we want.
|
||||
*
|
||||
@@ -662,8 +675,17 @@ export const faceCrop = async (faceID: string, enteFile: EnteFile) => {
|
||||
|
||||
await inFlight;
|
||||
|
||||
const cache = await blobCache("face-crops");
|
||||
return cache.get(faceID);
|
||||
let url = _state.faceCropObjectURLCache.get(faceID);
|
||||
if (!url) {
|
||||
const cache = await blobCache("face-crops");
|
||||
const blob = await cache.get(faceID);
|
||||
if (blob) {
|
||||
url = URL.createObjectURL(blob);
|
||||
if (url) _state.faceCropObjectURLCache.set(faceID, url);
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user