Passed
Pull Request — master (#164)
by Jan
03:42 queued 01:44
created

src/file-write.ts   A

Complexity

Total Complexity 15
Complexity/F 1.88

Size

Lines of Code 135
Function Count 8

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 15
eloc 107
mnd 7
bc 7
fnc 8
dl 0
loc 135
rs 10
bpm 0.875
cpm 1.875
noi 0
c 0
b 0
f 0

8 Functions

Rating   Name   Duplication   Size   Complexity  
A file-write.ts ➔ writeId3TagToFileSync 0 11 1
A file-write.ts ➔ streamOriginalIntoNewFileAsync 0 10 3
A file-write.ts ➔ getBufferWithoutId3TagAndSkipSync 0 20 2
A file-write.ts ➔ streamOriginalIntoNewFileSync 0 10 3
A file-write.ts ➔ writeId3TagToFileAsync 0 18 2
A file-write.ts ➔ getTmpFilePathSync 0 6 1
A file-write.ts ➔ getBufferWithoutId3TagAndSkipAsync 0 20 2
A file-write.ts ➔ getTmpFileAsync 0 8 1
1
import {
2
    fsReadPromise,
3
    fsRenamePromise,
4
    fsUnlinkPromise,
5
    fsWritePromise,
6
    getNextBufferSubarrayAsync,
7
    getNextBufferSubarraySync,
8
    processFile,
9
    processFileAsync
10
} from "./util-file"
11
import * as tmp from 'tmp'
12
import * as path from 'path'
13
import * as fs from 'fs'
14
import { findId3TagPosition, getId3TagSize } from "./id3-tag"
15
16
const FileBufferSize = 20 * 1024 * 1024
17
18
export function writeId3TagToFileSync(filepath: string, id3Tag: Buffer) {
19
    const tmpFile = getTmpFilePathSync(filepath)
20
    processFile(filepath, 'r', (readFileDescriptor) => {
21
        processFile(tmpFile, 'w', (writeFileDescriptor) => {
22
            fs.writeSync(writeFileDescriptor, id3Tag)
23
            streamOriginalIntoNewFileSync(readFileDescriptor, writeFileDescriptor)
24
        })
25
    })
26
    fs.unlinkSync(filepath)
27
    fs.renameSync(tmpFile, filepath)
28
}
29
30
export function writeId3TagToFileAsync(filepath: string, id3Tag: Buffer, callback: (err: Error|null) => void) {
31
    getTmpFileAsync(filepath, (err, tmpFile) => {
32
        if(err || !tmpFile) {
33
            return callback(err)
34
        }
35
36
        processFileAsync(filepath, 'r', async (readFileDescriptor) => {
37
            return processFileAsync(tmpFile, 'w', async (writeFileDescriptor) => {
38
                await fsWritePromise(writeFileDescriptor, id3Tag)
39
                await streamOriginalIntoNewFileAsync(readFileDescriptor, writeFileDescriptor)
40
            })
41
        }).then(async () => {
42
            await fsUnlinkPromise(filepath)
43
            await fsRenamePromise(tmpFile, filepath)
44
            callback(null)
45
        }).catch((error) => {
46
            callback(error)
47
        })
48
    })
49
}
50
51
function getTmpFilePathSync(filepath: string): string {
52
    const parsedPath = path.parse(filepath)
53
    return tmp.tmpNameSync({
54
        tmpdir: parsedPath.dir,
55
        template: `${parsedPath.base}.tmp-XXXXXX`,
56
    })
57
}
58
59
function getTmpFileAsync(filepath: string, callback: tmp.TmpNameCallback) {
60
    const parsedPath = path.parse(filepath)
61
    tmp.tmpName({
62
        tmpdir: parsedPath.dir,
63
        template: `${parsedPath.base}.tmp-XXXXXX`,
64
    }, (err, filename) => {
65
        callback(err, filename)
66
    })
67
}
68
69
function streamOriginalIntoNewFileSync(readFileDescriptor: number, writeFileDescriptor: number) {
70
    const buffer = Buffer.alloc(FileBufferSize)
71
    let data
72
    while((data = getNextBufferSubarraySync(readFileDescriptor, buffer)).length) {
73
        const id3TagPosition = findId3TagPosition(data)
74
        if(id3TagPosition !== -1) {
75
            data = getBufferWithoutId3TagAndSkipSync(readFileDescriptor, data, id3TagPosition)
76
        }
77
        fs.writeSync(writeFileDescriptor, data, 0, data.length, null)
78
    }
79
}
80
81
async function streamOriginalIntoNewFileAsync(readFileDescriptor: number, writeFileDescriptor: number) {
82
    const buffer = Buffer.alloc(FileBufferSize)
83
    let data
84
    while((data = await getNextBufferSubarrayAsync(readFileDescriptor, buffer)).length) {
85
        const id3TagPosition = findId3TagPosition(data)
86
        if(id3TagPosition !== -1) {
87
            data = await getBufferWithoutId3TagAndSkipAsync(readFileDescriptor, data, id3TagPosition)
88
        }
89
        await fsWritePromise(writeFileDescriptor, data, 0, data.length, null)
90
    }
91
}
92
93
function getBufferWithoutId3TagAndSkipSync(fileDescriptor: number, data: Buffer, id3TagPosition: number): Buffer {
94
    const dataFromId3Start = data.subarray(id3TagPosition)
95
    const id3TagSize = getId3TagSize(dataFromId3Start)
96
    if(id3TagSize > dataFromId3Start.length) {
97
        const missingBytesCount = id3TagSize - dataFromId3Start.length
98
        fs.readSync(
99
            fileDescriptor,
100
            Buffer.alloc(missingBytesCount),
101
            0,
102
            missingBytesCount,
103
            null
104
        )
105
        return data.subarray(0, id3TagPosition)
106
    }
107
108
    const id3TagEndPosition = id3TagPosition + id3TagSize
109
    return Buffer.concat([
110
        data.subarray(0, id3TagPosition),
111
        data.subarray(id3TagEndPosition)
112
    ])
113
}
114
115
async function getBufferWithoutId3TagAndSkipAsync(fileDescriptor: number, data: Buffer, id3TagPosition: number): Promise<Buffer> {
116
    const dataFromId3Start = data.subarray(id3TagPosition)
117
    const id3TagSize = getId3TagSize(dataFromId3Start)
118
    if(id3TagSize > dataFromId3Start.length) {
119
        const missingBytesCount = id3TagSize - dataFromId3Start.length
120
        await fsReadPromise(
121
            fileDescriptor,
122
            Buffer.alloc(missingBytesCount),
123
            0,
124
            missingBytesCount,
125
            null
126
        )
127
        return data.subarray(0, id3TagPosition)
128
    }
129
130
    const id3TagEndPosition = id3TagPosition + id3TagSize
131
    return Buffer.concat([
132
        data.subarray(0, id3TagPosition),
133
        data.subarray(id3TagEndPosition)
134
    ])
135
}