Skip to content

Commit

Permalink
Fix: Don't include broken files for missing images in Webmap export
Browse files Browse the repository at this point in the history
Switch to yazl instead of archiver for zip - library seems better implemented
  • Loading branch information
gmaclennan committed Nov 8, 2019
1 parent ef4eeab commit 5ec2554
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 45 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
"@material-ui/core": "^4.5.2",
"@material-ui/icons": "^4.5.1",
"@material-ui/styles": "^4.5.2",
"archiver": "^3.1.1",
"asar": "^1.0.0",
"clsx": "^1.0.4",
"core-js": "^3.3.5",
Expand All @@ -95,6 +94,7 @@
"mapeo-styles": "^3.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"once": "^1.4.0",
"osm-p2p": "^4.0.0",
"osm-p2p-server": "^5.0.0",
"pump": "^1.0.3",
Expand All @@ -105,12 +105,14 @@
"react-mapfilter": "^3.0.0-beta.12",
"react-transition-group": "^4.3.0",
"request": "^2.88.0",
"run-parallel-limit": "^1.0.5",
"run-series": "^1.1.4",
"safe-fs-blob-store": "^1.0.0",
"styled-components": "^3.4.4",
"subleveldown": "^3.0.1",
"tar-fs": "^1.16.0",
"through2": "^2.0.3"
"through2": "^2.0.3",
"yazl": "^2.5.1"
},
"devDependencies": {
"@babel/cli": "^7.6.0",
Expand Down
69 changes: 26 additions & 43 deletions src/renderer/components/MapFilter/MapExportDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import { TextField, DialogContentText } from '@material-ui/core'
import { defineMessages, useIntl, FormattedMessage } from 'react-intl'
import archiver from 'archiver'
import logger from 'electron-timber'
import fs from 'fs'
import path from 'path'
import request from 'request'
import { remote } from 'electron'
import pump from 'pump'

import ViewWrapper from 'react-mapfilter/commonjs/ViewWrapper'

import createZip from '../../create-zip'

const msgs = defineMessages({
// Title for webmaps export dialog
title: 'Export a map to share online',
Expand Down Expand Up @@ -79,58 +80,40 @@ const EditDialogContent = ({
path.basename(filepath, '.mapeomap') + '.mapeomap'
)
createArchive(filepathWithExtension, err => {
console.log('done', err)
if (err) {
logger.error('Failed to create archive', err)
} else {
logger.log('Successfully created map archive')
}
handleClose()
})
}
)

function createArchive (filepath, cb) {
var output = fs.createWriteStream(filepath)

var archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
})

output.on('end', function () {
console.log('Data has been drained')
})
const output = fs.createWriteStream(filepath)

// listen for all archive data to be written
// 'close' event is fired only when a file descriptor is involved
output.on('close', function () {
logger.log('Exported map ' + archive.pointer() + ' total bytes')
cb()
})

archive.on('warning', err => {
if (err.code === 'ENOENT') {
logger.warn(err)
} else {
cb(err)
const localFiles = [
{
data: JSON.stringify(points, null, 2),
metadataPath: 'points.json'
},
{
data: JSON.stringify(metadata, null, 2),
metadataPath: 'metadata.json'
}
})

archive.on('error', err => {
cb(err)
})

archive.pipe(output)
]

archive.append(JSON.stringify(points, null, 2), { name: 'points.json' })
archive.append(JSON.stringify(metadata, null, 2), {
name: 'metadata.json'
})
const remoteFiles = points.features
.filter(point => point.properties.image)
.map(point => ({
url: getMediaUrl(point.properties.image, 'original'),
metadataPath: 'images/' + point.properties.image
}))

points.features.forEach(point => {
const imageId = point.properties.image
if (!imageId) return
const imageStream = request(getMediaUrl(imageId, 'original'))
imageStream.on('error', console.error)
archive.append(imageStream, { name: 'images/' + imageId })
})
const archive = createZip(localFiles, remoteFiles)

archive.finalize()
pump(archive, output, cb)
}
}

Expand Down
66 changes: 66 additions & 0 deletions src/renderer/create-zip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import yazl from 'yazl'
import run from 'run-parallel-limit'
import ky from 'ky/umd'
import once from 'once'

const concurrency = 3

/**
* Create a zipfile from a collection of strings/buffers and remote files
* (remote files will be downloaded concurrently and streamed to the zipfile)
*
* @param {Array} localFiles Array of files to add to the zip archive. Must have
* properties `data` which should be a Buffer or String, and `name`
* which is the path to the file in the zipfile. Can also include options from
* https://github.com/thejoshwolfe/yazl#addfilerealpath-metadatapath-options
* @param {Array} remoteFiles Array of files to add to the zip archive. Must
* have properties `url` and `name` which is the path to the file in the
* zipfile. Can also include options from
* https://github.com/thejoshwolfe/yazl#addfilerealpath-metadatapath-options
* @returns {ReadableStream} readableStream of zipfile data
*/
export default function createZip (localFiles, remoteFiles) {
const zipfile = new yazl.ZipFile()
const missing = []

localFiles.forEach(({ data, metadataPath, ...options }) => {
if (typeof data === 'string') data = Buffer.from(data)
zipfile.addBuffer(data, metadataPath, options)
})

const tasks = remoteFiles.map(({ url, metadataPath, ...options }) => cb => {
cb = once(cb)
const start = Date.now()
console.log('Requesting', url)
// I tried doing this by adding streams to the zipfile, but it's really hard
// to catch errors when trying to download an image, so you end up with
// corrupt files in the zip. This uses a bit more memory, but images are
// only added once they have downloaded correctly
ky.get(url)
.arrayBuffer()
.then(arrBuf => {
console.log('Req end in ' + (Date.now() - start) + 'ms ' + metadataPath)
zipfile.addBuffer(Buffer.from(arrBuf), metadataPath, {
...options,
store: true
})
cb()
})
.catch(err => {
missing.push(metadataPath)
console.log('Error downloading file ' + metadataPath, err)
cb()
})
})
const start = Date.now()
console.log('Starting download')
run(tasks, concurrency, (...args) => {
console.log('Downloaded images in ' + (Date.now() - start) + 'ms')
if (missing.length) {
zipfile.addBuffer(Buffer.from(missing.join('\n') + '\n'), 'missing.txt')
}
zipfile.end()
})

return zipfile.outputStream
}

0 comments on commit 5ec2554

Please sign in to comment.