Passed
Push — filestream ( 7bdb53...c0cdb0 )
by Jan
01:48
created

file-read.ts ➔ getId3TagDataFromFileSync   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
1
import * as fs from 'fs'
2
import { promisify } from 'util'
3
import { findId3TagPosition, getId3TagSize, Header } from './id3-tag'
4
5
const FileBufferSize = 20 * 1024 * 1024
6
7
const fsOpenPromise = promisify(fs.open)
8
const fsReadPromise = promisify(fs.read)
9
const fsClosePromise = promisify(fs.close)
10
11
type SuccessCallback = (err: null, buffer: Buffer|null) => void
12
type ErrorCallback = (err: Error, buffer: null) => void
13
type Callback = SuccessCallback & ErrorCallback
14
15
export function getId3TagDataFromFileSync(filepath: string): Buffer|null {
16
    return processFile(filepath, 'r', (fileDescriptor) => {
17
        const partialId3TagData = findPartialId3TagSync(fileDescriptor)
18
        return partialId3TagData ? completePartialId3TagData(
19
            fileDescriptor,
20
            partialId3TagData
21
        ) : null
22
    })
23
}
24
25
export function getId3TagDataFromFileAsync(filepath: string, callback: Callback) {
26
    processFileAsync(filepath, 'r', async (fileDescriptor) => {
27
        const partialId3TagData = await findPartialId3TagAsync(fileDescriptor)
28
        return partialId3TagData ? completePartialId3TagDataAsync(
29
            fileDescriptor,
30
            partialId3TagData
31
        ) : null
32
    }).then((data) => {
33
        callback(null, data)
34
    }).catch((error) => {
35
        callback(error, null)
36
    })
37
}
38
39
function findPartialId3TagSync(fileDescriptor: number): Buffer|null {
40
    const buffer = Buffer.alloc(FileBufferSize)
41
    let data
42
    while((data = getNextBufferSubarraySync(fileDescriptor, buffer)).length > Header.size) {
43
        const id3TagPosition = findId3TagPosition(data)
44
        if(id3TagPosition !== -1) {
45
            return data.subarray(id3TagPosition)
46
        }
47
        buffer.copyWithin(0, buffer.length - Header.size)
48
    }
49
    return null
50
}
51
52
async function findPartialId3TagAsync(fileDescriptor: number): Promise<Buffer|null> {
53
    const buffer = Buffer.alloc(FileBufferSize)
54
    let data
55
    while((data = await getNextBufferSubarrayAsync(fileDescriptor, buffer)).length > Header.size) {
56
        const id3TagPosition = findId3TagPosition(data)
57
        if(id3TagPosition !== -1) {
58
            return data.subarray(id3TagPosition)
59
        }
60
        buffer.copyWithin(0, buffer.length - Header.size)
61
    }
62
    return null
63
}
64
65
function getNextBufferSubarraySync(fileDescriptor: number, buffer: Buffer): Buffer {
66
    const bytesRead = fs.readSync(fileDescriptor, buffer, {offset: Header.size})
67
    return buffer.subarray(0, bytesRead + Header.size)
68
}
69
70
async function getNextBufferSubarrayAsync(fileDescriptor: number, buffer: Buffer): Promise<Buffer> {
71
    const bytesRead = (await fsReadPromise(fileDescriptor, {buffer, offset: Header.size})).bytesRead
72
    return buffer.subarray(0, bytesRead + Header.size)
73
}
74
75
function processFile<T>(
76
    filepath: string,
77
    flags: string,
78
    process: (fileDescriptor: number) => T
79
) {
80
    const fileDescriptor = fs.openSync(filepath, flags)
81
    try {
82
        return process(fileDescriptor)
83
    }
84
    catch (error) {
85
        throw error
86
    }
87
    finally {
88
        fs.closeSync(fileDescriptor)
89
    }
90
}
91
92
async function processFileAsync<T>(
93
    filepath: string,
94
    flags: string,
95
    process: (fileDescriptor: number) => Promise<T>
96
): Promise<T> {
97
    const fileDescriptor = await fsOpenPromise(filepath, flags)
98
    try {
99
        return await process(fileDescriptor)
100
    }
101
    catch (error) {
102
        throw error
103
    }
104
    finally {
105
        await fsClosePromise(fileDescriptor)
106
    }
107
}
108
109
function calculateMissingBytes(id3TagSize: number, id3TagBuffer: Buffer): number {
110
    return Math.max(0, id3TagSize - id3TagBuffer.length)
111
}
112
113
function completePartialId3TagData(fileDescriptor: number, partialId3TagData: Buffer): Buffer {
114
    const id3TagSize = getId3TagSize(partialId3TagData)
115
    const missingBytesCount = calculateMissingBytes(id3TagSize, partialId3TagData)
116
    if(missingBytesCount) {
117
        const id3TagRemainingBuffer = Buffer.alloc(missingBytesCount, 0x00)
118
        fs.readSync(fileDescriptor, id3TagRemainingBuffer)
119
        return Buffer.concat([
120
            partialId3TagData,
121
            id3TagRemainingBuffer
122
        ])
123
    }
124
    return partialId3TagData.subarray(0, id3TagSize)
125
}
126
127
async function completePartialId3TagDataAsync(fileDescriptor: number, partialId3TagData: Buffer): Promise<Buffer> {
128
    const id3TagSize = getId3TagSize(partialId3TagData)
129
    const missingBytesCount = calculateMissingBytes(id3TagSize, partialId3TagData)
130
    if(missingBytesCount) {
131
        const id3TagRemainingBuffer = Buffer.alloc(missingBytesCount, 0x00)
132
        await fsReadPromise(fileDescriptor, {buffer: id3TagRemainingBuffer})
133
        return Buffer.concat([
134
            partialId3TagData,
135
            id3TagRemainingBuffer
136
        ])
137
    }
138
    return partialId3TagData.subarray(0, id3TagSize)
139
}