Files

205 lines
4.9 KiB
JavaScript

const net = require('net');
const ftp = require('basic-ftp');
const { Writable } = require('stream');
class FTPManager {
constructor(config) {
this.config = config.ps5;
this._isOnline = false;
this._lastChecked = 0;
this._checkingPromise = null;
this._heartbeatTimer = null;
this.startHeartbeat();
}
startHeartbeat(intervalMs = 8000) {
this.stopHeartbeat();
this.checkOnline(1500).catch(() => {});
this._heartbeatTimer = setInterval(() => {
this.checkOnline(1500).catch(() => {});
}, intervalMs);
if (this._heartbeatTimer && this._heartbeatTimer.unref) {
this._heartbeatTimer.unref();
}
}
stopHeartbeat() {
if (this._heartbeatTimer) {
clearInterval(this._heartbeatTimer);
this._heartbeatTimer = null;
}
}
async checkOnline(timeout = 1500) {
if (this._checkingPromise) {
return this._checkingPromise;
}
this._checkingPromise = new Promise((resolve) => {
const socket = new net.Socket();
let settled = false;
const finish = (online) => {
if (settled) return;
settled = true;
this._isOnline = online;
this._lastChecked = Date.now();
socket.destroy();
resolve(online);
};
socket.setTimeout(timeout);
socket.once('connect', () => finish(true));
socket.once('timeout', () => finish(false));
socket.once('error', () => finish(false));
try {
socket.connect(parseInt(this.config.port, 10) || 1337, this.config.host);
} catch {
finish(false);
}
}).finally(() => {
this._checkingPromise = null;
});
return this._checkingPromise;
}
isOnline() {
return !!this._isOnline;
}
updateConfig(ps5Config) {
this.config = { ...this.config, ...ps5Config };
this._isOnline = false;
this.checkOnline(1500).catch(() => {});
}
async getClient() {
const client = new ftp.Client();
client.ftp.verbose = false;
client.ftp.timeout = this.config.timeout || 6000;
try {
await client.access({
host: this.config.host,
port: parseInt(this.config.port, 10) || 1337,
user: this.config.user || 'anonymous',
password: this.config.password || '',
secure: false,
});
return client;
} catch (err) {
client.close();
throw err;
}
}
async testConnection(host, port) {
const client = new ftp.Client();
client.ftp.verbose = false;
client.ftp.timeout = 4000;
try {
await client.access({
host: host || this.config.host,
port: parseInt(port || this.config.port, 10) || 1337,
user: this.config.user || 'anonymous',
password: this.config.password || '',
secure: false,
});
// Try checking if /user or / exists using cd for ftpsrv compatibility
let rootList = [];
try {
await client.cd('/user');
rootList = await client.list();
} catch {
await client.cd('/');
rootList = await client.list();
}
client.close();
if (!host || host === this.config.host) {
this._isOnline = true;
this._lastChecked = Date.now();
}
return {
success: true,
message: 'Successfully connected to PS5 FTP server!',
itemsFound: rootList.length,
};
} catch (err) {
client.close();
if (!host || host === this.config.host) {
this._isOnline = false;
this._lastChecked = Date.now();
}
return {
success: false,
message: `Connection failed: ${err.message}`,
};
}
}
async list(remotePath) {
const client = await this.getClient();
try {
if (remotePath) {
await client.cd(remotePath);
}
return await client.list();
} finally {
client.close();
}
}
async size(remotePath) {
const client = await this.getClient();
try {
return await client.size(remotePath);
} finally {
client.close();
}
}
async getBuffer(remotePath) {
const client = await this.getClient();
const chunks = [];
const memoryStream = new Writable({
write(chunk, encoding, callback) {
chunks.push(chunk);
callback();
},
});
try {
await client.downloadTo(memoryStream, remotePath);
return Buffer.concat(chunks);
} finally {
client.close();
}
}
async streamTo(remotePath, outputStream, startAt = 0) {
const client = await this.getClient();
try {
// client.downloadTo supports startAt offset (FTP REST command)
await client.downloadTo(outputStream, remotePath, startAt);
} finally {
client.close();
}
}
async downloadFile(remotePath, localPath) {
const client = await this.getClient();
try {
await client.downloadTo(localPath, remotePath);
return true;
} finally {
client.close();
}
}
}
module.exports = FTPManager;