UNPKG

@mieweb/wikigdrive

Version:

Google Drive to MarkDown synchronization

270 lines (240 loc) 10.5 kB
import winston from 'winston'; import {QueueTask} from '../google_folder/QueueTask.ts'; import {FileContentService} from '../../utils/FileContentService.ts'; import {GoogleFile, MimeTypes} from '../../model/GoogleFile.ts'; import {BinaryFile, DrawingFile, LocalFile, MdFile} from '../../model/LocalFile.ts'; import {SvgTransform} from '../../SvgTransform.ts'; import {generateDocumentFrontMatter} from './frontmatters/generateDocumentFrontMatter.ts'; import {generateConflictMarkdown} from './frontmatters/generateConflictMarkdown.ts'; import {OdtProcessor} from '../../odt/OdtProcessor.ts'; import {UnMarshaller} from '../../odt/UnMarshaller.ts'; import {DocumentStyles, LIBREOFFICE_CLASSES} from '../../odt/LibreOffice.ts'; import {OdtToMarkdown} from '../../odt/OdtToMarkdown.ts'; import {LocalLinks} from './LocalLinks.ts'; import {SINGLE_THREADED_TRANSFORM} from './QueueTransformer.ts'; import {JobManagerContainer} from '../job/JobManagerContainer.ts'; import {UserConfig} from '../google_folder/UserConfigService.ts'; export function googleMimeToExt(mimeType: string, fileName: string) { switch (mimeType) { case MimeTypes.APPS_SCRIPT: return 'gs'; case 'image/jpeg': return 'jpg'; case 'image/png': return 'png'; case 'image/svg+xml': return 'svg'; case 'application/vnd.google-apps.drawing': return 'svg'; case 'application/vnd.google-apps.document': return 'odt'; case 'text/csv': return 'csv'; } if (fileName.indexOf('.') > -1) { return ''; } return 'bin'; } export class TaskLocalFileTransform extends QueueTask { constructor(protected logger: winston.Logger, private jobManagerContainer: JobManagerContainer, private realFileName: string, private googleFolder: FileContentService, private googleFile: GoogleFile, private destinationDirectory: FileContentService, private localFile: LocalFile, private localLinks: LocalLinks, private userConfig: UserConfig, private globalHeadersMap: {[key: string]: string}, private globalInvisibleBookmarks: {[key: number]: number}, ) { super(logger); this.retries = 0; if (!this.localFile.fileName) { throw new Error(`No fileName for: ${this.localFile.id}`); } } async run(): Promise<QueueTask[]> { await this.generate(this.localFile); return []; } async generateBinary(binaryFile: BinaryFile) { await new Promise<void>((resolve, reject) => { try { const dest = this.destinationDirectory.createWriteStream(this.realFileName); dest.on('error', err => { reject(err); }); const ext = googleMimeToExt(this.googleFile.mimeType, this.googleFile.name); const stream = this.googleFolder.createReadStream(`${binaryFile.id}${ext ? '.' + ext : ''}`) .on('error', err => { reject(err); }) .pipe(dest); stream.on('finish', () => { resolve(); }); stream.on('error', err => { reject(err); }); } catch (err) { reject(err); } }); } async generateDrawing(drawingFile: DrawingFile) { await new Promise<void>((resolve, reject) => { try { // await this.destinationDirectory.mkdir(getFileDir(targetPath)); const dest = this.destinationDirectory.createWriteStream(this.realFileName); const svgTransform = new SvgTransform(drawingFile.fileName); // const svgPath = this.googleScanner.getFilePathPrefix(drawingFile.id) + '.svg'; dest.on('error', err => { reject(err); }); const stream = this.googleFolder.createReadStream(`${drawingFile.id}.svg`) .on('error', err => { reject(err); }) .pipe(svgTransform) .pipe(dest); stream.on('finish', () => { this.localLinks.append(drawingFile.id, drawingFile.fileName, Array.from(svgTransform.links)); resolve(); }); stream.on('error', err => { reject(err); }); } catch (err) { reject(err); } }); } async generateDocument(localFile: MdFile) { let frontMatter; let markdown; let headersMap = {}; let invisibleBookmarks = {}; let links = []; let errors = []; const odtPath = this.googleFolder.getRealPath() + '/' + localFile.id + '.odt'; const destinationPath = this.destinationDirectory.getRealPath(); const rewriteRules = this.userConfig.rewrite_rules || []; const picturesDirAbsolute = destinationPath + '/' + this.realFileName.replace(/.md$/, '.assets/'); if (SINGLE_THREADED_TRANSFORM) { const processor = new OdtProcessor(true); await processor.load(odtPath); await processor.unzipAssets(destinationPath, this.realFileName); const content = processor.getContentXml(); const stylesXml = processor.getStylesXml(); const fileNameMap = processor.getFileNameMap(); const xmlMap = processor.getXmlMap(); const parser = new UnMarshaller(LIBREOFFICE_CLASSES, 'DocumentContent'); const document = parser.unmarshal(content); const parserStyles = new UnMarshaller(LIBREOFFICE_CLASSES, 'DocumentStyles'); const styles: DocumentStyles = parserStyles.unmarshal(stylesXml); if (!styles) { throw Error('No styles unmarshalled'); } const converter = new OdtToMarkdown(document, styles, fileNameMap, xmlMap); converter.setRewriteRules(rewriteRules); if (this.realFileName === '_index.md') { converter.setPicturesDir('./' + this.realFileName.replace(/.md$/, '.assets/'), picturesDirAbsolute); } else { converter.setPicturesDir('../' + this.realFileName.replace(/.md$/, '.assets/'), picturesDirAbsolute); } headersMap = converter.getHeadersMap(); invisibleBookmarks = converter.getInvisibleBookmarks(); markdown = await converter.convert(); links = Array.from(converter.links); const frontMatterOverload: Record<string, string> = {}; if (markdown.match(/^ *A. {2}/igm)) { frontMatterOverload['markup'] = 'pandoc'; } frontMatter = generateDocumentFrontMatter(localFile, links, this.userConfig.fm_without_version, frontMatterOverload); errors = converter.getErrors(); this.warnings = errors.length; } else { interface WorkerResult { links: Array<string>; frontMatter: string; markdown: string; errors: Array<string>; headersMap: {[key: string]: string}; invisibleBookmarks: {[key: string]: string}; } const workerResult: WorkerResult = <WorkerResult>await this.jobManagerContainer.scheduleWorker('OdtToMarkdown', { localFile, realFileName: this.realFileName, picturesDirAbsolute, odtPath, destinationPath, rewriteRules, fm_without_version: this.userConfig.fm_without_version }); links = workerResult.links; frontMatter = workerResult.frontMatter; markdown = workerResult.markdown; errors = workerResult.errors; headersMap = workerResult.headersMap; invisibleBookmarks = workerResult.invisibleBookmarks; this.warnings = errors.length; } for (const errorMsg of errors) { this.logger.warn('Error in: ['+ this.localFile.fileName +'](' + this.localFile.fileName + ') ' + errorMsg, { errorMdFile: this.localFile.fileName, errorMdMsg: errorMsg }); } await this.destinationDirectory.writeFile(this.realFileName, frontMatter + markdown); this.localLinks.append(localFile.id, localFile.fileName, links); for (const k in headersMap) { this.globalHeadersMap['gdoc:' + localFile.id + k] = 'gdoc:' + localFile.id + headersMap[k]; } for (const k in invisibleBookmarks) { this.globalInvisibleBookmarks['gdoc:' + localFile.id + k] = invisibleBookmarks[k]; } } async generate(localFile: LocalFile): Promise<void> { try { const verStr = this.localFile.version ? ' #' + this.localFile.version : ' '; if (localFile.type === 'conflict') { this.logger.info('Transforming conflict: ' + this.localFile.fileName); const md = generateConflictMarkdown(localFile); await this.destinationDirectory.writeFile(this.realFileName, md); } else if (localFile.type === 'redir') { // TODO this.logger.info('Transforming redir: ' + this.localFile.fileName); // const redirectToFile = this.toGenerate.find(f => f.id === localFile.redirectTo); // const redirectToFile = this.generatedScanner.getFileById(localFile.redirectTo); // const md = generateRedirectMarkdown(localFile, redirectToFile, this.linkTranslator); // await this.destinationDirectory.mkdir(getFileDir(targetPath)); // await this.destinationDirectory.writeFile(targetPath, md); // await this.generatedScanner.update(targetPath, md); } else if (localFile.type === 'md') { this.logger.info('Transforming markdown: ' + this.localFile.fileName + verStr); // const googleFile = await this.googleScanner.getFileById(localFile.id); // const downloadFile = await this.downloadFilesStorage.findFile(f => f.id === localFile.id); if (this.googleFile) { // && downloadFile await this.generateDocument(localFile); } } else if (localFile.type === 'drawing') { this.logger.info('Transforming drawing: ' + this.localFile.fileName + verStr); // const googleFile = await this.googleScanner.getFileById(localFile.id); // const downloadFile = await this.downloadFilesStorage.findFile(f => f.id === localFile.id); if (this.googleFile) { // && downloadFile await this.generateDrawing(localFile); } } else if (localFile.type === 'binary') { this.logger.info('Transforming binary: ' + this.localFile.fileName + verStr); if (this.googleFile) { // && downloadFile await this.generateBinary(localFile); } } this.logger.info('Transformed: ' + this.localFile.fileName + verStr); } catch (err) { this.logger.error('Error transforming ' + localFile.fileName + ' ' + err.stack ? err.stack : err.message); throw err; } } }