image-tagger/module/utils/fs.mjs

157 lines
3.8 KiB
JavaScript

import { config } from "../config.mjs";
import { __ID__, devMode, filePath } from "../consts.mjs";
const { fetchJsonWithTimeout } = foundry.utils;
/**
* Returns a pseudo-hash for a file. If the browser is in a secure
* context this will return a true SHA-1 hash of the file, otherwise
* a random ID is generated using the character set A-Za-z0-9.
*
* @param {File} file The file to hash
*/
export async function hashFile(file) {
// Handle fallback when hashing isn't possible
if (!window.isSecureContext || !crypto.subtle.digest) {
return foundry.utils.randomID(40);
};
// Actually hash the file since we can
const bytes = await file.arrayBuffer();
const buffer = await crypto.subtle.digest(`SHA-1`, bytes);
const intArray = new Uint8Array(buffer);
if (Uint8Array.prototype.toHex) {
return intArray.toHex();
};
return Array.from(intArray)
.map(b => b.toString(16).padStart(2, `0`))
.join(``);
};
/**
* Converts an image file into a webp file unless it is already a webp file or
* cannot be converted to a webp, in which case it doesn't modify the file at all.
*
* @param {File} file The file to convert
* @returns {Promise<File>}
*/
export async function convertToWebp(file) {
if (file.type === `image/webp`) { return null };
if (config.WEBP_IGNORE.includes(file.type)) { return null };
/** @type {HTMLImageElement} */
const image = document.createElement(`img`);
const url = URL.createObjectURL(file);
image.src = url;
await image.decode();
/** @type {HTMLCanvasElement} */
const canvas = document.createElement(`canvas`);
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
canvas.getContext(`2d`).drawImage(image, 0, 0);
return new Promise((resolve) => {
canvas.toBlob(
(blob) => {
const name = file.name.split(`.`).slice(0, -1).join(`.`);
const webp = new File([blob], `${name}.webp`, { type: blob.type });
resolve(webp);
},
`image/webp`,
config.WEBP_QUALITY,
);
});
};
export async function lastModifiedAt(path) {
try {
const response = await fetch(filePath(path), { method: `HEAD` });
return response.headers.get(`Last-Modified`);
} catch {
return null;
};
};
export async function getFileSize(path) {
try {
const response = await fetch(filePath(path), { method: `HEAD` });
const bytes = response.headers.get(`Content-Length`);
if (!bytes) { return null }
// round the non-bytes to 2 decimal places
const kilobytes = Math.floor(bytes / 10) / 100;
const megabytes = Math.floor(bytes / 10_000) / 100;
// determine the most appropriate unit to use
let friendly = `${bytes} bytes`;
if (megabytes > 0.25) {
friendly = `${megabytes}MB`;
} else if (kilobytes > 0) {
friendly = `${kilobytes}KB`;
};
return { bytes, kilobytes, megabytes, friendly };
} catch {
return null;
};
};
export async function getFile(path) {
try {
return fetchJsonWithTimeout(filePath(path));
} catch {
return undefined;
};
};
/**
* @param {string} path
* @param {any} data
*/
export async function uploadJson(path, filename, data) {
const content = JSON.stringify(
data,
undefined,
devMode() ? `\t` : undefined,
);
try {
const file = new File(
[content],
filename,
{ type: `text/plain` },
);
await uploadFile(path, file);
} catch {};
};
export async function uploadFile(path, file) {
// uploadPersistent adds "storage" into the path automatically
if (path.startsWith(`storage/`)) {
path = path.slice(8);
};
if (path.endsWith(`/`)) {
path = path.slice(0, -1);
};
const picker = foundry.applications.apps.FilePicker.implementation;
try {
await picker.uploadPersistent(
__ID__,
path,
file,
);
} catch {};
};
export function determineFileExtension(file) {
if (!file) return null;
for (const [short, long] of Object.entries(CONST.IMAGE_FILE_EXTENSIONS)) {
if (long === file.type) {
return short;
};
};
return null;
};