Compare commits

...

10 Commits

Author SHA1 Message Date
Alexandr Stelnykovych
2d7d0323b0 feat(VisualStudio Code): add launch configurations
Some checks failed
Angular / Lint (push) Has been cancelled
Angular / Build (push) Has been cancelled
Release v2.X / Prep (push) Has been cancelled
Tauri / Build (push) Has been cancelled
Tauri / Linter (push) Has been cancelled
Release v2.X / Installer linux (push) Has been cancelled
Release v2.X / Installer windows (push) Has been cancelled
Close Stale Issues / stale (push) Has been cancelled
2025-12-17 14:08:24 +02:00
Alexandr Stelnykovych
b5cb006937 Bump version 2.1.7 2025-12-09 23:35:06 +02:00
Alexandr Stelnykovych
3c8bd02808 Earthfile: exclude extra fields when creating assets.zip 2025-12-09 23:27:02 +02:00
Alexandr Stelnykovych
d5d47223b4 fix(spn): Ensure intel data is initialized
Prevent intel remaining uninitialized, which caused SPN to fail to bootstrap on connect.
https://github.com/safing/portmaster/issues/2095
2025-12-09 14:28:48 +02:00
Alexandr Stelnykovych
e1475a1a3e feat(build): add script to build Tauri application for Portmaster on Linux 2025-12-08 18:19:56 +02:00
Alexandr Stelnykovych
8c19468e32 refactor(UI): remove unused structs and simplify GTK theme handling 2025-12-08 17:58:58 +02:00
Alexandr Stelnykovych
9a2b4f6256 fix(UI): ensure GTK calls are executed on the main thread to prevent segfaults 2025-12-08 17:31:26 +02:00
Alexandr Stelnykovych
9af071ef17 fix(control): ensure wall-clock comparison for resume worker deadline 2025-12-01 13:32:46 +02:00
Alexandr Stelnykovych
1a47196d84 Merge pull request #2086 from safing/fix/pause-fixes
fixes in Pause logic
2025-11-28 13:40:59 +02:00
Alexandr Stelnykovych
aa507572d0 fix(group): correct IsStopped logic to use local state variable 2025-11-28 13:34:07 +02:00
19 changed files with 397 additions and 70 deletions

37
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,37 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "portmaster-core",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmds/portmaster-core",
"windows": {
"args": ["--bin-dir=C:\\Program Files\\Portmaster", "--log-stdout", "--log", "trace"]
},
"linux": {
"args": ["--bin-dir=/usr/lib/portmaster", "--log-stdout", "--log", "trace"]
},
},
{
"name": "portmaster-core (NO INTERCEPTION)",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/cmds/portmaster-core",
"windows": {
"args": ["--bin-dir=C:\\Program Files\\Portmaster", "--log-stdout", "--log", "trace", "--disable-interception=true"]
},
"linux": {
"args": ["--bin-dir=/usr/lib/portmaster", "--log-stdout", "--log", "trace", "--disable-interception=true"]
},
}
]
}

View File

@@ -359,7 +359,7 @@ assets:
WORKDIR /app/assets
COPY --keep-ts ./assets/data .
RUN zip -r -9 -db assets.zip *
RUN zip -r -9 -db -X assets.zip *
SAVE ARTIFACT --keep-ts "assets.zip" AS LOCAL "${outputDir}/all/assets.zip"

25
desktop/angular/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run UI in Chrome (npm run start)",
"type": "chrome",
"request": "launch",
"preLaunchTask": "debugproj",
"postDebugTask": "killnode",
"url": "http://localhost:4200/",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:/*": "${webRoot}/*",
"/./*": "${webRoot}/*",
"/src/*": "${webRoot}/*",
"/*": "*",
"/./~/*": "${webRoot}/node_modules/*"
}
},
]
}

152
desktop/angular/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,152 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "ui-library-watch",
"type": "npm",
"script": "build-ui:dev:watch",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"group": "dev-libraries"
},
"group": {
"kind": "build"
},
"problemMatcher": {
"owner": "typescript",
"source": "ts",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": "Building Angular Package",
"endsPattern": "Compilation complete\\. Watching for file changes\\.\\.\\."
}
}
},
{
"label": "api-library-watch",
"type": "npm",
"script": "build-api:dev:watch",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"group": "dev-libraries"
},
"group": {
"kind": "build"
},
"problemMatcher": {
"owner": "typescript",
"source": "ts",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": "Building Angular Package",
"endsPattern": "Compilation complete\\. Watching for file changes\\.\\.\\."
}
}
},
{
"label": "build-libs-first",
"type": "npm",
"script": "build-libs:dev",
"isBackground": false,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"group": {
"kind": "build"
},
"problemMatcher": []
},
{
"label": "debugproj",
"dependsOrder": "sequence",
"dependsOn": [
"build-libs-first",
"ui-library-watch",
"api-library-watch",
"main-app-with-polling"
],
"isBackground": true,
"presentation": {
"echo": false,
"reveal": "never",
"focus": false,
"panel": "shared"
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "main-app-with-polling",
"type": "npm",
"script": "serve-with-lib-watch",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "dedicated",
"group": "dev-main"
},
"group": {
"kind": "build"
},
"problemMatcher": {
"owner": "typescript",
"source": "ts",
"applyTo": "closedDocuments",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": "Generating browser application bundles",
"endsPattern": "Compiled successfully\\.|Failed to compile\\."
}
}
},
{
"label": "killnode",
"type": "process",
"windows": {
"command": "taskkill",
"args": ["/F", "/IM", "node.exe"]
},
"osx":{
"command": "killall",
"args": ["node"]
},
"linux":{
"command": "killall",
"args": ["node"]
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"group": {
"kind": "build"
},
"isBackground": false,
"problemMatcher": []
}
]
}

View File

@@ -1,12 +1,12 @@
{
"name": "portmaster",
"version": "2.0.25",
"version": "2.1.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "portmaster",
"version": "2.0.25",
"version": "2.1.7",
"dependencies": {
"@angular/animations": "^16.0.1",
"@angular/cdk": "^16.0.1",
@@ -3671,9 +3671,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8396,9 +8396,9 @@
"license": "Python-2.0"
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10323,9 +10323,9 @@
}
},
"node_modules/eslint/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11314,9 +11314,9 @@
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -13102,9 +13102,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15382,9 +15382,9 @@
"license": "MIT"
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
"integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
"dev": true,
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {

View File

@@ -1,11 +1,16 @@
{
"name": "portmaster",
"version": "2.0.25",
"version": "2.1.7",
"scripts": {
"ng": "ng",
"start": "npm install && npm run build-libs:dev && ng serve --proxy-config ./proxy.json",
"build-libs": "cross-env NODE_ENV=production ng build --configuration production @safing/ui && cross-env NODE_ENV=production ng build --configuration production @safing/portmaster-api",
"build-libs:dev": "ng build --configuration development @safing/ui && ng build --configuration development @safing/portmaster-api",
"build-ui:dev:watch": "ng build --configuration development @safing/ui --watch",
"build-api:dev:watch": "ng build --configuration development @safing/portmaster-api --watch",
"serve-with-lib-watch": "ng serve --proxy-config ./proxy.json --poll=2000",
"serve": "npm run build-libs:dev && ng serve --proxy-config ./proxy.json",
"build:dev": "npm run build-libs:dev && ng build",
"test": "ng test",

34
desktop/tauri/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,34 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
// How to debug Tauri project:
// https://v2.tauri.app/develop/debug/
//
"version": "0.2.0",
"configurations": [
{
"type": "lldb", // `vscode-lldb` extension has to be installed (https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)
"request": "launch",
"name": "Debug Dev",
"cargo": {"args": ["build", "--manifest-path=./src-tauri/Cargo.toml", "--no-default-features"]},
"env": { "TAURI_PM_URL": "http://127.0.0.1:817" }
},
{
"type": "lldb",
"request": "launch",
"name": "Debug Prod",
"cargo": {"args": ["build", "--manifest-path=./src-tauri/Cargo.toml", "--release"]},
},
{
"name": "Debug Dev (VS Win Debugger)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceRoot}/src-tauri/target/debug/portmaster.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "build:debug",
"env": { "TAURI_PM_URL": "http://127.0.0.1:817" }
}
]
}

13
desktop/tauri/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build:debug",
"type": "cargo",
"command": "build",
"args": ["--manifest-path=./src-tauri/Cargo.toml", "--no-default-features"]
}
]
}

View File

@@ -3814,7 +3814,7 @@ dependencies = [
[[package]]
name = "portmaster"
version = "2.0.25"
version = "2.1.7"
dependencies = [
"assert_matches",
"bytes",

View File

@@ -1,6 +1,6 @@
[package]
name = "portmaster"
version = "2.0.25"
version = "2.1.7"
description = "Portmaster UI"
authors = ["Safing"]
license = ""

View File

@@ -185,15 +185,4 @@ impl std::convert::TryFrom<Response> for Message {
Response::Done => Ok(Message{id: 0, cmd: "done".to_string(), key: None, payload: None}),
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct Record {
pub created: u64,
pub deleted: u64,
pub expires: u64,
pub modified: u64,
pub key: String,
}

View File

@@ -91,14 +91,19 @@ pub fn get_app_info<R: Runtime>(
}
let cloned = id.clone();
std::thread::spawn(move || match crate::xdg::get_app_info(info) {
Ok(info) => window.emit(&id, info),
Err(err) => window.emit(
&id,
Error {
error: err.to_string(),
},
),
// GTK calls are not thread-safe and must run on the main thread.
// Schedule the work on the GTK/GLib main thread to avoid random segfaults.
glib::idle_add_local(move || {
let _ = match crate::xdg::get_app_info(info.clone()) {
Ok(info) => window.emit(&id, info),
Err(err) => window.emit(
&id,
Error {
error: err.to_string(),
},
),
};
glib::ControlFlow::Break
});
Ok(cloned)

View File

@@ -329,9 +329,6 @@ pub trait PortmasterExt<R: Runtime> {
fn portmaster(&self) -> &PortmasterInterface<R>;
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct Config {}
impl<R: Runtime, T: Manager<R>> PortmasterExt<R> for T {
fn portmaster(&self) -> &PortmasterInterface<R> {
self.state::<PortmasterInterface<R>>().inner()

View File

@@ -67,7 +67,7 @@ pub fn create_main_window(app: &AppHandle) -> Result<WebviewWindow> {
// Issue: https://github.com/safing/portmaster/issues/1909
// Additional info: https://github.com/tauri-apps/tauri/issues/6162#issuecomment-1423304398
let win_clone = win.clone();
win.listen("tauri://focus", move |event| {
win.listen("tauri://focus", move |_event| {
let _ = win_clone.set_resizable(false);
let _ = win_clone.set_resizable(true);
});

View File

@@ -3,7 +3,7 @@ use dataurl::DataUrl;
use gdk_pixbuf::{Pixbuf, PixbufError};
use gtk_sys::{
gtk_icon_info_free, gtk_icon_info_get_filename, gtk_icon_theme_get_default,
gtk_icon_theme_lookup_icon, GtkIconTheme,
gtk_icon_theme_lookup_icon,
};
use log::{debug, error};
use std::collections::HashMap;
@@ -20,8 +20,6 @@ use thiserror::Error;
use ini::{Ini, ParseOption};
static mut GTK_DEFAULT_THEME: Option<*mut GtkIconTheme> = None;
lazy_static! {
static ref APP_INFO_CACHE: Arc<RwLock<HashMap<String, Option<AppInfo>>>> =
Arc::new(RwLock::new(HashMap::new()));
@@ -352,17 +350,10 @@ fn parse_app_info(props: &ini::Properties) -> AppInfo {
}
fn get_icon_as_png_dataurl(name: &str, size: i8) -> Result<(String, String)> {
unsafe {
if GTK_DEFAULT_THEME.is_none() {
let theme = gtk_icon_theme_get_default();
if theme.is_null() {
debug!("You have to initialize GTK!");
return Err(Error::new(ErrorKind::Other, "You have to initialize GTK!").into());
}
let theme = gtk_icon_theme_get_default();
GTK_DEFAULT_THEME = Some(theme);
}
// gtk_icon_theme_get_default() is lightweight - it returns a borrowed reference to GTK's singleton icon theme
let theme = unsafe { gtk_icon_theme_get_default() };
if theme.is_null() {
return Err(Error::new(ErrorKind::Other, "GTK not initialized").into());
}
let mut icons = Vec::new();
@@ -402,7 +393,7 @@ fn get_icon_as_png_dataurl(name: &str, size: i8) -> Result<(String, String)> {
let c_str = CString::new(name).unwrap();
let icon_info = gtk_icon_theme_lookup_icon(
GTK_DEFAULT_THEME.unwrap(),
theme,
c_str.as_ptr() as *const c_char,
size as c_int,
0,

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail
# This script builds the Tauri application for Portmaster on Linux.
# It optionally builds the required Angular tauri-builtin project first.
# The script assumes that all necessary dependencies (Node.js, Angular CLI, Rust, cargo-tauri) are installed.
# Output file: dist/portmaster
# Resolve script directory and project root
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/../../../" && pwd)"
ORIGINAL_DIR="$(pwd)"
# Create output directory
OUTPUT_DIR="${SCRIPT_DIR}/dist"
mkdir -p "${OUTPUT_DIR}"
# Helper: check for command availability
have() { command -v "$1" >/dev/null 2>&1; }
# Optional: build Angular tauri-builtin
read -r -p "Build Angular tauri-builtin project? (Y/N, default: Y) " REPLY
REPLY=${REPLY:-Y}
if [[ ! ${REPLY} =~ ^[Nn]$ ]]; then
# Ensure Angular CLI is available
if ! have ng; then
echo "Error: Angular CLI 'ng' not found in PATH." >&2
echo "Install via: npm install -g @angular/cli" >&2
exit 1
fi
# Navigate to Angular project
pushd "${PROJECT_ROOT}/desktop/angular" >/dev/null
# Build tauri-builtin with production config
ng build --configuration production --base-href / tauri-builtin || {
popd >/dev/null
cd "${ORIGINAL_DIR}"
exit 1
}
popd >/dev/null
fi
# Navigate to Tauri src-tauri directory
pushd "${PROJECT_ROOT}/desktop/tauri/src-tauri" >/dev/null
# Ensure cargo and tauri plugin are available
if ! have cargo; then
echo "Error: cargo not found. Install Rust toolchain (rustup)." >&2
exit 1
fi
if ! cargo tauri --help >/dev/null 2>&1; then
echo "Error: cargo-tauri not installed." >&2
echo "Install via: cargo install tauri-cli" >&2
popd >/dev/null
exit 1
fi
# Build Tauri project (no bundle)
cargo tauri build --no-bundle
# Copy built binary to dist
TAURI_OUTPUT_DIR="$(pwd)/target/release"
if [[ -f "${TAURI_OUTPUT_DIR}/portmaster" ]]; then
cp -f "${TAURI_OUTPUT_DIR}/portmaster" "${OUTPUT_DIR}/"
echo "Build completed successfully: ${OUTPUT_DIR}/portmaster"
else
echo "Error: Built binary not found at ${TAURI_OUTPUT_DIR}/portmaster" >&2
popd >/dev/null
exit 1
fi
# Return to original directory
popd >/dev/null
cd "${ORIGINAL_DIR}"

View File

@@ -143,7 +143,9 @@ func (c *Control) stopResumeWorker() {
// startResumeWorker starts a worker that will resume normal operation after the specified duration.
// No thread safety, caller must hold c.locker.
func (c *Control) startResumeWorker(duration time.Duration) {
deadline := time.Now().Add(duration)
// Strip monotonic clock from deadline to force wall-clock comparison.
// This is critical for VM suspend/resume and system sleep scenarios.
deadline := time.Now().Round(0).Add(duration)
c.pauseInfo.TillTime = deadline
resumerWorkerFunc := func(wc *mgr.WorkerCtx) error {
@@ -173,7 +175,8 @@ func (c *Control) startResumeWorker(duration time.Duration) {
needToAutoResume = true
}
case <-ticker.C:
if time.Now().After(deadline) {
// Check wall-clock time.
if time.Now().Round(0).After(deadline) {
needToAutoResume = true
}
case <-timer.C:

View File

@@ -166,7 +166,7 @@ func (g *Group) IsStopped() (bool, error) {
if state == groupStateInvalid {
return false, errors.New("invalid group state")
}
return g.state.Load() == groupStateOff, nil
return state == groupStateOff, nil
}
// Stop stops all modules in the group in the reverse order.

View File

@@ -48,11 +48,14 @@ func updateSPNIntel(_ context.Context, _ interface{}) (err error) {
return fmt.Errorf("failed to get SPN intel update: %w", err)
}
// Check if file is newer.
// Continue on check failure.
newer, ok := file.IsNewerThan(intelResource)
if ok && !newer {
return nil
// If intel is not initialized, skip version comparison.
if navigator.Main.GetIntel() != nil {
// Check if file is newer.
// Continue on check failure.
newer, ok := file.IsNewerThan(intelResource)
if ok && !newer {
return nil
}
}
// Load intel file from disk.