Passed
Pull Request — master (#164)
by Jan
02:01
created

file-read.ts ➔ findPartialId3TagAsync   A

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 12
rs 9.9
c 0
b 0
f 0
cc 3
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(
67
        fileDescriptor,
68
        buffer,
69
        Header.size,
70
        buffer.length - Header.size,
71
        null
72
    )
73
    return buffer.subarray(0, bytesRead + Header.size)
74
}
75
76
async function getNextBufferSubarrayAsync(fileDescriptor: number, buffer: Buffer): Promise<Buffer> {
77
    const bytesRead = (await fsReadPromise(
78
        fileDescriptor,
79
        buffer,
80
        Header.size,
81
        buffer.length - Header.size,
82
        null
83
    )).bytesRead
84
    return buffer.subarray(0, bytesRead + Header.size)
85
}
86
87
function processFile<T>(
88
    filepath: string,
89
    flags: string,
90
    process: (fileDescriptor: number) => T
91
) {
92
    const fileDescriptor = fs.openSync(filepath, flags)
93
    try {
94
        return process(fileDescriptor)
95
    }
96
    finally {
97
        fs.closeSync(fileDescriptor)
98
    }
99
}
100
101
async function processFileAsync<T>(
102
    filepath: string,
103
    flags: string,
104
    process: (fileDescriptor: number) => Promise<T>
105
): Promise<T> {
106
    const fileDescriptor = await fsOpenPromise(filepath, flags)
107
    try {
108
        return await process(fileDescriptor)
109
    }
110
    finally {
111
        await fsClosePromise(fileDescriptor)
112
    }
113
}
114
115
function calculateMissingBytes(id3TagSize: number, id3TagBuffer: Buffer): number {
116
    return Math.max(0, id3TagSize - id3TagBuffer.length)
117
}
118
119
function completePartialId3TagData(fileDescriptor: number, partialId3TagData: Buffer): Buffer {
120
    const id3TagSize = getId3TagSize(partialId3TagData)
121
    const missingBytesCount = calculateMissingBytes(id3TagSize, partialId3TagData)
122
    if(missingBytesCount) {
123
        const id3TagRemainingBuffer = Buffer.alloc(missingBytesCount, 0x00)
124
        fs.readSync(fileDescriptor, id3TagRemainingBuffer)
125
        return Buffer.concat([
126
            partialId3TagData,
127
            id3TagRemainingBuffer
128
        ])
129
    }
130
    return partialId3TagData.subarray(0, id3TagSize)
131
}
132
133
async function completePartialId3TagDataAsync(fileDescriptor: number, partialId3TagData: Buffer): Promise<Buffer> {
134
    const id3TagSize = getId3TagSize(partialId3TagData)
135
    const missingBytesCount = calculateMissingBytes(id3TagSize, partialId3TagData)
136
    if(missingBytesCount) {
137
        const id3TagRemainingBuffer = Buffer.alloc(missingBytesCount, 0x00)
138
        await fsReadPromise(fileDescriptor, {buffer: id3TagRemainingBuffer})
139
        return Buffer.concat([
140
            partialId3TagData,
141
            id3TagRemainingBuffer
142
        ])
143
    }
144
    return partialId3TagData.subarray(0, id3TagSize)
145
}