Skip to content
Merged

v0.18.2 #1114

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/build_server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,11 @@ jobs:
sudo apt update
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake patchelf

- name: Install MacOS pyAudio
- name: Install MacOS dependencies
if: ${{startsWith(matrix.os, 'macos')}}
run: |
brew install portaudio
python -m pip install --upgrade pip
pip3 install pyaudio

- name: Install Python dependencies
run: |
Expand Down
65 changes: 58 additions & 7 deletions .github/workflows/pip_package.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Deploy Python Package
on: [push]
on:
push:
tags:
- "v*"

jobs:
deploy:
Expand All @@ -17,16 +20,64 @@ jobs:
with:
node-version: 24

- name: Install Linux dependencies
run: |
sudo apt update
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake patchelf

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install .[build]

- name: Set package version from tag
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
python3 - <<'EOF'
import os
import re
import sys

from packaging.version import InvalidVersion, Version

tag = os.environ["RELEASE_TAG"]
version = tag.removeprefix("v")

if not re.fullmatch(r"\d+\.\d+\.\d+(-[0-9A-Za-z.]+)?", version):
print(
f"::error::Tag '{tag}' does not look like a release version. "
f"Use e.g. v1.2.3, v1.2.3-beta, v1.2.3-rc1 or v1.2.3-alpha.1."
)
sys.exit(1)

try:
Version(version)
except InvalidVersion:
print(
f"::error::Tag '{tag}' has an unrecognized pre-release suffix "
f"('{version}' is not valid PEP 440). Use a standard suffix such "
f"as -alpha, -alpha.1, -beta, -rc1 or -dev."
)
sys.exit(1)

print(f"Releasing version {version} (from tag {tag})")

path = "freedata_server/constants.py"
with open(path) as f:
content = f.read()

new_content, count = re.subn(
r'^MODEM_VERSION = .*$',
f'MODEM_VERSION = "{version}"',
content,
count=1,
flags=re.MULTILINE,
)
if count != 1:
print("::error::Could not find MODEM_VERSION in freedata_server/constants.py")
sys.exit(1)

with open(path, "w") as f:
f.write(new_content)
EOF
grep "^MODEM_VERSION" freedata_server/constants.py

- name: Build GUI
working-directory: freedata_gui
run: |
Expand All @@ -39,7 +90,7 @@ jobs:

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.14.0
if: startsWith(github.ref, 'refs/tags/v')
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ARG HAMLIB_VERSION=4.5.5
ENV HAMLIB_VERSION=${HAMLIB_VERSION}

RUN apt-get update && \
apt-get install --upgrade -y fonts-noto-color-emoji git build-essential cmake portaudio19-dev python3-pyaudio python3-colorama wget && \
apt-get install --upgrade -y fonts-noto-color-emoji git build-essential cmake portaudio19-dev python3-colorama wget && \
mkdir -p /app/FreeDATA

WORKDIR /src
Expand Down
34 changes: 25 additions & 9 deletions freedata_server/codec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,36 @@ def freedv_get_mode_name_by_value(mode: int) -> str:
return FREEDV_MODE(mode).name


# Get the directory of the current script file
script_dir = os.path.dirname(os.path.abspath(__file__))
# Determine the base directory to search for the codec2 shared library.
#
# In normal (non-frozen) execution this is simply the directory containing
# this script, and that's where "lib/codec2/*" lives relative to
# freedata_server/codec2.py.
#
# When compiled by Nuitka into a standalone binary however, data files added
# via --include-data-dir/--include-data-files (e.g. "lib=lib") are placed
# relative to the *distribution* directory (next to the produced .exe), not
# relative to this module's own (nested) package directory. Using
# os.path.dirname(__file__) in that case points at "<dist>/freedata_server"
# while the actual DLL ends up at "<dist>/lib/codec2/libcodec2.dll" - a
# sibling directory, not a child - so the glob below never finds it.
#
# Nuitka exposes the correct directory via the compiled-only global
# `__compiled__.containing_dir`, which always points at the distribution
# directory regardless of platform or nesting. See:
# https://nuitka.net/user-documentation/common-issue-solutions.html#standalone-finding-files
try:
script_dir = __compiled__.containing_dir # type: ignore[name-defined]
except NameError:
script_dir = os.path.dirname(os.path.abspath(__file__))

# Use script_dir to construct the paths for file search
if sys.platform == "linux":
files = glob.glob(os.path.join(script_dir, "**/*libcodec2*"), recursive=True)
# files.append(os.path.join(script_dir, "libcodec2.so"))
files = glob.glob(os.path.join(script_dir, "**", "*libcodec2*"), recursive=True)
elif sys.platform == "darwin":
if hasattr(sys, "_MEIPASS"):
files = glob.glob(os.path.join(getattr(sys, "_MEIPASS"), "**/*libcodec2*"), recursive=True)
else:
files = glob.glob(os.path.join(script_dir, "**/*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(os.path.join(script_dir, "**\\*libcodec2*.dll"), recursive=True)
files = glob.glob(os.path.join(script_dir, "**", "*libcodec2*.dll"), recursive=True)
else:
files = []
api = None
Expand Down
6 changes: 6 additions & 0 deletions freedata_server/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import configparser
import structlog
import json
import os


class CONFIG:
Expand Down Expand Up @@ -296,6 +297,11 @@ def write_to_file(self):
data if successful, False otherwise.
"""
try:
# need to create the directory before writing to it
config_dir = os.path.dirname(self.config_name)
if config_dir:
os.makedirs(config_dir, exist_ok=True)

with open(self.config_name, "w") as configfile:
self.parser.write(configfile)
self.ctx.config = self.read()
Expand Down
26 changes: 26 additions & 0 deletions freedata_server/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
# Module for saving some constants
import os
import sys


def _default_app_dir() -> str:
"""
Per-user directory for config, database and log file, following each
OS's own convention rather than forcing a single layout everywhere:
- Windows: %APPDATA%\\FreeDATA
- macOS: ~/Library/Application Support/FreeDATA
- Linux: $XDG_CONFIG_HOME/FreeDATA or ~/.config/FreeDATA
Used only when FREEDATA_CONFIG / FREEDATA_DATABASE are not set (e.g. a
plain `pip install freedata` run). Keeping this outside the installed
package directory means it survives package upgrades/reinstalls.
"""
home = os.path.expanduser("~")
if sys.platform == "win32":
base = os.getenv("APPDATA") or home
elif sys.platform == "darwin":
base = os.path.join(home, "Library", "Application Support")
else:
base = os.getenv("XDG_CONFIG_HOME") or os.path.join(home, ".config")
return os.path.join(base, "FreeDATA")


CONFIG_ENV_VAR = "FREEDATA_CONFIG"
DEFAULT_CONFIG_FILE = "config.ini"
DEFAULT_APP_DIR = _default_app_dir()
MODEM_VERSION = "0.18.1"
API_VERSION = 4
ARQ_PROTOCOL_VERSION = 1
Expand Down
13 changes: 7 additions & 6 deletions freedata_server/message_system_db_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import structlog
from freedata_server import helpers
import os
from freedata_server.constants import MESSAGE_SYSTEM_DATABASE_VERSION
from freedata_server.constants import MESSAGE_SYSTEM_DATABASE_VERSION, DEFAULT_APP_DIR


class DatabaseManager:
Expand Down Expand Up @@ -42,18 +42,19 @@ def get_database(self):
This method determines the database file path based on the
environment variable `FREEDATA_DATABASE`. If the variable is set,
its value is used as the path. Otherwise, it defaults to
`freedata-messages.db` in the script directory.
`freedata-messages.db` in the per-user app directory
(DEFAULT_APP_DIR), so a plain `pip install` keeps the database
outside the installed package and it survives upgrades.

Returns:
str: The database file path as a SQLAlchemy URL.
"""
script_directory = os.path.dirname(os.path.abspath(__file__))

if self.DATABASE_ENV_VAR in os.environ:
# db_path = os.getenv(self.DATABASE_ENV_VAR, os.path.join(script_directory, self.DEFAULT_DATABASE_FILE))
db_path = os.getenv(self.DATABASE_ENV_VAR)
else:
db_path = os.path.join(script_directory, self.DEFAULT_DATABASE_FILE)
db_path = os.path.join(DEFAULT_APP_DIR, self.DEFAULT_DATABASE_FILE)

os.makedirs(os.path.dirname(db_path), exist_ok=True)
return "sqlite:///" + db_path

def initialize_default_values(self):
Expand Down
Loading