122 lines
4.1 KiB
TypeScript
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();
|