Passed
Pull Request — filestream (#169)
by
unknown
01:48
created

file-write.ts ➔ removeId3TagIfFound   A

Complexity

Conditions 2

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 14
rs 9.8
c 0
b 0
f 0
cc 2
1
import {
2
    fsWritePromise,
3
    fillBufferAsync,
4
    fillBufferSync,
5
    processFileSync,
6
    processFileAsync,
7
    fsExistsPromise,
8
    fsWriteFilePromise,
9
    fsRenamePromise,
10
    unlinkIfExistSync,
11
    unlinkIfExist
12
} from "./util-file"
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
    if (!fs.existsSync(filepath)) {
20
        fs.writeFileSync(filepath, id3Tag)
21
        return
22
    }
23
    const tempFilepath = makeTempFilepath(filepath)
24
    processFileSync(filepath, 'r', (readFileDescriptor) => {
25
        try {
26
            processFileSync(tempFilepath, 'w', (writeFileDescriptor) => {
27
                fs.writeSync(writeFileDescriptor, id3Tag)
28
                copyFileWithoutId3TagSync(
29
                    readFileDescriptor, writeFileDescriptor
30
                )
31
            })
32
        } catch(error) {
33
            unlinkIfExistSync(tempFilepath)
34
            throw error
35
        }
36
    })
37
    fs.renameSync(tempFilepath, filepath)
38
}
39
40
export async function writeId3TagToFileAsync(filepath: string, id3Tag: Buffer) {
41
    if (!await fsExistsPromise(filepath)) {
42
        await fsWriteFilePromise(filepath, id3Tag)
43
        return
44
    }
45
    const tempFilepath = makeTempFilepath(filepath)
46
    await processFileAsync(filepath, 'r', async (readFileDescriptor) => {
47
        try {
48
            await processFileAsync(tempFilepath, 'w',
49
                async (writeFileDescriptor) => {
50
                    await fsWritePromise(writeFileDescriptor, id3Tag)
51
                    await copyFileWithoutId3TagAsync(
52
                        readFileDescriptor, writeFileDescriptor
53
                    )
54
                }
55
            )
56
        } catch(error) {
57
            await unlinkIfExist(tempFilepath)
58
            throw error
59
        }
60
61
    })
62
    await fsRenamePromise(tempFilepath, filepath)
63
}
64
65
function makeTempFilepath(filepath: string) {
66
    return `${filepath}.tmp-${Date.now()}`
67
}
68
69
function copyFileWithoutId3TagSync(
70
    readFileDescriptor: number,
71
    writeFileDescriptor: number
72
) {
73
    const buffer = Buffer.alloc(FileBufferSize)
74
    let readData
75
    while((readData = fillBufferSync(readFileDescriptor, buffer)).length) {
76
        const { data, bytesToSkip } = removeId3TagIfFound(readData)
77
        if (bytesToSkip) {
78
            fillBufferSync(readFileDescriptor, Buffer.alloc(bytesToSkip))
79
        }
80
        fs.writeSync(writeFileDescriptor, data, 0, data.length, null)
81
    }
82
}
83
84
async function copyFileWithoutId3TagAsync(
85
    readFileDescriptor: number,
86
    writeFileDescriptor: number
87
) {
88
    const buffer = Buffer.alloc(FileBufferSize)
89
    let readData
90
    while((readData = await fillBufferAsync(readFileDescriptor, buffer)).length) {
91
        const { data, bytesToSkip } = removeId3TagIfFound(readData)
92
        if (bytesToSkip) {
93
            await fillBufferAsync(readFileDescriptor, Buffer.alloc(bytesToSkip))
94
        }
95
        await fsWritePromise(writeFileDescriptor, data, 0, data.length, null)
96
    }
97
}
98
99
function removeId3TagIfFound(data: Buffer) {
100
    const id3TagPosition = findId3TagPosition(data)
101
    if (id3TagPosition === -1) {
102
        return { data }
103
    }
104
    const dataFromId3Start = data.subarray(id3TagPosition)
105
    const id3TagSize = getId3TagSize(dataFromId3Start)
106
    return {
107
        data: Buffer.concat([
108
            data.subarray(0, id3TagPosition),
109
            dataFromId3Start.subarray(Math.min(id3TagSize, dataFromId3Start.length))
110
        ]),
111
        bytesToSkip: Math.max(0, id3TagSize - dataFromId3Start.length)
112
    }
113
}
114