2021-01-23 06:00:44 -08:00
import {
IExecuteFunctions ,
} from 'n8n-core' ;
import {
IBinaryKeyData ,
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2021-01-23 06:00:44 -08:00
} from 'n8n-workflow' ;
import * as fflate from 'fflate' ;
import { promisify } from 'util' ;
const gunzip = promisify ( fflate . gunzip ) ;
const gzip = promisify ( fflate . gzip ) ;
const unzip = promisify ( fflate . unzip ) ;
const zip = promisify ( fflate . zip ) ;
import * as mime from 'mime-types' ;
const ALREADY_COMPRESSED = [
'7z' ,
'aifc' ,
'bz2' ,
'doc' ,
'docx' ,
'gif' ,
'gz' ,
'heic' ,
'heif' ,
'jpg' ,
'jpeg' ,
'mov' ,
'mp3' ,
'mp4' ,
'pdf' ,
'png' ,
'ppt' ,
'pptx' ,
'rar' ,
'webm' ,
'webp' ,
'xls' ,
'xlsx' ,
'zip' ,
] ;
export class Compression implements INodeType {
description : INodeTypeDescription = {
displayName : 'Compression' ,
name : 'compression' ,
icon : 'fa:file-archive' ,
group : [ 'transform' ] ,
subtitle : '={{$parameter["operation"]}}' ,
version : 1 ,
description : 'Compress and uncompress files' ,
defaults : {
name : 'Compression' ,
color : '#408000' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
properties : [
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'options' ,
2022-05-20 14:47:24 -07:00
noDataExpression : true ,
2021-01-23 06:00:44 -08:00
options : [
{
name : 'Compress' ,
value : 'compress' ,
} ,
{
name : 'Decompress' ,
value : 'decompress' ,
} ,
] ,
default : 'decompress' ,
} ,
{
displayName : 'Binary Property' ,
name : 'binaryPropertyName' ,
type : 'string' ,
default : 'data' ,
required : true ,
displayOptions : {
show : {
operation : [
'compress' ,
'decompress' ,
] ,
} ,
} ,
placeholder : '' ,
2022-04-22 09:29:51 -07:00
description : 'Name of the binary property which contains the data for the file(s) to be compress/decompress. Multiple can be used separated by a comma (,).' ,
2021-01-23 06:00:44 -08:00
} ,
{
displayName : 'Output Format' ,
name : 'outputFormat' ,
type : 'options' ,
default : '' ,
options : [
{
name : 'gzip' ,
value : 'gzip' ,
} ,
{
name : 'zip' ,
value : 'zip' ,
} ,
] ,
displayOptions : {
show : {
operation : [
'compress' ,
] ,
} ,
} ,
description : 'Format of the output file' ,
} ,
{
displayName : 'File Name' ,
name : 'fileName' ,
type : 'string' ,
default : '' ,
placeholder : 'data.zip' ,
required : true ,
displayOptions : {
show : {
operation : [
'compress' ,
] ,
outputFormat : [
'zip' ,
] ,
} ,
} ,
description : 'Name of the file to be compressed' ,
} ,
{
displayName : 'Binary Property Output' ,
name : 'binaryPropertyOutput' ,
type : 'string' ,
default : 'data' ,
displayOptions : {
show : {
outputFormat : [
'zip' ,
] ,
operation : [
'compress' ,
] ,
} ,
} ,
placeholder : '' ,
2022-05-06 14:01:25 -07:00
description : 'Name of the binary property to which to write the data of the compressed files' ,
2021-01-23 06:00:44 -08:00
} ,
{
displayName : 'Output Prefix' ,
name : 'outputPrefix' ,
type : 'string' ,
default : 'data' ,
required : true ,
displayOptions : {
show : {
operation : [
'compress' ,
] ,
outputFormat : [
'gzip' ,
] ,
} ,
} ,
description : 'Prefix use for all gzip compresed files' ,
} ,
{
displayName : 'Output Prefix' ,
name : 'outputPrefix' ,
type : 'string' ,
default : 'file_' ,
required : true ,
displayOptions : {
show : {
operation : [
'decompress' ,
] ,
} ,
} ,
description : 'Prefix use for all decompressed files' ,
} ,
] ,
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
2022-04-22 09:29:51 -07:00
const length = items . length ;
2021-01-23 06:00:44 -08:00
const returnData : INodeExecutionData [ ] = [ ] ;
const operation = this . getNodeParameter ( 'operation' , 0 ) as string ;
for ( let i = 0 ; i < length ; i ++ ) {
2021-07-19 23:58:54 -07:00
try {
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
if ( operation === 'decompress' ) {
const binaryPropertyNames = ( this . getNodeParameter ( 'binaryPropertyName' , 0 ) as string ) . split ( ',' ) . map ( key = > key . trim ( ) ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const outputPrefix = this . getNodeParameter ( 'outputPrefix' , 0 ) as string ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const binaryObject : IBinaryKeyData = { } ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
let zipIndex = 0 ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
for ( const [ index , binaryPropertyName ] of binaryPropertyNames . entries ( ) ) {
if ( items [ i ] . binary === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , 'No binary data exists on item!' ) ;
}
//@ts-ignore
if ( items [ i ] . binary [ binaryPropertyName ] === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , ` No binary data property " ${ binaryPropertyName } " does not exists on item! ` ) ;
}
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const binaryData = ( items [ i ] . binary as IBinaryKeyData ) [ binaryPropertyName ] ;
2021-08-20 09:08:40 -07:00
const binaryDataBuffer = await this . helpers . getBinaryDataBuffer ( i , binaryPropertyName ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
if ( binaryData . fileExtension === 'zip' ) {
2021-08-20 09:08:40 -07:00
const files = await unzip ( binaryDataBuffer ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
for ( const key of Object . keys ( files ) ) {
// when files are compresed using MACOSX for some reason they are duplicated under __MACOSX
if ( key . includes ( '__MACOSX' ) ) {
continue ;
}
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const data = await this . helpers . prepareBinaryData ( Buffer . from ( files [ key ] . buffer ) , key ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
binaryObject [ ` ${ outputPrefix } ${ zipIndex ++ } ` ] = data ;
}
} else if ( binaryData . fileExtension === 'gz' ) {
2021-08-20 09:08:40 -07:00
const file = await gunzip ( binaryDataBuffer ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const fileName = binaryData . fileName ? . split ( '.' ) [ 0 ] ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const propertyName = ` ${ outputPrefix } ${ index } ` ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
binaryObject [ propertyName ] = await this . helpers . prepareBinaryData ( Buffer . from ( file . buffer ) , fileName ) ;
const fileExtension = mime . extension ( binaryObject [ propertyName ] . mimeType ) as string ;
binaryObject [ propertyName ] . fileName = ` ${ fileName } . ${ fileExtension } ` ;
binaryObject [ propertyName ] . fileExtension = fileExtension ;
}
2021-01-23 06:00:44 -08:00
}
2021-07-19 23:58:54 -07:00
returnData . push ( {
json : items [ i ] . json ,
binary : binaryObject ,
} ) ;
}
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
if ( operation === 'compress' ) {
const binaryPropertyNames = ( this . getNodeParameter ( 'binaryPropertyName' , 0 ) as string ) . split ( ',' ) . map ( key = > key . trim ( ) ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const outputFormat = this . getNodeParameter ( 'outputFormat' , 0 ) as string ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const zipData : fflate.Zippable = { } ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const binaryObject : IBinaryKeyData = { } ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
for ( const [ index , binaryPropertyName ] of binaryPropertyNames . entries ( ) ) {
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
if ( items [ i ] . binary === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , 'No binary data exists on item!' ) ;
}
//@ts-ignore
if ( items [ i ] . binary [ binaryPropertyName ] === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , ` No binary data property " ${ binaryPropertyName } " does not exists on item! ` ) ;
}
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const binaryData = ( items [ i ] . binary as IBinaryKeyData ) [ binaryPropertyName ] ;
2021-08-20 09:08:40 -07:00
const binaryDataBuffer = await this . helpers . getBinaryDataBuffer ( i , binaryPropertyName ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
if ( outputFormat === 'zip' ) {
zipData [ binaryData . fileName as string ] = [
2021-08-20 09:08:40 -07:00
binaryDataBuffer , {
2021-07-19 23:58:54 -07:00
level : ALREADY_COMPRESSED.includes ( binaryData . fileExtension as string ) ? 0 : 6 ,
} ,
] ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
} else if ( outputFormat === 'gzip' ) {
const outputPrefix = this . getNodeParameter ( 'outputPrefix' , 0 ) as string ;
2021-01-23 06:00:44 -08:00
2021-08-20 09:08:40 -07:00
const data = await gzip ( binaryDataBuffer ) as Uint8Array ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const fileName = binaryData . fileName ? . split ( '.' ) [ 0 ] ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
binaryObject [ ` ${ outputPrefix } ${ index } ` ] = await this . helpers . prepareBinaryData ( Buffer . from ( data ) , ` ${ fileName } .gzip ` ) ;
}
2021-01-23 06:00:44 -08:00
}
2021-07-19 23:58:54 -07:00
if ( outputFormat === 'zip' ) {
const fileName = this . getNodeParameter ( 'fileName' , 0 ) as string ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const binaryPropertyOutput = this . getNodeParameter ( 'binaryPropertyOutput' , 0 ) as string ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const buffer = await zip ( zipData ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
const data = await this . helpers . prepareBinaryData ( Buffer . from ( buffer ) , fileName ) ;
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
returnData . push ( {
json : items [ i ] . json ,
binary : {
[ binaryPropertyOutput ] : data ,
} ,
} ) ;
}
2021-01-23 06:00:44 -08:00
2021-07-19 23:58:54 -07:00
if ( outputFormat === 'gzip' ) {
returnData . push ( {
json : items [ i ] . json ,
binary : binaryObject ,
} ) ;
}
}
2021-08-20 09:08:40 -07:00
2021-07-19 23:58:54 -07:00
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
2021-08-20 09:08:40 -07:00
returnData . push ( { json : { error : error.message } } ) ;
2021-07-19 23:58:54 -07:00
continue ;
2021-01-23 06:00:44 -08:00
}
2021-07-19 23:58:54 -07:00
throw error ;
2021-01-23 06:00:44 -08:00
}
}
return this . prepareOutputData ( returnData ) ;
}
}