1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16declare function requireNapi(napiModuleName: string): any;
17const stream = requireNapi('util.stream');
18const fileIo = requireNapi('file.fs');
19
20interface ReadStreamOptions {
21    start?: number;
22    end?: number;
23}
24
25interface WriteStreamOptions {
26    start?: number;
27    mode?: number;
28}
29
30class ReadStream extends stream.Readable {
31    private pathInner: string;
32    private bytesReadInner: number;
33    private offset: number;
34    private start?: number;
35    private end?: number;
36    // @ts-ignore
37    private stream?: fileIo.Stream;
38
39    constructor(path: string, options?: ReadStreamOptions) {
40        super();
41        this.pathInner = path;
42        this.bytesReadInner = 0;
43        this.start = options?.start;
44        this.end = options?.end;
45        this.stream = fileIo.createStreamSync(this.pathInner, 'r');
46        this.offset = this.start ?? 0;
47    }
48
49    get path(): string {
50        return this.pathInner;
51    }
52
53    get bytesRead(): number {
54        return this.bytesReadInner;
55    }
56
57    // @ts-ignore
58    seek(offset: number, whence?: fileIo.WhenceType): number {
59        if (whence === undefined) {
60            this.offset = this.stream?.seek(offset);
61        } else {
62            this.offset = this.stream?.seek(offset, whence);
63        }
64        return this.offset;
65    }
66
67    close(): void {
68        this.stream?.close();
69    }
70
71    doInitialize(callback: Function): void {
72        callback();
73    }
74
75    doRead(size: number): void {
76        let readSize = size;
77        if (this.end !== undefined) {
78            if (this.offset > this.end) {
79                this.push(null);
80                return;
81            }
82            if (this.offset + readSize > this.end) {
83                readSize = this.end - this.offset;
84            }
85        }
86        let buffer = new ArrayBuffer(readSize);
87        const off = this.offset;
88        this.offset += readSize;
89        this.stream?.read(buffer, { offset: off, length: readSize })
90            .then((readOut: number) => {
91                if (readOut > 0) {
92                    this.bytesReadInner += readOut;
93                    this.push(new Uint8Array(buffer.slice(0, readOut)));
94                }
95                if (readOut !== readSize || readOut < size) {
96                    this.offset = this.offset - readSize + readOut;
97                    this.push(null);
98                }
99            });
100    }
101}
102
103class WriteStream extends stream.Writable {
104    private pathInner: string;
105    private bytesWrittenInner: number;
106    private offset: number;
107    private mode: string;
108    private start?: number;
109    // @ts-ignore
110    private stream?: fileIo.Stream;
111
112    constructor(path: string, options?: WriteStreamOptions) {
113        super();
114        this.pathInner = path;
115        this.bytesWrittenInner = 0;
116        this.start = options?.start;
117        this.mode = this.convertOpenMode(options?.mode);
118        this.stream = fileIo.createStreamSync(this.pathInner, this.mode);
119        this.offset = this.start ?? 0;
120    }
121
122    get path(): string {
123        return this.pathInner;
124    }
125
126    get bytesWritten(): number {
127        return this.bytesWrittenInner;
128    }
129
130    // @ts-ignore
131    seek(offset: number, whence?: fileIo.WhenceType): number {
132        if (whence === undefined) {
133            this.offset = this.stream?.seek(offset);
134        } else {
135            this.offset = this.stream?.seek(offset, whence);
136        }
137        return this.offset;
138    }
139
140    close(): void {
141        this.stream?.close();
142    }
143
144    doInitialize(callback: Function): void {
145        callback();
146    }
147
148    doWrite(chunk: string | Uint8Array, encoding: string, callback: Function): void {
149        this.stream?.write(chunk, { offset: this.offset })
150            .then((writeIn: number) => {
151                this.offset += writeIn;
152                this.bytesWrittenInner += writeIn;
153                callback();
154            })
155            .finally(() => {
156                this.stream?.flush();
157            });
158    }
159
160    convertOpenMode(mode?: number): string {
161        let modeStr = 'w';
162        if (mode === undefined) {
163            return modeStr;
164        }
165        if (mode & fileIo.OpenMode.WRITE_ONLY) {
166            modeStr = 'w';
167        }
168        if (mode & fileIo.OpenMode.READ_WRITE) {
169            modeStr = 'w+';
170        }
171        if ((mode & fileIo.OpenMode.WRITE_ONLY) && (mode & fileIo.OpenMode.APPEND)) {
172            modeStr = 'a';
173        }
174        if ((mode & fileIo.OpenMode.READ_WRITE) && (mode & fileIo.OpenMode.APPEND)) {
175            modeStr = 'a+';
176        }
177        return modeStr;
178    }
179}
180
181export default {
182    ReadStream: ReadStream,
183    WriteStream: WriteStream,
184};
185