Add support for loading gedzip files

This commit is contained in:
Przemek Więch
2023-08-26 10:55:38 +02:00
parent 57dab46775
commit 05574f24ff
6 changed files with 96 additions and 33 deletions

View File

@@ -3,6 +3,7 @@ import {convertGedcom, getSoftware, TopolaData} from '../util/gedcom_util';
import {DataSource, DataSourceEnum, SourceSelection} from './data_source';
import {IndiInfo, JsonGedcomData} from 'topola';
import {TopolaError} from '../util/error';
import AdmZip from 'adm-zip';
/**
* Returns a valid IndiInfo object, either with the given indi and generation
@@ -36,6 +37,45 @@ function prepareData(
return data;
}
async function loadGedzip(
blob: Blob,
): Promise<{gedcom: string; images: Map<string, string>}> {
const zip = new AdmZip(Buffer.from(await blob.arrayBuffer()));
const entries = zip.getEntries();
let gedcom = undefined;
const images = new Map<string, string>();
for (const entry of entries) {
if (entry.entryName.endsWith('.ged')) {
if (gedcom) {
console.warn('Multiple GEDCOM files found in zip archive.');
} else {
gedcom = entry.getData().toString();
}
} else {
// Save image for later.
images.set(
entry.entryName,
URL.createObjectURL(new Blob([entry.getData()])),
);
}
}
if (!gedcom) {
throw new Error('GEDCOM file not found in zip archive.');
}
return {gedcom, images};
}
export async function loadFile(
blob: Blob,
): Promise<{gedcom: string; images: Map<string, string>}> {
const fileHeader = await blob.slice(0, 2).text();
if (fileHeader === 'PK') {
return loadGedzip(blob);
}
return {gedcom: await blob.text(), images: new Map()};
}
/** Fetches data from the given URL. Uses cors-anywhere if handleCors is true. */
export async function loadFromUrl(
url: string,
@@ -63,16 +103,15 @@ export async function loadFromUrl(
url = `https://drive.google.com/uc?id=${driveUrlMatch2[1]}&export=download`;
}
const urlToFetch = handleCors
? 'https://topolaproxy.bieda.it/' + url
: url;
const urlToFetch = handleCors ? 'https://topolaproxy.bieda.it/' + url : url;
const response = await window.fetch(urlToFetch);
if (response.status !== 200) {
throw new Error(response.statusText);
}
const gedcom = await response.text();
return prepareData(gedcom, url);
const {gedcom, images} = await loadFile(await response.blob());
return prepareData(gedcom, url, images);
}
/** Loads data from the given GEDCOM file contents. */

View File

@@ -6,16 +6,7 @@ import {FormattedMessage} from 'react-intl';
import {MenuType} from './menu_item';
import {SyntheticEvent} from 'react';
import {useHistory, useLocation} from 'react-router';
function loadFileAsText(file: File): Promise<string> {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = (evt: ProgressEvent) => {
resolve((evt.target as FileReader).result as string);
};
reader.readAsText(file);
});
}
import {loadFile} from '../datasource/load_data';
function isImageFileName(fileName: string) {
const lower = fileName.toLowerCase();
@@ -47,27 +38,18 @@ export function UploadMenu(props: Props) {
? filesArray[0]
: filesArray.find((file) => file.name.toLowerCase().endsWith('.ged')) ||
filesArray[0];
const {gedcom, images} = await loadFile(gedcomFile);
// Convert uploaded images to object URLs.
const images = filesArray
filesArray
.filter(
(file) => file.name !== gedcomFile.name && isImageFileName(file.name),
)
.map((file) => ({
name: file.name,
url: URL.createObjectURL(file),
}));
const imageMap = new Map(
images.map((entry) => [entry.name, entry.url] as [string, string]),
);
.forEach((file) => images.set(file.name, URL.createObjectURL(file)));
const data = await loadFileAsText(gedcomFile);
const imageFileNames = images
.map((image) => image.name)
.sort()
.join('|');
// Hash GEDCOM contents with uploaded image file names.
const hash = md5(md5(data) + imageFileNames);
const imageFileNames = Array.from(images.keys()).sort().join('|');
const hash = md5(md5(gedcom) + imageFileNames);
// Use history.replace() when reuploading the same file and history.push() when loading
// a new file.
@@ -77,7 +59,7 @@ export function UploadMenu(props: Props) {
historyPush({
pathname: '/view',
search: queryString.stringify({file: hash}),
state: {data, images: imageMap},
state: {data: gedcom, images},
});
}
@@ -101,7 +83,7 @@ export function UploadMenu(props: Props) {
<input
className="hidden"
type="file"
accept=".ged,image/*"
accept=".ged,.gdz,.gedzip,.zip,image/*"
id="fileInput"
multiple
onChange={handleUpload}

View File

@@ -210,9 +210,12 @@ function filterImage(indi: JsonIndi, images: Map<string, string>): JsonIndi {
}
const newImages: JsonImage[] = [];
indi.images.forEach((image) => {
const fileName = image.url.match(/[^/\\]*$/)![0];
const filePath = image.url.replaceAll('\\', '/');
const fileName = filePath.match(/[^/]*$/)![0];
// If the image file has been loaded into memory, use it.
if (images.has(fileName)) {
if (images.has(filePath)) {
newImages.push({url: images.get(filePath)!, title: image.title});
} else if (images.has(fileName)) {
newImages.push({url: images.get(fileName)!, title: image.title});
} else if (image.url.startsWith('http') && isImageFile(image.url)) {
newImages.push(image);