FDLogUp/client/index.ts

122 lines
4.1 KiB
TypeScript

import debounce from 'just-debounce-it';
import { watch } from 'node:fs';
import { stat, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import readline from 'node:readline/promises';
import { parse } from 'ini';
import MDBReader from 'mdb-reader';
import { diff } from 'just-diff';
import { version } from './package.json';
console.log(`FDLogUp Client v${version}`);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
async function promptToClose(message: string) {
await rl.question(`== ${message}\n== Press enter to quit.`);
process.exit(0);
}
// Check for config.ini file
const configPath = join(process.cwd(), 'config.ini');
try {
const configStat = await stat(configPath);
if (configStat.isDirectory()) await promptToClose('The config file must be a file.');
} catch (e) {
await writeFile(configPath, [
'; The database file to upload, defaults to C:\\Users\\<user>\\Documents\\Affirmatech\\N3FJP Software\\ARRL-Field-Day\\LogData.mdb',
`; database_file = "${process.env.USERNAME ? `C:\\Users\\${process.env.USERNAME}\\Documents\\Affirmatech\\N3FJP Software\\ARRL-Field-Day\\LogData.mdb` : ''}"`,
'',
'; The endpoint to upload to',
'log_endpoint = "https://example.local/up"'
].join('\n'))
await promptToClose('Failed to find config file. A new one was created, so put in your variables in there.');
}
let databaseFile: string;
let logEndpoint: string;
try {
const config = parse(await readFile(configPath, { encoding: 'utf-8' }));
databaseFile = config.database_file;
logEndpoint = config.log_endpoint;
if (!logEndpoint) await promptToClose('A log endpoint is required in the config file.');
} catch (e) {
databaseFile = '';
await promptToClose('Failed to parse config file. You can remove the file and re-run this program if more problems occur.');
}
const dbPath = databaseFile ?? process.env.USERNAME ? join('C:/Users', process.env.USERNAME!, 'Documents/Affirmatech/N3FJP Software/ARRL-Field-Day/LogData.mdb') : '';
// Check for log file
try {
if (!dbPath) await promptToClose('A log path is needed to use this program.');
const fileStat = await stat(dbPath);
if (fileStat.isDirectory()) await promptToClose('The log path must point to a file.');
console.log(`\n== Following changes for the file "${dbPath}"\n== Ctrl-C to exit.\n`);
} catch (e) {
await promptToClose(`Failed to find log file at "${dbPath}"`);
}
// Upload logic
let lastData: Record<string, any> = {};
async function _upload() {
try {
console.log('-- Reading log...');
const reader = new MDBReader(await readFile(dbPath));
if (!reader.getTableNames().includes('tblContacts'))
return console.log('!! No "tblContacts" table present.');
const table = reader.getTable('tblContacts');
const tableData = table.getData<Record<string, string>>();
const data = tableData.reduce((p, r) => ({ ...p, [r.fldPrimaryKey]: r }), {} as Record<string, any>);
const differences = diff(lastData, data);
const changedIds = [...new Set(differences.filter((r) => r.op !== 'remove').map((r) => r.path[0]))];
const changedRows = changedIds.map((id) => data[id]);
if (changedIds.length === 0) return void console.log('-- No changes found.');
try {
console.log(`-> Uploading ${changedIds.length.toLocaleString()} row(s)...`);
const response = await fetch(logEndpoint, {
method: 'POST',
body: JSON.stringify(changedRows),
headers: {
'Content-Type': 'application/json'
}
});
console.log(`<- Recieved ${response.status} (${response.statusText})`);
if (response.status === 200) lastData = data;
} catch (e) {
console.log('-! Failed to send log');
console.error(e);
}
} catch (e) {
console.log('-! Failed to upload');
console.error(e);
}
}
const upload = debounce(_upload, 250);
const watcher = watch(dbPath, (event, filename) => {
if (event === 'rename') return void console.log(`== ${filename} was renamed, something might break`);
upload();
});
process.on("SIGINT", () => {
// close watcher when Ctrl-C is pressed
console.log('\n== Closing watcher...');
watcher.close();
process.exit(0);
});
rl.close();
// "Upload first" option
if (process.argv[2] === '--upload') _upload();