Merge pull request #673 from DJ2LS/dev-nsis-installer

NSIS Installer for Windows
This commit is contained in:
DJ2LS 2024-03-04 07:14:36 -08:00 committed by GitHub
commit 9520bb4689
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 413 additions and 675 deletions

View File

@ -14,6 +14,15 @@ jobs:
with:
python-version: "3.11"
- name: Electron Builder
working-directory: gui
run: |
npm i
npm run build
- name: LIST ALL FILES
run: ls -R
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
@ -36,9 +45,9 @@ jobs:
run: ls -R
- name: Create installer
uses: joncloud/makensis-action@v4
uses: joncloud/makensis-action@v4.1
with:
script-file: "freedata-server-nsis-config.nsi"
script-file: "freedata-nsis-config.nsi"
arguments: '/V3'
- name: LIST ALL FILES
@ -47,12 +56,14 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: 'FreeData-Server-Installer'
path: ./FreeData-Server-Installer.exe
name: 'FreeDATA-Installer'
path: ./FreeDATA-Installer.exe
- name: Upload Installer to Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/v')
with:
draft: true
files: ./FreeData-Server-Installer.exe
files: ./FreeDATA-Installer.exe
tag_name: ${{ github.ref_name }}
name: 'FreeDATA-Installer-${{ github.ref_name }}'

View File

@ -48,7 +48,7 @@ jobs:
brew install portaudio
python -m pip install --upgrade pip
pip3 install pyaudio
export PYTHONPATH=/opt/homebrew/opt/portaudio/lib/:$PYTHONPATH
export PYTHONPATH=/usr/local/lib/:$PYTHONPATH
- name: Install Python dependencies
run: |

132
freedata-nsis-config.nsi Normal file
View File

@ -0,0 +1,132 @@
!include "MUI2.nsh"
; Request administrative rights
RequestExecutionLevel admin
; The name and file name of the installer
Name "FreeDATA Installer"
OutFile "FreeDATA-Installer.exe"
; Default installation directory for the server
InstallDir "$LOCALAPPDATA\FreeDATA"
; Registry key to store the installation directory
InstallDirRegKey HKCU "Software\FreeDATA" "Install_Dir"
; Modern UI settings
!define MUI_ABORTWARNING
; Installer interface settings
!define MUI_ICON "documentation\icon.ico"
!define MUI_UNICON "documentation\icon.ico" ; Icon for the uninstaller
; Define the welcome page text
!define MUI_WELCOMEPAGE_TEXT "Welcome to the FreeDATA Setup Wizard. This wizard will guide you through the installation process."
!define MUI_FINISHPAGE_TEXT "Folder: $INSTDIR"
!define MUI_DIRECTORYPAGE_TEXT_TOP "Please select the installation folder. It's recommended to use the suggested one to avoid permission problems."
; Pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "LICENSE"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
; Uninstaller
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
; Language (you can choose and configure the language(s) you want)
!insertmacro MUI_LANGUAGE "English"
; Installer Sections
Section "FreeData Server" SEC01
; Set output path to the installation directory
SetOutPath $INSTDIR\freedata-server
; Check if "config.ini" exists and back it up
IfFileExists $INSTDIR\freedata-server\config.ini backupConfig
doneBackup:
; Add your application files here
File /r "modem\server.dist\*"
; Restore the original "config.ini" if it was backed up
IfFileExists $INSTDIR\freedata-server\config.ini.bak restoreConfig
; Create a shortcut in the user's desktop
CreateShortCut "$DESKTOP\FreeDATA Server.lnk" "$INSTDIR\freedata-server\freedata-server.exe"
; Create Uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
; Create a Start Menu directory
CreateDirectory "$SMPROGRAMS\FreeDATA"
; Create shortcut in the Start Menu directory
CreateShortCut "$SMPROGRAMS\FreeDATA\FreeDATA Server.lnk" "$INSTDIR\freedata-server\freedata-server.exe"
; Create an Uninstall shortcut
CreateShortCut "$SMPROGRAMS\FreeDATA\Uninstall FreeDATA.lnk" "$INSTDIR\Uninstall.exe"
; Backup "config.ini" before overwriting files
backupConfig:
Rename $INSTDIR\freedata-server\config.ini $INSTDIR\freedata-server\config.ini.bak
Goto doneBackup
; Restore the original "config.ini"
restoreConfig:
Delete $INSTDIR\freedata-server\config.ini
Rename $INSTDIR\freedata-server\config.ini.bak $INSTDIR\freedata-server\config.ini
SectionEnd
Section "FreeData x64 GUI" SEC02
; Set output path to the GUI installation directory
SetOutPath $INSTDIR\freedata-gui
; Add GUI files here
File /r "gui\release\win-unpacked\*"
; Create a shortcut on the desktop for the GUI
CreateShortCut "$DESKTOP\FreeDATA GUI.lnk" "$INSTDIR\freedata-gui\freedata.exe"
; Create a start menu shortcut
CreateShortCut "$SMPROGRAMS\FreeDATA\FreeDATA GUI.lnk" "$INSTDIR\freedata-gui\freedata.exe"
; Create an Uninstall shortcut
CreateShortCut "$SMPROGRAMS\FreeDATA\Uninstall FreeDATA.lnk" "$INSTDIR\Uninstall.exe"
SectionEnd
; Uninstaller Section
Section "Uninstall"
; Delete files and directories for the server
Delete $INSTDIR\freedata-server\*.*
RMDir /r $INSTDIR\freedata-server
; Delete files and directories for the GUI
Delete $INSTDIR\freedata-gui\*.*
RMDir /r $INSTDIR\freedata-gui
; Remove the desktop shortcuts
Delete "$DESKTOP\FreeDATA Server.lnk"
Delete "$DESKTOP\FreeDATA GUI.lnk"
; Remove Start Menu shortcuts
Delete "$SMPROGRAMS\FreeDATA\*.*"
RMDir "$SMPROGRAMS\FreeDATA"
; Attempt to delete the uninstaller itself
Delete $EXEPATH
; Now remove the installation directory if it's empty
RMDir /r $INSTDIR
SectionEnd

View File

@ -1,102 +0,0 @@
!include "MUI2.nsh"
; Request administrative rights
RequestExecutionLevel admin
; The name and file name of the installer
Name "FreeData Server"
OutFile "FreeData-Server-Installer.exe"
; Default installation directory
; InstallDir "$PROGRAMFILES\FreeData\freedata-server"
InstallDir "$LOCALAPPDATA\FreeData\freedata-server"
; Registry key to store the installation directory
InstallDirRegKey HKCU "Software\FreeData\freedata-server" "Install_Dir"
; Modern UI settings
!define MUI_ABORTWARNING
; Installer interface settings
!define MUI_ICON "documentation\icon.ico"
!define MUI_UNICON "documentation\icon.ico" ; Icon for the uninstaller
; Define the welcome page text
!define MUI_WELCOMEPAGE_TEXT "Welcome to the FreeData Server Setup Wizard. This wizard will guide you through the installation process."
!define MUI_FINISHPAGE_TEXT "Folder: $INSTDIR"
!define MUI_DIRECTORYPAGE_TEXT_TOP "Please select the installation folder. Its recommended using the suggested one for avoiding permission problems."
; Pages
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "LICENSE"
;!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
; Uninstaller
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
; Language (you can choose and configure the language(s) you want)
!insertmacro MUI_LANGUAGE "English"
; Installer Sections
Section "FreeData Server" SEC01
; Set output path to the installation directory
SetOutPath $INSTDIR
; Check if "config.ini" exists and back it up
IfFileExists $INSTDIR\config.ini backupConfig
doneBackup:
; Add your application files here
File /r "modem\server.dist\*.*"
; Restore the original "config.ini" if it was backed up
IfFileExists $INSTDIR\config.ini.bak restoreConfig
; Create a shortcut in the user's desktop
CreateShortCut "$DESKTOP\FreeData Server.lnk" "$INSTDIR\freedata-server.exe"
; Create Uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
; Backup "config.ini" before overwriting files
backupConfig:
Rename $INSTDIR\config.ini $INSTDIR\config.ini.bak
Goto doneBackup
; Restore the original "config.ini"
restoreConfig:
Delete $INSTDIR\config.ini
Rename $INSTDIR\config.ini.bak $INSTDIR\config.ini
SectionEnd
; Uninstaller Section
Section "Uninstall"
; Delete files and directories
Delete $INSTDIR\freedata-server.exe
RMDir /r $INSTDIR
; Remove the shortcut
Delete "$DESKTOP\FreeData Server.lnk"
; Additional uninstallation commands here
SectionEnd

View File

@ -45,18 +45,12 @@
"icon": "build/icon.png",
"target": [
{
"target": "nsis",
"target": "portable",
"arch": ["arm64", "x64"]
}
],
"artifactName": "${productName}-GUI-Windows-${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": true
},
"linux": {
"category": "Development",
"target": [

View File

@ -1,7 +1,6 @@
import { app, BrowserWindow, shell, ipcMain } from "electron";
import { release, platform } from "node:os";
import { join } from "node:path";
import { autoUpdater } from "electron-updater";
import { release, platform } from "os";
import { join, dirname } from "path";
import { existsSync } from "fs";
import { spawn } from "child_process";
@ -20,7 +19,6 @@ process.env.DIST = join(process.env.DIST_ELECTRON, "../dist");
process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
? join(process.env.DIST_ELECTRON, "../public")
: process.env.DIST;
process.env.FDUpdateAvail = "0";
// Disable GPU Acceleration for Windows 7
if (release().startsWith("6.1")) app.disableHardwareAcceleration();
@ -40,7 +38,7 @@ if (!app.requestSingleInstanceLock()) {
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
// set daemon process var
var daemonProcess = null;
var serverProcess = null;
let win: BrowserWindow | null = null;
// Here, you can also use other preload
const preload = join(__dirname, "../preload/index.js");
@ -75,9 +73,9 @@ async function createWindow() {
}
// Test actively push message to the Electron-Renderer
win.webContents.on("did-finish-load", () => {
win?.webContents.send("main-process-message", new Date().toLocaleString());
});
//win.webContents.on("did-finish-load", () => {
// win?.webContents.send("main-process-message", new Date().toLocaleString());
//});
// Make all links open with the browser, not with the application
win.webContents.setWindowOpenHandler(({ url }) => {
@ -87,12 +85,7 @@ async function createWindow() {
// win.webContents.on('will-navigate', (event, url) => { }) #344
win.once("ready-to-show", () => {
//autoUpdater.logger = log.scope("updater");
//autoUpdater.channel = config.update_channel;
autoUpdater.autoInstallOnAppQuit = false;
autoUpdater.autoDownload = true;
autoUpdater.checkForUpdatesAndNotify();
//autoUpdater.quitAndInstall();
//
});
}
@ -102,68 +95,61 @@ app.whenReady().then(() => {
console.log(platform());
//Generate daemon binary path
var daemonPath = "";
var serverPath = "";
console.log(process.env);
// Attempt to find Installation Folder
console.log(app.getAppPath());
console.log(join(app.getAppPath(), "..", ".."));
console.log(join(app.getAppPath(), "..", "..", ".."));
//var basePath = join(app.getAppPath(), '..', '..', '..') || join(process.env.PWD, '..') || join(process.env.INIT_CWD, '..') || join(process.env.DIST, '..', '..', '..');
var basePath = join(app.getAppPath(), "..", "..", "..");
switch (platform().toLowerCase()) {
case "darwin":
daemonPath = join(process.resourcesPath, "modem", "freedata-server");
case "linux":
daemonPath = join(process.resourcesPath, "modem", "freedata-server");
break;
//case "darwin":
//serverPath = join(basePath, "freedata-server", "freedata-server.exe");
//serverProcess = spawn(serverPath, [], { detached: true });
//serverProcess.unref(); // Allow the server process to continue running independently of the parent process
// break;
//case "linux":
//serverPath = join(basePath, "freedata-server", "freedata-server.exe");
//serverProcess = spawn(serverPath, [], { detached: true });
//serverProcess.unref(); // Allow the server process to continue running independently of the parent process
// break;
case "win32":
daemonPath = join(process.resourcesPath, "modem", "freedata-server.exe");
break;
case "win64":
daemonPath = join(process.resourcesPath, "modem", "freedata-server.exe");
serverPath = join(basePath, "freedata-server", "freedata-server.exe");
console.log(`Starting server with path: ${serverPath}`);
serverProcess = spawn(
"cmd.exe",
["/c", "start", "cmd.exe", "/c", serverPath],
{ shell: true },
);
console.log(`Started server | PID: ${serverProcess.pid}`);
break;
default:
console.log("Unhandled OS Platform: ", platform());
serverProcess = null;
serverPath = null;
break;
}
//Start daemon binary if it exists
if (existsSync(daemonPath)) {
console.log("Starting freedata-server binary");
console.log("daemonPath:", daemonPath);
console.log("CWD:", join(daemonPath, ".."));
/*
var daemonProcess = spawn("freedata-server", [], {
cwd: join(process.env.DIST, "modem"),
shell: true
});
*/
/*
daemonProcess = spawn(daemonPath, [], {
shell: true
});
console.log(daemonProcess)
*/
daemonProcess = spawn(daemonPath, [], {});
serverProcess.on("error", (err) => {
console.error("Failed to start server process:", err);
serverProcess = null;
serverPath = null;
});
serverProcess.stdout.on("data", (data) => {
//console.log(`stdout: ${data}`);
});
// return process messages
daemonProcess.on("error", (err) => {
//daemonProcessLog.error(`error when starting daemon: ${err}`);
console.log(err);
});
daemonProcess.on("message", () => {
// daemonProcessLog.info(`${data}`);
});
daemonProcess.stdout.on("data", () => {
// daemonProcessLog.info(`${data}`);
});
daemonProcess.stderr.on("data", (data) => {
// daemonProcessLog.info(`${data}`);
console.log(data);
});
daemonProcess.on("close", (code) => {
// daemonProcessLog.warn(`daemonProcess exited with code ${code}`);
});
} else {
daemonProcess = null;
daemonPath = null;
console.log("Daemon binary doesn't exist--normal for dev environments.");
}
serverProcess.stderr.on("data", (data) => {
console.error(`stderr: ${data}`);
});
});
//)
app.on("before-quit", () => {
close_sub_processes();
});
app.on("window-all-closed", () => {
@ -205,104 +191,33 @@ ipcMain.handle("open-win", (_, arg) => {
}
});
//restart and install udpate
ipcMain.on("request-restart-and-install-update", (event, data) => {
close_sub_processes();
autoUpdater.quitAndInstall();
});
// LISTENER FOR UPDATER EVENTS
autoUpdater.on("update-available", (info) => {
process.env.FDUpdateAvail = "1";
console.log("update available");
let arg = {
status: "update-available",
info: info,
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("update-not-available", (info) => {
console.log("update not available");
let arg = {
status: "update-not-available",
info: info,
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("update-downloaded", (info) => {
process.env.FDUpdateAvail = "1";
console.log("update downloaded");
let arg = {
status: "update-downloaded",
info: info,
};
win.webContents.send("action-updater", arg);
// we need to call this at this point.
// if an update is available and we are force closing the app
// the entire screen crashes...
//console.log('quit application and install update');
//autoUpdater.quitAndInstall();
});
autoUpdater.on("checking-for-update", () => {
console.log("checking for update");
let arg = {
status: "checking-for-update",
version: app.getVersion(),
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("download-progress", (progress) => {
let arg = {
status: "download-progress",
progress: progress,
};
win.webContents.send("action-updater", arg);
});
autoUpdater.on("error", (error) => {
console.log("update error");
let arg = {
status: "error",
progress: error,
};
win.webContents.send("action-updater", arg);
console.log("AUTO UPDATER : " + error);
});
function close_sub_processes() {
console.log("closing sub processes");
console.log("Closing sub processes...");
// closing the modem binary if not closed when closing application and also our daemon which has been started by the gui
try {
if (daemonProcess != null) {
daemonProcess.kill();
}
} catch (e) {
console.log(e);
}
if (serverProcess != null) {
try {
console.log(`Killing server process with PID: ${serverProcess.pid}`);
console.log("closing modem and daemon");
try {
if (platform() == "win32") {
spawn("Taskkill", ["/IM", "freedata-modem.exe", "/F"]);
spawn("Taskkill", ["/IM", "freedata-server.exe", "/F"]);
}
switch (platform().toLowerCase()) {
//case "darwin":
// process.kill(serverProcess.pid);
// break;
//case "linux":
// process.kill(serverProcess.pid);
// break;
case "win32":
// For Windows, use taskkill to ensure all child processes are also terminated
spawn("taskkill", ["/pid", serverProcess.pid.toString(), "/f", "/t"]);
break;
if (platform() == "linux") {
spawn("pkill", ["-9", "freedata-modem"]);
spawn("pkill", ["-9", "freedata-server"]);
default:
console.log("Unhandled OS Platform: ", platform());
serverProcess = null;
serverPath = null;
break;
}
} catch (error) {
console.error(`Error killing server process: ${error}`);
}
if (platform() == "darwin") {
spawn("pkill", ["-9", "freedata-modem"]);
spawn("pkill", ["-9", "freedata-server"]);
}
} catch (e) {
console.log(e);
}
}

View File

@ -111,83 +111,4 @@ window.onmessage = (ev) => {
ev.data.payload === "removeLoading" && removeLoading();
};
setTimeout(removeLoading, 4999);
// IPC ACTION FOR AUTO UPDATER
ipcRenderer.on("action-updater", (event, arg) => {
if (arg.status == "download-progress") {
var progressinfo =
"(" +
Math.round(arg.progress.transferred / 1024) +
"kB /" +
Math.round(arg.progress.total / 1024) +
"kB)" +
" @ " +
Math.round(arg.progress.bytesPerSecond / 1024) +
"kByte/s";
document.getElementById("UpdateProgressInfo").innerHTML = progressinfo;
document
.getElementById("UpdateProgressBar")
.setAttribute("aria-valuenow", arg.progress.percent);
document
.getElementById("UpdateProgressBar")
.setAttribute("style", "width:" + arg.progress.percent + "%;");
}
if (arg.status == "checking-for-update") {
//document.title = document.title + ' - v' + arg.version;
//updateTitle(
// config.myCall,
// config.tnc_host,
// config.tnc_port,
// " -v " + arg.version,
//);
document.getElementById("updater_status").innerHTML =
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
document.getElementById("updater_status").className =
"btn btn-secondary btn-sm";
document.getElementById("update_and_install").style.display = "none";
}
if (arg.status == "update-downloaded") {
document.getElementById("update_and_install").removeAttribute("style");
document.getElementById("updater_status").innerHTML =
'<i class="bi bi-cloud-download ms-1 me-1" style="color: white;"></i>';
document.getElementById("updater_status").className =
"btn btn-success btn-sm";
// HERE WE NEED TO RUN THIS SOMEHOW...
//mainLog.info('quit application and install update');
//autoUpdater.quitAndInstall();
}
if (arg.status == "update-not-available") {
document.getElementById("updater_last_version").innerHTML =
arg.info.releaseName;
document.getElementById("updater_last_update").innerHTML =
arg.info.releaseDate;
document.getElementById("updater_release_notes").innerHTML =
arg.info.releaseNotes;
document.getElementById("updater_status").innerHTML =
'<i class="bi bi-check2-square ms-1 me-1" style="color: white;"></i>';
document.getElementById("updater_status").className =
"btn btn-success btn-sm";
document.getElementById("update_and_install").style.display = "none";
}
if (arg.status == "update-available") {
document.getElementById("updater_status").innerHTML =
'<i class="bi bi-hourglass-split ms-1 me-1" style="color: white;"></i>';
document.getElementById("updater_status").className =
"btn btn-warning btn-sm";
document.getElementById("update_and_install").style.display = "none";
}
if (arg.status == "error") {
document.getElementById("updater_status").innerHTML =
'<i class="bi bi-exclamation-square ms-1 me-1" style="color: white;"></i>';
document.getElementById("updater_status").className =
"btn btn-danger btn-sm";
document.getElementById("update_and_install").style.display = "none";
}
});
setTimeout(removeLoading, 3999);

View File

@ -13,7 +13,8 @@
"release": "vue-tsc --noEmit && vite build && electron-builder -p onTag",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue src",
"lint-fix": "eslint --ext .js,.vue --fix src"
"lint-fix": "eslint --ext .js,.vue --fix src",
"install-deps": "npm install && npm update"
},
"repository": {
"type": "git",

View File

@ -21,15 +21,13 @@ import main_modem_healthcheck from "./main_modem_healthcheck.vue";
import Dynamic_components from "./dynamic_components.vue";
import { getFreedataMessages } from "../js/api";
import { getRemote } from "../store/settingsStore.js";
import { loadAllData } from "../js/eventHandler";
</script>
<template>
<!-------------------------------- INFO TOASTS ---------------->
<div
aria-live="polite"
aria-atomic="true"
class="position-relative z-3"
>
<div aria-live="polite" aria-atomic="true" class="position-relative z-3">
<div
class="toast-container position-absolute top-0 end-0 p-3"
id="mainToastContainer"
@ -47,6 +45,7 @@ import { getFreedataMessages } from "../js/api";
id="main-list-tab"
role="tablist"
style="margin-top: 100px"
@click="loadAllData"
>
<main_modem_healthcheck />
@ -111,6 +110,7 @@ import { getFreedataMessages } from "../js/api";
role="tab"
aria-controls="list-settings"
title="Settings"
@click="loadAllData"
><i class="bi bi-gear-wide-connected h3"></i
></a>
</div>

View File

@ -2,8 +2,6 @@
import { Modal } from "bootstrap";
import { onMounted } from "vue";
import settings_updater_core from "./settings_updater_core.vue";
import { setActivePinia } from "pinia";
import pinia from "../store/index";
setActivePinia(pinia);
@ -14,6 +12,11 @@ import { sendModemCQ } from "../js/api.js";
import { useStateStore } from "../store/stateStore.js";
const state = useStateStore(pinia);
import { useAudioStore } from "../store/audioStore";
const audioStore = useAudioStore();
import { useSerialStore } from "../store/serialStore";
const serialStore = useSerialStore();
import {
getVersion,
setConfig,
@ -21,11 +24,8 @@ import {
stopModem,
getModemState,
} from "../js/api";
import { audioInputOptions, audioOutputOptions } from "../js/deviceFormHelper";
import { serialDeviceOptions } from "../js/deviceFormHelper";
const version = import.meta.env.PACKAGE_VERSION;
var updateAvailable = process.env.FDUpdateAvail;
// start modemCheck modal once on startup
onMounted(() => {
@ -203,6 +203,7 @@ function testHamlib() {
</button>
</label>
</div>
<!-- Audio Input Device -->
<div class="input-group input-group-sm mb-1">
<label class="input-group-text w-50"
@ -215,10 +216,10 @@ function testHamlib() {
v-model="settings.remote.AUDIO.input_device"
>
<option
v-for="option in audioInputOptions()"
v-bind:value="option.id"
v-for="device in audioStore.audioInputs"
:value="device.id"
>
{{ option.name }} [{{ option.api }}]
{{ device.name }} [{{ device.api }}]
</option>
</select>
</div>
@ -235,10 +236,10 @@ function testHamlib() {
v-model="settings.remote.AUDIO.output_device"
>
<option
v-for="option in audioOutputOptions()"
v-bind:value="option.id"
v-for="device in audioStore.audioOutputs"
:value="device.id"
>
{{ option.name }} [{{ option.api }}]
{{ device.name }} [{{ device.api }}]
</option>
</select>
</div>
@ -310,18 +311,16 @@ function testHamlib() {
>
<select
class="form-select form-select-sm"
aria-label=".form-select-sm"
id="hamlib_deviceport"
style="width: 7rem"
@change="onChange"
v-model="settings.remote.RADIO.serial_port"
class="form-select form-select-sm"
>
<option
v-for="option in serialDeviceOptions()"
v-bind:value="option.port"
v-for="device in serialStore.serialDevices"
:value="device.port"
:key="device.port"
>
{{ option.description }}
{{ device.description }}
</option>
</select>
</div>
@ -392,18 +391,6 @@ function testHamlib() {
data-bs-toggle="collapse"
>
Version
<span
class="badge ms-2"
:class="
updateAvailable === '1' ? 'bg-warning' : 'bg-success'
"
>
{{
updateAvailable === "1"
? "Update available ! ! ! !"
: "Current"
}}</span
>
</button>
</h2>
<div
@ -426,9 +413,6 @@ function testHamlib() {
>
Modem version | {{ state.modem_version }}
</button>
<div :class="updateAvailable === '1' ? '' : 'd-none'">
<settings_updater_core />
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
import settings_updater from "./settings_updater.vue";
import settings_station from "./settings_station.vue";
import settings_gui from "./settings_gui.vue";
import settings_chat from "./settings_chat.vue";
@ -23,20 +22,6 @@ import settings_exp from "./settings_exp.vue";
<li class="nav-item" role="presentation">
<button
class="nav-link active"
id="updater-tab"
data-bs-toggle="tab"
data-bs-target="#updater"
type="button"
role="tab"
aria-controls="home"
aria-selected="true"
>
Updater
</button>
</li>
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="station-tab"
data-bs-toggle="tab"
data-bs-target="#station"
@ -141,23 +126,10 @@ import settings_exp from "./settings_exp.vue";
>
<!-- SETTINGS Nav Tab panes -->
<!-- Updater tab contents-->
<div class="tab-content">
<div
class="tab-pane active"
id="updater"
role="tabpanel"
aria-labelledby="updater-tab"
tabindex="0"
>
<settings_updater />
</div>
</div>
<!-- Station tab contents-->
<div class="tab-content">
<div
class="tab-pane"
class="tab-pane active"
id="station"
role="tabpanel"
aria-labelledby="station-tab"

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import { serialDeviceOptions } from "../js/deviceFormHelper";
import { useSerialStore } from "../store/serialStore";
const serialStore = useSerialStore();
</script>
<template>
@ -21,13 +22,13 @@ import { serialDeviceOptions } from "../js/deviceFormHelper";
<div class="input-group input-group-sm mb-1">
<span class="input-group-text" style="width: 180px">Rigctld port</span>
<input
type="text"
type="number"
class="form-control"
placeholder="rigctld port"
id="hamlib_rigctld_port"
aria-label="Device Port"
@change="onChange"
v-model="settings.remote.RIGCTLD.port"
v-model.number="settings.remote.RIGCTLD.port"
/>
</div>
@ -334,18 +335,16 @@ import { serialDeviceOptions } from "../js/deviceFormHelper";
<span class="input-group-text" style="width: 180px">Radio port</span>
<select
class="form-select form-select-sm"
aria-label=".form-select-sm"
id="hamlib_deviceport"
style="width: 7rem"
@change="onChange"
v-model="settings.remote.RADIO.serial_port"
class="form-select form-select-sm"
>
<option
v-for="option in serialDeviceOptions()"
v-bind:value="option.port"
v-for="device in serialStore.serialDevices"
:value="device.port"
:key="device.port"
>
{{ option.description }}
{{ device.description }}
</option>
</select>
</div>
@ -422,18 +421,18 @@ import { serialDeviceOptions } from "../js/deviceFormHelper";
<div class="input-group input-group-sm mb-1">
<span class="input-group-text" style="width: 180px">PTT device port</span>
<select
class="form-select form-select-sm"
aria-label=".form-select-sm"
id="hamlib_ptt_port"
@change="onChange"
v-model="settings.remote.RADIO.ptt_port"
class="form-select form-select-sm"
>
<option
v-for="option in serialDeviceOptions()"
v-bind:value="option.port"
v-for="device in serialStore.serialDevices"
:value="device.port"
:key="device.port"
>
{{ option.description }}
{{ device.description }}
</option>
</select>
</div>

View File

@ -6,7 +6,9 @@ import { useStateStore } from "../store/stateStore.js";
const state = useStateStore(pinia);
import { startModem, stopModem } from "../js/api.js";
import { audioInputOptions, audioOutputOptions } from "../js/deviceFormHelper";
import { useAudioStore } from "../store/audioStore";
const audioStore = useAudioStore();
</script>
<template>
@ -74,8 +76,8 @@ import { audioInputOptions, audioOutputOptions } from "../js/deviceFormHelper";
@change="onChange"
v-model="settings.remote.AUDIO.input_device"
>
<option v-for="option in audioInputOptions()" v-bind:value="option.id">
{{ option.name }} [{{ option.api }}]
<option v-for="device in audioStore.audioInputs" :value="device.id">
{{ device.name }} [{{ device.api }}]
</option>
</select>
</div>
@ -89,11 +91,12 @@ import { audioInputOptions, audioOutputOptions } from "../js/deviceFormHelper";
@change="onChange"
v-model="settings.remote.AUDIO.output_device"
>
<option v-for="option in audioOutputOptions()" v-bind:value="option.id">
{{ option.name }} [{{ option.api }}]
<option v-for="device in audioStore.audioOutputs" :value="device.id">
{{ device.name }} [{{ device.api }}]
</option>
</select>
</div>
<!-- Audio rx level-->
<div class="input-group input-group-sm mb-1">
<span class="input-group-text w-25">RX Audio Level</span>

View File

@ -1,6 +1,5 @@
<script setup lang="ts">
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
import { serialDeviceOptions } from "../js/deviceFormHelper";
</script>
<template>

View File

@ -1,16 +0,0 @@
<script setup lang="ts">
import settings_updater_core from "./settings_updater_core.vue";
</script>
<template>
<div>
<div class="alert alert-warning" role="alert">
The updater might not working, yet! Please update manually if you are running into problems!
</div>
<div class="alert alert-warning" role="alert">
The updater doesnt contain the server related parts, yet! We are discussing this topic actually, feel free contributing with your opinion on Discord!
</div>
<settings_updater_core />
</div>
</template>

View File

@ -1,102 +0,0 @@
<script setup lang="ts">
import { setActivePinia } from "pinia";
import pinia from "../store/index";
setActivePinia(pinia);
import { useStateStore } from "../store/stateStore.js";
import { settingsStore } from "../store/settingsStore";
import { onMounted } from "vue";
import { ipcRenderer } from "electron";
const state = useStateStore(pinia);
onMounted(() => {
window.addEventListener("DOMContentLoaded", () => {
// we are using this area for implementing the electron runUpdater
// we need access to DOM for displaying updater results in GUI
// close app, update and restart
document
.getElementById("update_and_install")
.addEventListener("click", () => {
ipcRenderer.send("request-restart-and-install-update");
});
});
});
</script>
<template>
<div class="card m-2">
<div class="card-header p-1 d-flex">
<div class="container">
<div class="row">
<div class="col-1">
<i class="bi bi-cloud-download" style="font-size: 1.2rem"></i>
</div>
<div class="col-3">
<strong class="fs-5">Updater</strong>
</div>
<div class="col-7">
<div class="progress w-100 ms-1 m-1">
<div
class="progress-bar"
style="width: 0%"
role="progressbar"
id="UpdateProgressBar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100"
>
<span id="UpdateProgressInfo"></span>
</div>
</div>
</div>
<div class="col-1 text-end">
<button
type="button"
id="openHelpModalUpdater"
data-bs-toggle="modal"
data-bs-target="#updaterHelpModal"
class="btn m-0 p-0 border-0"
>
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
</button>
</div>
</div>
</div>
</div>
<div class="card-body p-2 mb-1">
<button
class="btn btn-secondary btn-sm ms-1 me-1"
id="updater_channel"
type="button"
disabled
>
Update channel:&nbsp; {{ settingsStore.local.update_channel }}
</button>
<button
class="btn btn-secondary btn-sm ms-1"
id="updater_status"
type="button"
disabled
>
...
</button>
<button
class="btn btn-secondary btn-sm ms-1"
id="updater_changelog"
type="button"
data-bs-toggle="modal"
data-bs-target="#updaterReleaseNotes"
>
Changelog
</button>
<button
class="btn btn-primary btn-sm ms-1"
id="update_and_install"
type="button"
style="display: none"
>
Install & Restart
</button>
</div>
</div>
</template>

View File

@ -1,53 +0,0 @@
import { getAudioDevices, getSerialDevices } from "./api";
let audioDevices = await getAudioDevices();
let serialDevices = await getSerialDevices();
//Dummy device data sent if unable to get devices from modem to prevent GUI crash
const skel = JSON.parse(`
[{
"api": "MME",
"id": "0000",
"name": "No devices received from modem",
"native_index": 0
}]`);
export function loadAudioDevices() {
getAudioDevices().then((devices) => {
audioDevices = devices;
});
}
export function loadSerialDevices() {
getSerialDevices().then((devices) => {
serialDevices = devices;
});
}
export function audioInputOptions() {
if (audioDevices === undefined) {
return skel;
}
return audioDevices.in;
}
export function audioOutputOptions() {
if (audioDevices === undefined) {
return skel;
}
return audioDevices.out;
}
export function serialDeviceOptions() {
//Return ignore option if no serialDevices
if (serialDevices === undefined)
return [{ description: "-- ignore --", port: "ignore" }];
if (serialDevices.findIndex((device) => device.port == "ignore") == -1) {
//Add an ignore option for rig and ptt for transceivers that don't require them
serialDevices.push({ description: "-- ignore --", port: "ignore" });
}
return serialDevices;
}

View File

@ -8,16 +8,15 @@ import {
} from "./chatHandler";
*/
import { displayToast } from "./popupHandler";
import {
getFreedataMessages,
getConfig,
getAudioDevices,
getSerialDevices,
getModemState,
} from "./api";
import { getFreedataMessages, getModemState, getAudioDevices } from "./api";
import { processFreedataMessages } from "./messagesHandler.ts";
import { processRadioStatus } from "./radioHandler.ts";
import { useAudioStore } from "../store/audioStore";
const audioStore = useAudioStore();
import { useSerialStore } from "../store/serialStore";
const serialStore = useSerialStore();
// ----------------- init pinia stores -------------
import { setActivePinia } from "pinia";
import pinia from "../store/index";
@ -30,6 +29,17 @@ import {
getRemote,
} from "../store/settingsStore.js";
export function loadAllData() {
getModemState();
getRemote();
getOverallHealth();
audioStore.loadAudioDevices();
serialStore.loadSerialDevices();
getFreedataMessages();
processFreedataMessages();
processRadioStatus();
}
export function connectionFailed(endpoint, event) {
stateStore.modem_connection = "disconnected";
}
@ -95,12 +105,7 @@ export function eventDispatcher(data) {
switch (data["modem"]) {
case "started":
displayToast("success", "bi-arrow-left-right", "Modem started", 5000);
getModemState();
getConfig();
getAudioDevices();
getSerialDevices();
getFreedataMessages();
processRadioStatus();
loadAllData();
return;
case "stopped":
@ -109,12 +114,7 @@ export function eventDispatcher(data) {
case "restarted":
displayToast("secondary", "bi-bootstrap-reboot", "Modem restarted", 5000);
getModemState();
getConfig();
getAudioDevices();
getSerialDevices();
getFreedataMessages();
processRadioStatus();
loadAllData();
return;
case "failed":
@ -135,19 +135,7 @@ export function eventDispatcher(data) {
displayToast("success", "bi-ethernet", message, 5000);
stateStore.modem_connection = "connected";
getRemote().then(() => {
//initConnections();
getModemState();
});
//getConfig();
getModemState();
getOverallHealth();
getAudioDevices();
getSerialDevices();
getFreedataMessages();
processFreedataMessages();
processRadioStatus();
loadAllData();
return;

View File

@ -0,0 +1,39 @@
import { defineStore } from "pinia";
import { getAudioDevices } from "../js/api";
import { ref } from "vue";
// Define skel fallback data
const skel = [
{
api: "ERR",
id: "0000",
name: "No devices received from modem",
native_index: 0,
},
];
export const useAudioStore = defineStore("audioStore", () => {
const audioInputs = ref([]);
const audioOutputs = ref([]);
const loadAudioDevices = async () => {
try {
const devices = await getAudioDevices();
// Check if devices are valid and have entries, otherwise use skel
audioInputs.value = devices && devices.in.length > 0 ? devices.in : skel;
audioOutputs.value =
devices && devices.out.length > 0 ? devices.out : skel;
} catch (error) {
console.error("Failed to load audio devices:", error);
// Use skel as fallback in case of error
audioInputs.value = skel;
audioOutputs.value = skel;
}
};
return {
audioInputs,
audioOutputs,
loadAudioDevices,
};
});

View File

@ -0,0 +1,38 @@
import { defineStore } from "pinia";
import { getSerialDevices } from "../js/api"; // Make sure this points to the correct file
import { ref } from "vue";
// Define "skel" fallback data for serial devices
const skelSerial = [
{
description: "No devices received from modem",
port: "ignore", // Using "ignore" as a placeholder value
},
];
export const useSerialStore = defineStore("serialStore", () => {
const serialDevices = ref([]);
const loadSerialDevices = async () => {
try {
const devices = await getSerialDevices();
// Check if devices are valid and have entries, otherwise use skelSerial
serialDevices.value =
devices && devices.length > 0 ? devices : skelSerial;
} catch (error) {
console.error("Failed to load serial devices:", error);
// Use skelSerial as fallback in case of error
serialDevices.value = skelSerial;
}
// Ensure the "-- ignore --" option is always available
if (!serialDevices.value.some((device) => device.port === "ignore")) {
serialDevices.value.push({ description: "-- ignore --", port: "ignore" });
}
};
return {
serialDevices,
loadSerialDevices,
};
});

View File

@ -74,23 +74,20 @@ def freedv_get_mode_name_by_value(mode: int) -> str:
return FREEDV_MODE(mode).name
# Check if we are running in a pyinstaller environment
#if hasattr(sys, "_MEIPASS"):
# sys.path.append(getattr(sys, "_MEIPASS"))
#else:
sys.path.append(os.path.abspath("."))
#log.info("[C2 ] Searching for libcodec2...")
# Get the directory of the current script file
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(script_dir)
# Use script_dir to construct the paths for file search
if sys.platform == "linux":
files = glob.glob(r"**/*libcodec2*", recursive=True)
files.append("libcodec2.so")
files = glob.glob(os.path.join(script_dir, "**/*libcodec2*"), recursive=True)
files.append(os.path.join(script_dir, "libcodec2.so"))
elif sys.platform == "darwin":
if hasattr(sys, "_MEIPASS"):
files = glob.glob(getattr(sys, "_MEIPASS") + '/**/*libcodec2*', recursive=True)
files = glob.glob(os.path.join(getattr(sys, "_MEIPASS"), '**/*libcodec2*'), recursive=True)
else:
files = glob.glob(r"**/*libcodec2*.dylib", recursive=True)
files = glob.glob(os.path.join(script_dir, "**/*libcodec2*.dylib"), recursive=True)
elif sys.platform in ["win32", "win64"]:
files = glob.glob(r"**\*libcodec2*.dll", recursive=True)
files = glob.glob(os.path.join(script_dir, "**\\*libcodec2*.dll"), recursive=True)
else:
files = []

View File

@ -2,12 +2,16 @@ from message_system_db_manager import DatabaseManager
from message_system_db_model import MessageAttachment, Attachment, P2PMessage
import json
import hashlib
import os
class DatabaseManagerAttachments(DatabaseManager):
def __init__(self, uri='sqlite:///freedata-messages.db'):
super().__init__(uri)
def __init__(self, db_file=None):
if not db_file:
script_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(script_dir, 'freedata-messages.db')
db_file = 'sqlite:///' + db_path
super().__init__(db_file)
def add_attachment(self, session, message, attachment_data):

View File

@ -4,14 +4,16 @@ from sqlalchemy.orm import scoped_session, sessionmaker
from threading import local
from message_system_db_model import Base, Beacon, Station, Status, Attachment, P2PMessage
from datetime import timezone, timedelta, datetime
import json
import structlog
import helpers
import os
class DatabaseManagerBeacon(DatabaseManager):
def __init__(self, uri):
super().__init__(uri)
def __init__(self, db_file=None):
if not db_file:
script_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(script_dir, 'freedata-messages.db')
db_file = 'sqlite:///' + db_path
super().__init__(db_file)
def add_beacon(self, timestamp, callsign, snr, gridsquare):
session = None

View File

@ -7,12 +7,17 @@ from threading import local
from message_system_db_model import Base, Station, Status
import structlog
import helpers
import os
class DatabaseManager:
def __init__(self, event_manger, uri='sqlite:///freedata-messages.db'):
def __init__(self, event_manger, db_file=None):
self.event_manager = event_manger
if not db_file:
script_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(script_dir, 'freedata-messages.db')
db_file = 'sqlite:///' + db_path
self.engine = create_engine(uri, echo=False)
self.engine = create_engine(db_file, echo=False)
self.thread_local = local()
self.session_factory = sessionmaker(bind=self.engine)
Base.metadata.create_all(self.engine)

View File

@ -4,12 +4,18 @@ from message_system_db_model import Status, P2PMessage
from sqlalchemy.exc import IntegrityError
from datetime import datetime
import json
import os
class DatabaseManagerMessages(DatabaseManager):
def __init__(self, uri='sqlite:///freedata-messages.db'):
super().__init__(uri)
self.attachments_manager = DatabaseManagerAttachments(uri)
def __init__(self, db_file=None):
if not db_file:
script_dir = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(script_dir, 'freedata-messages.db')
db_file = 'sqlite:///' + db_path
super().__init__(db_file)
self.attachments_manager = DatabaseManagerAttachments(db_file)
def add_message(self, message_data, statistics, direction='receive', status=None, is_read=True):
session = self.get_thread_scoped_session()

View File

@ -36,7 +36,8 @@ def set_config():
if 'FREEDATA_CONFIG' in os.environ:
config_file = os.environ['FREEDATA_CONFIG']
else:
config_file = 'config.ini'
script_dir = os.path.dirname(os.path.abspath(__file__))
config_file = os.path.join(script_dir, 'config.ini')
if os.path.exists(config_file):
print(f"Using config from {config_file}")