-
Notifications
You must be signed in to change notification settings - Fork 60
Move file reading and writing to streaming #164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
pbricout
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a partial review and addressed most of the points here, this is still work in progress there is bit more to do, but this is a good start.
| streamOriginalIntoNewFileSync(readFileDescriptor, writeFileDescriptor) | ||
| }) | ||
| }) | ||
| fs.unlinkSync(filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary, rename will overwrite the destination and is safer as the operation will be atomic.
| function getTmpFilePathSync(filepath: string): string { | ||
| const parsedPath = path.parse(filepath) | ||
| return tmp.tmpNameSync({ | ||
| tmpdir: parsedPath.dir, | ||
| template: `${parsedPath.base}.tmp-XXXXXX`, | ||
| }) | ||
| } | ||
|
|
||
| function getTmpFileAsync(filepath: string, callback: tmp.TmpNameCallback) { | ||
| const parsedPath = path.parse(filepath) | ||
| tmp.tmpName({ | ||
| tmpdir: parsedPath.dir, | ||
| template: `${parsedPath.base}.tmp-XXXXXX`, | ||
| }, (err, filename) => { | ||
| callback(err, filename) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The common part could be factored in a function getTmpNameOptions(filename) => then tmp.tmpName(getTmpNameOptions(flename)).
|
|
||
| function getTmpFileAsync(filepath: string, callback: tmp.TmpNameCallback) { | ||
| const parsedPath = path.parse(filepath) | ||
| tmp.tmpName({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we promisify that one too so we can use await and simplify writeId3TagToFileAsync().
| await streamOriginalIntoNewFileAsync(readFileDescriptor, writeFileDescriptor) | ||
| }) | ||
| }).then(async () => { | ||
| await fsUnlinkPromise(filepath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unnecessary, rename will overwrite the destination and is safer as the operation will be atomic.
| export const fsOpenPromise = promisify(fs.open) | ||
| export const fsReadPromise = promisify(fs.read) | ||
| export const fsClosePromise = promisify(fs.close) | ||
| export const fsWritePromise = promisify(fs.write) | ||
| export const fsUnlinkPromise = promisify(fs.unlink) | ||
| export const fsRenamePromise = promisify(fs.rename) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could export an object:
export const fsPromise = {
open: promisify(fs.open)
// ...
}| import * as fs from 'fs' | ||
| import { findId3TagPosition, getId3TagSize } from "./id3-tag" | ||
|
|
||
| const FileBufferSize = 20 * 1024 * 1024 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be an option with a default, this will allow to write tests with smaller files.
|
|
||
| const FileBufferSize = 20 * 1024 * 1024 | ||
|
|
||
| export function writeId3TagToFileSync(filepath: string, id3Tag: Buffer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The temp file is not deleted in case of error.
| fs.renameSync(tmpFile, filepath) | ||
| } | ||
|
|
||
| export function writeId3TagToFileAsync(filepath: string, id3Tag: Buffer, callback: (err: Error|null) => void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The temp file is not deleted in case of error.
| */ | ||
| export function write( | ||
| tags: WriteTags, filebuffer: string | Buffer, callback: WriteCallback | ||
| tags: WriteTags, filebuffer: string | Buffer, callback: WriteFileCallback | WriteCallback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not make the existing API even more complicated, let's take this opportunity to create a new release with a simplified API.
|
|
||
| export function writeId3TagToFileSync(filepath: string, id3Tag: Buffer) { | ||
| const tmpFile = getTmpFilePathSync(filepath) | ||
| processFile(filepath, 'r', (readFileDescriptor) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails if the source file does not exist, but we want to support this case, if there is no source file, we should just write.
| fs.renameSync(tmpFile, filepath) | ||
| } | ||
|
|
||
| export function writeId3TagToFileAsync(filepath: string, id3Tag: Buffer, callback: (err: Error|null) => void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we could optimize by doing the process of the buffer during an async read/write operation, but this is a bit more complicated, let's leave it for another PR.
| const buffer = Buffer.alloc(FileBufferSize) | ||
| let data | ||
| while((data = getNextBufferSubarraySync(readFileDescriptor, buffer)).length) { | ||
| const id3TagPosition = findId3TagPosition(data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a bug, we may miss the tag if across two readings, we need to use the same trick than in read.
This PR implements file streaming for reading files using the
NodeID3.read,NodeID3.write(and update) function.By doing so, we support reading/writing big files and go easy on memory.
NodeID3.writecallback should not return a buffer if a file is being manipulated. This is also how it is documented in the README.mdRelated issue: #161