Passed
Pull Request — master (#135)
by
unknown
01:40
created

index.js   F

Complexity

Total Complexity 65
Complexity/F 1.86

Size

Lines of Code 344
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 344
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 ➔ writeInBuffer 0 4 1
A ➔ write 0 8 2
B ➔ writeAsync 0 20 6
A ➔ create 0 19 2
B ➔ removeTagsAsync 0 22 6
A ➔ readAsync 0 13 4
D ➔ update 0 50 13
A ➔ readSync 0 6 2
B ➔ makePromise 0 14 6
A ➔ removeTagsSync 0 21 4
A ➔ read 0 10 3
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
 * Check and remove already written ID3-Frames from a buffer
13
 * @param {Buffer} data
14
 * @returns {false|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 {object} tags - Object containing tags to be written
83
 * @param {string|Buffer} filebuffer - Filepath or buffer
84
 * @param {WriteCallback} [fn] - Function for async version
85
 * @returns {boolean|Buffer|Error|void}
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
 * Create a buffer containing the ID3 Tag
98
 * @param {object} tags - Object containing tags to be written
99
 * @param {CreateCallback} [fn] - Function for async version
100
 * @returns {Buffer|undefined}
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
/**
123
 * Read ID3-Tags from passed buffer/filepath
124
 * @param {string|Buffer} filebuffer - Filepath or buffer
125
 * @param {Options} options - Object containing options
126
 * @returns {object} - Return the read tags
127
 */
128
function readSync(filebuffer, options) {
129
    if(isString(filebuffer)) {
130
        filebuffer = fs.readFileSync(filebuffer)
131
    }
132
    return ID3Helpers.getTagsFromBuffer(filebuffer, options)
133
}
134
135
/**
136
 * Read ID3-Tags from passed buffer/filepath
137
 * @param {string|Buffer} filebuffer - Filepath or buffer
138
 * @param {Options} options - Object containing options
139
 * @param {ReadCallback} fn - Function for async version
140
 * @returns {void} - Return the read tags
141
 */
142
 function readAsync(filebuffer, options, fn) {
143
    if(isString(filebuffer)) {
144
        fs.readFile(filebuffer, (error, data) => {
145
            if(error) {
146
                fn(error, null)
147
            } else {
148
                fn(null, ID3Helpers.getTagsFromBuffer(data, options))
149
            }
150
        })
151
    } else {
152
        fn(null, ID3Helpers.getTagsFromBuffer(filebuffer, options))
153
    }
154
}
155
156
/**
157
 * Read ID3-Tags from passed buffer/filepath
158
 * @param {string|Buffer} filebuffer - Filepath or buffer
159
 * @param {Options} [options] - Object containing options
160
 * @param {ReadCallback} [fn] - Function for async version
161
 * @returns {boolean} - Return the read tags
162
 */
163
function read(filebuffer, options, fn) {
164
    if(!options || typeof options === 'function') {
165
        fn = fn || options
166
        options = {}
167
    }
168
    if(isFunction(fn)) {
169
        return readAsync(filebuffer, options, fn)
170
    }
171
    return readSync(filebuffer, options)
172
}
173
174
/**
175
 * Update ID3-Tags from passed buffer/filepath
176
 * @param {object} tags - Object containing tags to be written
177
 * @param {string|Buffer} filebuffer - A filepath string or buffer
178
 * @param {Options} [options] - Object containing options
179
 * @param {WriteCallback} [fn] - Function for async version
180
 * @returns {boolean|Buffer|Error}
181
 */
182
function update(tags, filebuffer, options, fn) {
183
    if(!options || typeof options === 'function') {
184
        fn = fn || options
185
        options = {}
186
    }
187
188
    const rawTags = Object.keys(tags).reduce((acc, val) => {
189
        if(ID3Definitions.FRAME_IDENTIFIERS.v3[val] !== undefined) {
190
            acc[ID3Definitions.FRAME_IDENTIFIERS.v3[val]] = tags[val]
191
        } else {
192
            acc[val] = tags[val]
193
        }
194
        return acc
195
    }, {})
196
197
    const updateFn = (currentTags) => {
198
        currentTags = currentTags.raw || {}
199
        Object.keys(rawTags).map((frameIdentifier) => {
200
            const options = ID3Util.getSpecOptions(frameIdentifier, 3)
201
            const cCompare = {}
202
            if(options.multiple && currentTags[frameIdentifier] && rawTags[frameIdentifier]) {
203
                if(options.updateCompareKey) {
204
                    currentTags[frameIdentifier].forEach((cTag, index) => {
205
                        cCompare[cTag[options.updateCompareKey]] = index
206
                    })
207
208
                }
209
                if (!(rawTags[frameIdentifier] instanceof Array)) {
210
                    rawTags[frameIdentifier] = [rawTags[frameIdentifier]]
211
                }
212
                rawTags[frameIdentifier].forEach((rTag) => {
213
                    const comparison = cCompare[rTag[options.updateCompareKey]]
214
                    if (comparison !== undefined) {
215
                        currentTags[frameIdentifier][comparison] = rTag
216
                    } else {
217
                        currentTags[frameIdentifier].push(rTag)
218
                    }
219
                })
220
            } else {
221
                currentTags[frameIdentifier] = rawTags[frameIdentifier]
222
            }
223
        })
224
        return currentTags
225
    }
226
227
    if(isFunction(fn)) {
228
        return write(updateFn(read(filebuffer, options)), filebuffer, fn)
229
    }
230
    return write(updateFn(read(filebuffer, options)), filebuffer)
231
}
232
233
/**
234
 * @param {string} filepath - Filepath to file
235
 * @returns {boolean|Error}
236
 */
237
function removeTagsSync(filepath) {
238
    let data
239
    try {
240
        data = fs.readFileSync(filepath)
241
    } catch(error) {
242
        return error
243
    }
244
245
    const newData = removeTagsFromBuffer(data)
246
    if(!newData) {
247
        return false
248
    }
249
250
    try {
251
        fs.writeFileSync(filepath, newData, 'binary')
252
    } catch(error) {
253
        return error
254
    }
255
256
    return true
257
}
258
259
/**
260
 * @param {string} filepath - Filepath to file
261
 * @param {WriteCallback} fn - Function for async usage
262
 * @returns {void}
263
 */
264
function removeTagsAsync(filepath, fn) {
265
    fs.readFile(filepath, (error, data) => {
266
        if(error) {
267
            fn(error)
268
            return
269
        }
270
271
        const newData = removeTagsFromBuffer(data)
272
        if(!newData) {
273
            fn(error)
274
            return
275
        }
276
277
        fs.writeFile(filepath, newData, 'binary', (error) => {
278
            if(error) {
279
                fn(error)
280
            } else {
281
                fn(null)
282
            }
283
        })
284
    })
285
}
286
287
/**
288
 * Check and remove already written ID3-Frames from a file
289
 * @param {string} filepath - Filepath to file
290
 * @param {WriteCallback} [fn] - Function for async usage
291
 * @returns {boolean|Error|void}
292
 */
293
function removeTags(filepath, fn) {
294
    if(isFunction(fn)) {
295
        return removeTagsAsync(filepath, fn)
296
    }
297
    return removeTagsSync(filepath)
298
}
299
300
function makeSwapParameters(fn) {
301
    return (a, b) => fn(b, a)
302
}
303
304
// The reorderParameter is a workaround because the callback function
305
// does not have a consistent interface between all the API functions.
306
// Ideally, all the functions should align with the promise style and
307
// always have the result first and the error second.
308
// Changing this would break the current public API.
309
// This could be changed internally and swap the parameter in a light
310
// wrapper when creating the public interface and then remove in a
311
// version 1.0 later with an API breaking change.
312
function makePromise(
313
    fn,
314
    reorderParameters = fn => (a, b) => fn(a, b)
315
) {
316
    return new Promise((resolve, reject) => {
317
        fn(reorderParameters((error, result) => {
318
            if(error) {
319
                reject(error)
320
            } else {
321
                resolve(result)
322
            }
323
        }))
324
    })
325
}
326
327
const PromiseExport = {
328
    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...
329
    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...
330
    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 182 does not use this.
Loading history...
331
    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 163 does not use this.
Loading history...
332
    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 293 does not use this.
Loading history...
333
}
334
335
module.exports = {
336
    TagConstants: ID3Definitions.TagConstants,
337
    create,
338
    write,
339
    update,
340
    read,
341
    removeTags,
342
    removeTagsFromBuffer,
343
    Promise: PromiseExport
344
}
345