Passed
Push — master ( b034ab...4274b4 )
by Jan
01:50 queued 11s
created

index.js   F

Complexity

Total Complexity 65
Complexity/F 1.86

Size

Lines of Code 331
Function Count 35

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 65
eloc 175
mnd 30
bc 30
fnc 35
dl 0
loc 331
rs 3.2
bpm 0.8571
cpm 1.8571
noi 5
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
A ➔ removeTagsFromBuffer 0 22 4
A ➔ writeSync 0 14 3
A ➔ readAsync 0 13 4
A ➔ readSync 0 6 2
A ➔ writeInBuffer 0 4 1
A ➔ read 0 10 3
A ➔ write 0 8 2
B ➔ writeAsync 0 20 6
A ➔ create 0 19 2
B ➔ removeTagsAsync 0 22 6
D ➔ update 0 50 13
B ➔ makePromise 0 14 6
A ➔ removeTagsSync 0 21 4
A ➔ makeSwapParameters 0 3 2
A ➔ removeTags 0 6 2

How to fix   Complexity   

Complexity

Complex classes like index.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
const fs = require('fs')
2
const ID3Definitions = require("./src/ID3Definitions")
3
const ID3Util = require('./src/ID3Util')
4
const ID3Helpers = require('./src/ID3Helpers')
5
const { isFunction, isString } = require('./src/util')
6
7
/*
8
**  Used specification: http://id3.org/id3v2.3.0
9
*/
10
11
/**
12
 * Checks and removes already written ID3-Frames from a buffer
13
 * @param {Buffer} data
14
 * @returns {boolean|Buffer}
15
 */
16
function removeTagsFromBuffer(data) {
17
    const framePosition = ID3Util.getFramePosition(data)
18
19
    if (framePosition === -1) {
20
        return data
21
    }
22
23
    const encodedSize = data.slice(framePosition + 6, framePosition + 10)
24
    if (!ID3Util.isValidEncodedSize(encodedSize)) {
25
        return false
26
    }
27
28
    if (data.length >= framePosition + 10) {
29
        const size = ID3Util.decodeSize(encodedSize)
30
        return Buffer.concat([
31
            data.slice(0, framePosition),
32
            data.slice(framePosition + size + 10)
33
        ])
34
    }
35
36
    return data
37
}
38
39
function writeInBuffer(tags, buffer) {
40
    buffer = removeTagsFromBuffer(buffer) || buffer
41
    return Buffer.concat([tags, buffer])
42
}
43
44
function writeAsync(tags, filebuffer, fn) {
45
    if(isString(filebuffer)) {
46
        try {
47
            fs.readFile(filebuffer, (error, data) => {
48
                if(error) {
49
                    fn(error)
50
                    return
51
                }
52
                const newData = writeInBuffer(tags, data)
53
                fs.writeFile(filebuffer, newData, 'binary', (error) => {
54
                    fn(error)
55
                })
56
            })
57
        } catch(error) {
58
            fn(error)
59
        }
60
    } else {
61
        fn(null, writeInBuffer(tags, filebuffer))
62
    }
63
}
64
65
function writeSync(tags, filebuffer) {
66
    if(isString(filebuffer)) {
67
        try {
68
            const data = fs.readFileSync(filebuffer)
69
            const newData = writeInBuffer(tags, data)
70
            fs.writeFileSync(filebuffer, newData, 'binary')
71
            return true
72
        } catch(error) {
73
            return error
74
        }
75
    }
76
77
    return writeInBuffer(tags, filebuffer)
78
}
79
80
/**
81
 * Write passed tags to a file/buffer
82
 * @param tags - Object containing tags to be written
83
 * @param filebuffer - Can contain a filepath string or buffer
84
 * @param fn - (optional) Function for async version
85
 * @returns {boolean|Buffer|Error}
86
 */
87
function write(tags, filebuffer, fn) {
88
    const completeTags = create(tags)
89
90
    if(isFunction(fn)) {
91
        return writeAsync(completeTags, filebuffer, fn)
92
    }
93
    return writeSync(completeTags, filebuffer)
94
}
95
96
/**
97
 * Creates a buffer containing the ID3 Tag
98
 * @param tags - Object containing tags to be written
99
 * @param fn fn - (optional) Function for async version
100
 * @returns {Buffer}
101
 */
102
function create(tags, fn) {
103
    const frames = ID3Helpers.createBufferFromTags(tags)
104
105
    //  Create ID3 header
106
    const header = Buffer.alloc(10)
107
    header.fill(0)
108
    header.write("ID3", 0)              //File identifier
109
    header.writeUInt16BE(0x0300, 3)     //Version 2.3.0  --  03 00
110
    header.writeUInt16BE(0x0000, 5)     //Flags 00
111
    ID3Util.encodeSize(frames.length).copy(header, 6)
112
113
    const id3Data = Buffer.concat([header, frames])
114
115
    if(isFunction(fn)) {
116
        fn(id3Data)
117
        return undefined
118
    }
119
    return id3Data
120
}
121
122
function readSync(filebuffer, options) {
123
    if(isString(filebuffer)) {
124
        filebuffer = fs.readFileSync(filebuffer)
125
    }
126
    return ID3Helpers.getTagsFromBuffer(filebuffer, options)
127
}
128
129
function readAsync(filebuffer, options, fn) {
130
    if(isString(filebuffer)) {
131
        fs.readFile(filebuffer, (error, data) => {
132
            if(error) {
133
                fn(error, null)
134
            } else {
135
                fn(null, ID3Helpers.getTagsFromBuffer(data, options))
136
            }
137
        })
138
    } else {
139
        fn(null, ID3Helpers.getTagsFromBuffer(filebuffer, options))
140
    }
141
}
142
143
/**
144
 * Read ID3-Tags from passed buffer/filepath
145
 * @param filebuffer - Can contain a filepath string or buffer
146
 * @param options - (optional) Object containing options
147
 * @param fn - (optional) Function for async version
148
 * @returns {boolean}
149
 */
150
function read(filebuffer, options, fn) {
151
    if(!options || typeof options === 'function') {
152
        fn = fn || options
153
        options = {}
154
    }
155
    if(isFunction(fn)) {
156
        return readAsync(filebuffer, options, fn)
157
    }
158
    return readSync(filebuffer, options)
159
}
160
161
/**
162
 * Update ID3-Tags from passed buffer/filepath
163
 * @param tags - Object containing tags to be written
164
 * @param filebuffer - Can contain a filepath string or buffer
165
 * @param options - (optional) Object containing options
166
 * @param fn - (optional) Function for async version
167
 * @returns {boolean|Buffer|Error}
168
 */
169
function update(tags, filebuffer, options, fn) {
170
    if(!options || typeof options === 'function') {
171
        fn = fn || options
172
        options = {}
173
    }
174
175
    const rawTags = Object.keys(tags).reduce((acc, val) => {
176
        if(ID3Definitions.FRAME_IDENTIFIERS.v3[val] !== undefined) {
177
            acc[ID3Definitions.FRAME_IDENTIFIERS.v3[val]] = tags[val]
178
        } else {
179
            acc[val] = tags[val]
180
        }
181
        return acc
182
    }, {})
183
184
    const updateFn = (currentTags) => {
185
        currentTags = currentTags.raw || {}
186
        Object.keys(rawTags).map((frameIdentifier) => {
187
            const options = ID3Util.getSpecOptions(frameIdentifier, 3)
188
            const cCompare = {}
189
            if(options.multiple && currentTags[frameIdentifier] && rawTags[frameIdentifier]) {
190
                if(options.updateCompareKey) {
191
                    currentTags[frameIdentifier].forEach((cTag, index) => {
192
                        cCompare[cTag[options.updateCompareKey]] = index
193
                    })
194
195
                }
196
                if (!(rawTags[frameIdentifier] instanceof Array)) {
197
                    rawTags[frameIdentifier] = [rawTags[frameIdentifier]]
198
                }
199
                rawTags[frameIdentifier].forEach((rTag) => {
200
                    const comparison = cCompare[rTag[options.updateCompareKey]]
201
                    if (comparison !== undefined) {
202
                        currentTags[frameIdentifier][comparison] = rTag
203
                    } else {
204
                        currentTags[frameIdentifier].push(rTag)
205
                    }
206
                })
207
            } else {
208
                currentTags[frameIdentifier] = rawTags[frameIdentifier]
209
            }
210
        })
211
        return currentTags
212
    }
213
214
    if(isFunction(fn)) {
215
        return write(updateFn(read(filebuffer, options)), filebuffer, fn)
216
    }
217
    return write(updateFn(read(filebuffer, options)), filebuffer)
218
}
219
220
/**
221
 * @param {string} filepath - Filepath to file
222
 * @returns {boolean|Error}
223
 */
224
function removeTagsSync(filepath) {
225
    let data
226
    try {
227
        data = fs.readFileSync(filepath)
228
    } catch(error) {
229
        return error
230
    }
231
232
    const newData = removeTagsFromBuffer(data)
233
    if(!newData) {
234
        return false
235
    }
236
237
    try {
238
        fs.writeFileSync(filepath, newData, 'binary')
239
    } catch(error) {
240
        return error
241
    }
242
243
    return true
244
}
245
246
/**
247
 * @param {string} filepath - Filepath to file
248
 * @param {(error: Error) => void} fn - Function for async usage
249
 * @returns {void}
250
 */
251
function removeTagsAsync(filepath, fn) {
252
    fs.readFile(filepath, (error, data) => {
253
        if(error) {
254
            fn(error)
255
            return
256
        }
257
258
        const newData = removeTagsFromBuffer(data)
259
        if(!newData) {
260
            fn(error)
261
            return
262
        }
263
264
        fs.writeFile(filepath, newData, 'binary', (error) => {
265
            if(error) {
266
                fn(error)
267
            } else {
268
                fn(null)
269
            }
270
        })
271
    })
272
}
273
274
/**
275
 * Checks and removes already written ID3-Frames from a file
276
 * @param {string} filepath - Filepath to file
277
 * @param fn - (optional) Function for async usage
278
 * @returns {boolean|Error}
279
 */
280
function removeTags(filepath, fn) {
281
    if(isFunction(fn)) {
282
        return removeTagsAsync(filepath, fn)
283
    }
284
    return removeTagsSync(filepath)
285
}
286
287
function makeSwapParameters(fn) {
288
    return (a, b) => fn(b, a)
289
}
290
291
// The reorderParameter is a workaround because the callback function
292
// does not have a consistent interface between all the API functions.
293
// Ideally, all the functions should align with the promise style and
294
// always have the result first and the error second.
295
// Changing this would break the current public API.
296
// This could be changed internally and swap the parameter in a light
297
// wrapper when creating the public interface and then remove in a
298
// version 1.0 later with an API breaking change.
299
function makePromise(
300
    fn,
301
    reorderParameters = fn => (a, b) => fn(a, b)
302
) {
303
    return new Promise((resolve, reject) => {
304
        fn(reorderParameters((error, result) => {
305
            if(error) {
306
                reject(error)
307
            } else {
308
                resolve(result)
309
            }
310
        }))
311
    })
312
}
313
314
const PromiseExport = {
315
    create: (tags) => makePromise(create.bind(null, tags), makeSwapParameters),
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function create declared on line 102 does not use this.
Loading history...
316
    write: (tags, file) => makePromise(write.bind(null, tags, file)),
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function write declared on line 87 does not use this.
Loading history...
317
    update: (tags, file, options) => makePromise(update.bind(null, tags, file, options)),
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function update declared on line 169 does not use this.
Loading history...
318
    read: (file, options) => makePromise(read.bind(null, file, options)),
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function read declared on line 150 does not use this.
Loading history...
319
    removeTags: (filepath) => makePromise(removeTags.bind(null, filepath))
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function removeTags declared on line 280 does not use this.
Loading history...
320
}
321
322
module.exports = {
323
    TagConstants: ID3Definitions.TagConstants,
324
    create,
325
    write,
326
    update,
327
    read,
328
    removeTags,
329
    removeTagsFromBuffer,
330
    Promise: PromiseExport
331
}
332