Compare commits

..

8 Commits

Author SHA1 Message Date
3ff6de201d 11.0.4 2023-11-24 19:29:00 +01:00
f5c106b2ca fix(core): update 2023-11-24 19:28:59 +01:00
d3c26d0d46 11.0.3 2023-11-24 19:15:41 +01:00
9935fe2d3c fix(core): update 2023-11-24 19:15:41 +01:00
3b05aab39b 11.0.2 2023-11-07 21:32:00 +01:00
53be2eb59d fix(core): update 2023-11-07 21:32:00 +01:00
c92a0dddbd 11.0.1 2023-11-07 14:09:49 +01:00
27403a73b5 fix(core): update 2023-11-07 14:09:48 +01:00
6 changed files with 667 additions and 322 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "@push.rocks/smartfile", "name": "@push.rocks/smartfile",
"private": false, "private": false,
"version": "11.0.0", "version": "11.0.4",
"description": "offers smart ways to work with files in nodejs", "description": "offers smart ways to work with files in nodejs",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
@ -26,7 +26,7 @@
}, },
"homepage": "https://gitlab.com/push.rocks/smartfile#readme", "homepage": "https://gitlab.com/push.rocks/smartfile#readme",
"dependencies": { "dependencies": {
"@push.rocks/lik": "^6.0.5", "@push.rocks/lik": "^6.0.12",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfile-interfaces": "^1.0.7", "@push.rocks/smartfile-interfaces": "^1.0.7",
"@push.rocks/smarthash": "^3.0.4", "@push.rocks/smarthash": "^3.0.4",
@ -34,11 +34,11 @@
"@push.rocks/smartmime": "^1.0.5", "@push.rocks/smartmime": "^1.0.5",
"@push.rocks/smartpath": "^5.0.11", "@push.rocks/smartpath": "^5.0.11",
"@push.rocks/smartpromise": "^4.0.2", "@push.rocks/smartpromise": "^4.0.2",
"@push.rocks/smartrequest": "^2.0.20", "@push.rocks/smartrequest": "^2.0.21",
"@push.rocks/smartstream": "^3.0.7", "@push.rocks/smartstream": "^3.0.30",
"@types/fs-extra": "^11.0.3", "@types/fs-extra": "^11.0.4",
"@types/glob": "^8.1.0", "@types/glob": "^8.1.0",
"@types/js-yaml": "^4.0.8", "@types/js-yaml": "^4.0.9",
"fs-extra": "^11.1.1", "fs-extra": "^11.1.1",
"glob": "^10.3.10", "glob": "^10.3.10",
"js-yaml": "^4.1.0" "js-yaml": "^4.1.0"
@ -46,9 +46,9 @@
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.70", "@git.zone/tsbuild": "^2.1.70",
"@git.zone/tsrun": "^1.2.46", "@git.zone/tsrun": "^1.2.46",
"@git.zone/tstest": "^1.0.81", "@git.zone/tstest": "^1.0.84",
"@push.rocks/tapbundle": "^5.0.15", "@push.rocks/tapbundle": "^5.0.15",
"@types/node": "^20.8.10" "@types/node": "^20.10.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

736
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartfile', name: '@push.rocks/smartfile',
version: '11.0.0', version: '11.0.4',
description: 'offers smart ways to work with files in nodejs' description: 'offers smart ways to work with files in nodejs'
} }

View File

@ -91,6 +91,12 @@ export class SmartFile extends plugins.smartjson.Smartjson {
}); });
} }
public static async fromUrl (urlArg: string) {
const response = await plugins.smartrequest.getBinary(urlArg);
const smartfile = await SmartFile.fromBuffer(urlArg, response.body);
return smartfile;
}
// ======== // ========
// INSTANCE // INSTANCE
// ======== // ========

View File

@ -389,27 +389,58 @@ export const listFileTree = async (
}; };
/** /**
* checks wether a file is ready for processing * Watches for file stability before resolving the promise.
*/ */
export const waitForFileToBeReady = async (filePathArg: string): Promise<void> => { export const waitForFileToBeReady = (filePathArg: string): Promise<void> => {
if (!plugins.path.isAbsolute(filePathArg)) { return new Promise(async (resolve, reject) => {
filePathArg = plugins.path.resolve(filePathArg); let lastFileSize = -1;
} let fileIsStable = false;
const limitedArray = new plugins.lik.LimitedArray<number>(3);
let fileReady = false; const checkFileStability = async () => {
while (!fileReady) { let currentFileSize: number;
const stats = await plugins.fsExtra.stat(filePathArg); const deferred = plugins.smartpromise.defer();
limitedArray.addOne(stats.size); plugins.fs.stat(filePathArg, (err, stats) => {
if ( if (err) {
limitedArray.array.length < 3 || fileIsStable = true;
!( watcher.close();
limitedArray.array[0] === limitedArray.array[1] && reject(err);
limitedArray.array[1] === limitedArray.array[2] return;
) }
) { currentFileSize = stats.size;
await plugins.smartdelay.delayFor(5000); deferred.resolve();
} else { });
fileReady = true; await deferred.promise;
if (currentFileSize === lastFileSize) {
fileIsStable = true;
await plugins.smartdelay.delayFor(100);
resolve();
}
lastFileSize = currentFileSize;
};
const watcher = plugins.fs.watch(filePathArg, (eventType, filename) => {
if (eventType === 'change') {
checkFileStability();
}
});
watcher.on('error', (error) => {
watcher.close();
reject(error);
});
while (!fileIsStable) {
await checkFileStability();
if (!fileIsStable) {
await plugins.smartdelay.delayFor(5000);
}
} }
} watcher.close();
});
}; };

View File

@ -37,3 +37,159 @@ export const processDirectory = async (
} }
} }
}; };
/**
* Checks if a file is ready to be streamed (exists and is not empty).
*/
export const isFileReadyForStreaming = async (filePathArg: string): Promise<boolean> => {
try {
const stats = await plugins.fs.promises.stat(filePathArg);
return stats.size > 0;
} catch (error) {
if (error.code === 'ENOENT') { // File does not exist
return false;
}
throw error; // Rethrow other unexpected errors
}
};
/**
* Waits for a file to be ready for streaming (exists and is not empty).
*/
export const waitForFileToBeReadyForStreaming = (filePathArg: string): Promise<void> => {
return new Promise((resolve, reject) => {
// Normalize and resolve the file path
const filePath = plugins.path.resolve(filePathArg);
// Function to check file stats
const checkFile = (resolve: () => void, reject: (reason: any) => void) => {
plugins.fs.stat(filePath, (err, stats) => {
if (err) {
if (err.code === 'ENOENT') {
// File not found, wait and try again
return;
}
// Some other error occurred
return reject(err);
}
if (stats.size > 0) {
// File exists and is not empty, resolve the promise
resolve();
}
});
};
// Set up file watcher
const watcher = plugins.fs.watch(filePath, { persistent: false }, (eventType) => {
if (eventType === 'change' || eventType === 'rename') {
checkFile(resolve, reject);
}
});
// Check file immediately in case it's already ready
checkFile(resolve, reject);
// Error handling
watcher.on('error', (error) => {
watcher.close();
reject(error);
});
});
};
class SmartReadStream extends plugins.stream.Readable {
private watcher: plugins.fs.FSWatcher | null = null;
private lastReadSize: number = 0;
private endTimeout: NodeJS.Timeout | null = null;
private filePath: string;
private endDelay: number;
private reading: boolean = false;
constructor(filePath: string, endDelay = 60000, opts?: plugins.stream.ReadableOptions) {
super(opts);
this.filePath = filePath;
this.endDelay = endDelay;
}
private startWatching(): void {
this.watcher = plugins.fs.watch(this.filePath, (eventType) => {
if (eventType === 'change') {
this.resetEndTimeout();
}
});
this.watcher.on('error', (error) => {
this.cleanup();
this.emit('error', error);
});
}
private resetEndTimeout(): void {
if (this.endTimeout) clearTimeout(this.endTimeout);
this.endTimeout = setTimeout(() => this.checkForEnd(), this.endDelay);
}
private checkForEnd(): void {
plugins.fs.stat(this.filePath, (err, stats) => {
if (err) {
this.emit('error', err);
return;
}
if (this.lastReadSize === stats.size) {
this.push(null); // Signal the end of the stream
this.cleanup();
} else {
this.lastReadSize = stats.size;
this.resetEndTimeout();
if (!this.reading) {
// We only want to continue reading if we were previously waiting for more data
this.reading = true;
this._read(10000); // Try to read more data
}
}
});
}
private cleanup(): void {
if (this.endTimeout) clearTimeout(this.endTimeout);
if (this.watcher) this.watcher.close();
}
_read(size: number): void {
this.reading = true;
const chunkSize = Math.min(size, 16384); // Read in chunks of 16KB
const buffer = Buffer.alloc(chunkSize);
plugins.fs.open(this.filePath, 'r', (err, fd) => {
if (err) {
this.emit('error', err);
return;
}
plugins.fs.read(fd, buffer, 0, chunkSize, this.lastReadSize, (err, bytesRead, buffer) => {
if (err) {
this.emit('error', err);
return;
}
if (bytesRead > 0) {
this.lastReadSize += bytesRead;
this.push(buffer.slice(0, bytesRead)); // Push the data onto the stream
} else {
this.reading = false; // No more data to read for now
this.resetEndTimeout();
}
plugins.fs.close(fd, (err) => {
if (err) {
this.emit('error', err);
}
});
});
});
}
_destroy(error: Error | null, callback: (error: Error | null) => void): void {
this.cleanup();
callback(error);
}
}