diff --git a/package-lock.json b/package-lock.json index 42749f886..be169470d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -803,6 +803,43 @@ } } }, + "archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "requires": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + } + } + }, "are-we-there-yet": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", @@ -1780,6 +1817,24 @@ "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", "dev": true }, + "compress-commons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", + "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", + "requires": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^3.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1898,6 +1953,35 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "crc32-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", + "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", + "requires": { + "crc": "^3.4.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -5614,6 +5698,16 @@ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", "dev": true }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" + }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -5622,6 +5716,11 @@ "lodash._root": "^3.0.0" } }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -5648,6 +5747,11 @@ "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=" }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -5693,6 +5797,11 @@ "lodash.escape": "^3.0.0" } }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -10310,6 +10419,28 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "zip-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", + "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", + "requires": { + "archiver-utils": "^2.1.0", + "compress-commons": "^2.1.1", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } } } } diff --git a/package.json b/package.json index 1e5716176..7a05a6064 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,8 @@ "turndown": "6.0.0", "turndown-plugin-gfm": "1.0.2", "unescape": "1.0.1", - "ws": "7.2.3" + "ws": "7.2.3", + "zip-stream": "^2.1.3" }, "devDependencies": { "electron": "9.0.0-beta.9", diff --git a/src/routes/api/export.js b/src/routes/api/export.js index b38be0041..0a42606dd 100644 --- a/src/routes/api/export.js +++ b/src/routes/api/export.js @@ -1,6 +1,6 @@ "use strict"; -const tarExportService = require('../../services/export/tar'); +const zipExportService = require('../../services/export/tar'); const singleExportService = require('../../services/export/single'); const opmlExportService = require('../../services/export/opml'); const repository = require("../../services/repository"); @@ -15,7 +15,7 @@ async function exportBranch(req, res) { try { if (type === 'subtree' && (format === 'html' || format === 'markdown')) { - await tarExportService.exportToTar(taskContext, branch, format, res); + await zipExportService.exportToZip(taskContext, branch, format, res); } else if (type === 'single') { await singleExportService.exportSingleNote(taskContext, branch, format, res); diff --git a/src/services/export/tar.js b/src/services/export/zip.js similarity index 89% rename from src/services/export/tar.js rename to src/services/export/zip.js index f379a712c..8107a1fe5 100644 --- a/src/services/export/tar.js +++ b/src/services/export/zip.js @@ -3,7 +3,7 @@ const html = require('html'); const repository = require('../repository'); const dateUtils = require('../date_utils'); -const tar = require('tar-stream'); +const zip = require('tar-stream'); const path = require('path'); const mimeTypes = require('mime-types'); const mdService = require('./md'); @@ -13,17 +13,30 @@ const protectedSessionService = require('../protected_session'); const sanitize = require("sanitize-filename"); const fs = require("fs"); const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; +const ZipStream = require('zip-stream'); /** * @param {TaskContext} taskContext * @param {Branch} branch * @param {string} format - 'html' or 'markdown' */ -async function exportToTar(taskContext, branch, format, res) { - const pack = tar.pack(); +async function exportToZip(taskContext, branch, format, res) { + const packer = new ZipStream(); const noteIdToMeta = {}; + async function addEntry(source, data) { + return new Promise((res, rej) => { + packer.entry(source, data, (err, entry) => { + if (err) { + rej(err); + } + + res(entry); + }) + }); + } + function getUniqueFilename(existingFileNames, fileName) { const lcFileName = fileName.toLowerCase(); @@ -265,7 +278,8 @@ ${content} content = prepareContent(noteMeta.title, content, noteMeta); - pack.entry({name: filePathPrefix + noteMeta.dataFileName, size: content.length}, content); + await addEntry(content, {name: filePathPrefix + noteMeta.dataFileName}); + return; } @@ -276,11 +290,10 @@ ${content} if (noteMeta.dataFileName) { const content = prepareContent(noteMeta.title, await note.getContent(), noteMeta); - pack.entry({ + await addEntry(content, { name: filePathPrefix + noteMeta.dataFileName, - size: content.length, - mtime: dateUtils.parseDateTime(note.utcDateModified) - }, content); + date: dateUtils.parseDateTime(note.utcDateModified) + }); } taskContext.increaseProgressCount(); @@ -288,10 +301,10 @@ ${content} if (noteMeta.children && noteMeta.children.length > 0) { const directoryPath = filePathPrefix + noteMeta.dirFileName; - pack.entry({ + await addEntry(null,{ name: directoryPath, type: 'directory', - mtime: dateUtils.parseDateTime(note.utcDateModified) + date: dateUtils.parseDateTime(note.utcDateModified) }); for (const childMeta of noteMeta.children) { @@ -300,7 +313,7 @@ ${content} } } - function saveNavigation(rootMeta, navigationMeta) { + async function saveNavigation(rootMeta, navigationMeta) { function saveNavigationInner(meta) { let html = '
  • '; @@ -339,10 +352,10 @@ ${content} `; const prettyHtml = html.prettyPrint(fullHtml, {indent_size: 2}); - pack.entry({name: navigationMeta.dataFileName, size: prettyHtml.length}, prettyHtml); + await addEntry(prettyHtml, {name: navigationMeta.dataFileName}); } - function saveIndex(rootMeta, indexMeta) { + async function saveIndex(rootMeta, indexMeta) { let firstNonEmptyNote; let curMeta = rootMeta; @@ -370,13 +383,13 @@ ${content} `; - pack.entry({name: indexMeta.dataFileName, size: fullHtml.length}, fullHtml); + await addEntry(fullHtml, {name: indexMeta.dataFileName}); } - function saveCss(rootMeta, cssMeta) { + async function saveCss(rootMeta, cssMeta) { const cssContent = fs.readFileSync(RESOURCE_DIR + '/libraries/ckeditor/ckeditor-content.css'); - pack.entry({name: cssMeta.dataFileName, size: cssContent.length}, cssContent); + await addEntry(cssContent, {name: cssMeta.dataFileName}); } const existingFileNames = format === 'html' ? ['navigation', 'index'] : []; @@ -425,29 +438,29 @@ ${content} const metaFileJson = JSON.stringify(metaFile, null, '\t'); - pack.entry({name: "!!!meta.json", size: metaFileJson.length}, metaFileJson); + await addEntry(metaFileJson, {name: "!!!meta.json"}); await saveNote(rootMeta, ''); if (format === 'html') { - saveNavigation(rootMeta, navigationMeta); - saveIndex(rootMeta, indexMeta); - saveCss(rootMeta, cssMeta); + await saveNavigation(rootMeta, navigationMeta); + await saveIndex(rootMeta, indexMeta); + await saveCss(rootMeta, cssMeta); } - pack.finalize(); + packer.finalize(); const note = await branch.getNote(); - const tarFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".tar"; + const zipFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".zip"; - res.setHeader('Content-Disposition', utils.getContentDisposition(tarFileName)); - res.setHeader('Content-Type', 'application/tar'); + res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName)); + res.setHeader('Content-Type', 'application/zip'); - pack.pipe(res); + packer.pipe(res); taskContext.taskSucceeded(); } module.exports = { - exportToTar + exportToZip }; \ No newline at end of file diff --git a/src/views/dialogs/export.ejs b/src/views/dialogs/export.ejs index 5b0ed94c0..4c09a2213 100644 --- a/src/views/dialogs/export.ejs +++ b/src/views/dialogs/export.ejs @@ -18,7 +18,7 @@
    - +