Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: build snap package #298

Merged
merged 22 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
09fcb79
initial snapcraft.yaml
n8marti Oct 16, 2024
0df058e
chore: create constants.RUNMODE
n8marti Feb 5, 2025
13c6f46
chore: set alt user downloads dir if snap package
n8marti Feb 5, 2025
4709ab1
fix: set snap version
n8marti Feb 5, 2025
6b62c7c
fix: create ~/.cache dir if it doesn't exist
n8marti Feb 5, 2025
cf7d4f8
fix: move INSTALLDIR to $SNAP_USER_COMMON
n8marti Feb 5, 2025
80c0bac
fix: sotre .cache in $SNAP_USER_COMMON
n8marti Feb 5, 2025
2a507b2
fix: have get_user_downloads_dir default to $HOME instead of $HOME/Do…
n8marti Feb 6, 2025
695e6d2
feat: add app.user_download_dir attrib
n8marti Feb 6, 2025
102cb8c
feat: set default cache dir to $XDG_CACHE_HOME/FaithLife-Community
n8marti Feb 6, 2025
55450f9
chore: only check user download dir and cache dir for downloaded files
n8marti Feb 6, 2025
0e6e6ad
feat: store RUNMODE as constant
n8marti Feb 6, 2025
2a3e8f0
chore: move desktop file content into creation func
n8marti Feb 10, 2025
07beea5
fix: add Faithlife app subcommands and launchers
n8marti Feb 10, 2025
79fbee5
fix: give correct fallback dir if XDG_DATA_HOME is not defined
n8marti Feb 10, 2025
d946c37
fix: use ~/Downloads as fallback instead of ~
n8marti Feb 10, 2025
c3fbe0e
fix: simplify setting the fallback value for cache_dir
n8marti Feb 10, 2025
6f90b9f
feat: add system-trace interface
n8marti Feb 10, 2025
9c5b47c
feat: add system-observe interface
n8marti Feb 10, 2025
6b9e9b1
fix: syntax typo
n8marti Feb 10, 2025
2c7f445
fix: store img files in toplevel of binary
n8marti Feb 10, 2025
978ab4d
fix: pull code from main repo branch
n8marti Feb 10, 2025
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
2 changes: 1 addition & 1 deletion ou_dedetai.spec
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ a = Analysis(
pathex=[],
#binaries=[('/usr/bin/tclsh8.6', '.')],
binaries=[],
datas=[('ou_dedetai/img/*icon.png', 'ou_dedetai/img'),('ou_dedetai/assets/LogosStubFailOK.mst', 'assets')],
datas=[('ou_dedetai/img/*icon.png', 'img'),('ou_dedetai/assets/LogosStubFailOK.mst', 'assets')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
Expand Down
9 changes: 8 additions & 1 deletion ou_dedetai/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ class Config:
# i.e. filesystem traversals
_wine_user: Optional[str] = None
_download_dir: Optional[str] = None
_user_download_dir: Optional[str] = None
_wine_output_encoding: Optional[str] = None
_installed_faithlife_product_release: Optional[str] = None
_wine_binary_files: Optional[list[str]] = None
Expand Down Expand Up @@ -999,9 +1000,15 @@ def skip_install_system_dependencies(self, val: bool):
@property
def download_dir(self) -> str:
if self._download_dir is None:
self._download_dir = utils.get_user_downloads_dir()
self._download_dir = constants.DEFAULT_CACHE_DIR
return self._download_dir

@property
def user_download_dir(self) -> str:
if self._user_download_dir is None:
self._user_download_dir = utils.get_user_downloads_dir()
return self._user_download_dir

@property
def installed_faithlife_product_release(self) -> Optional[str]:
if self._raw.install_dir is None:
Expand Down
34 changes: 25 additions & 9 deletions ou_dedetai/constants.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import logging
import os
import sys

from pathlib import Path

# This is relative to this file itself
APP_IMAGE_DIR = Path(__file__).parent / "img"

def get_runmode() -> str:
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
return 'binary'
elif os.environ.get('SNAP'):
return 'snap'
else:
return 'script'



# Are we running from binary or src?
if getattr(sys, 'frozen', False):
# We are running inside a PyInstaller bundle
RUNMODE = get_runmode()
if RUNMODE == 'binary':
BUNDLE_DIR = Path(sys._MEIPASS)
elif RUNMODE == 'snap':
BUNDLE_DIR = Path(os.environ.get('SNAP'))
else:
# We are running in normal development mode
BUNDLE_DIR = Path(__file__).resolve().parent

# Now define an assets directory that works in both modes:
# Now define assets and img directories.
APP_IMAGE_DIR = BUNDLE_DIR / 'img'
APP_ASSETS_DIR = BUNDLE_DIR / 'assets'

# Define app name variables.
Expand All @@ -31,11 +41,17 @@
CACHE_LIFETIME_HOURS = 12
"""How long to wait before considering our version cache invalid"""

if RUNMODE == 'snap':
cache_dir = Path(os.getenv('SNAP_USER_COMMON')) / '.cache'
else:
cache_dir = Path(os.getenv('XDG_CACHE_HOME', Path.home() / '.cache'))

# Set other run-time variables not set in the env.
DEFAULT_CONFIG_PATH = os.path.expanduser(f"~/.config/FaithLife-Community/{BINARY_NAME}.json") # noqa: E501
DEFAULT_APP_WINE_LOG_PATH= os.path.expanduser("~/.local/state/FaithLife-Community/wine.log") # noqa: E501
DEFAULT_APP_LOG_PATH= os.path.expanduser(f"~/.local/state/FaithLife-Community/{BINARY_NAME}.log") # noqa: E501
NETWORK_CACHE_PATH = os.path.expanduser("~/.cache/FaithLife-Community/network.json") # noqa: E501
DEFAULT_APP_WINE_LOG_PATH = os.path.expanduser("~/.local/state/FaithLife-Community/wine.log") # noqa: E501
DEFAULT_APP_LOG_PATH = os.path.expanduser(f"~/.local/state/FaithLife-Community/{BINARY_NAME}.log") # noqa: E501
DEFAULT_CACHE_DIR = str(cache_dir / 'FaithLife-Community')
NETWORK_CACHE_PATH = f"{DEFAULT_CACHE_DIR}/network.json"
DEFAULT_WINEDEBUG = "fixme-all,err-all"
LEGACY_CONFIG_FILES = [
os.path.expanduser("~/.config/FaithLife-Community/Logos_on_Linux.json"),
Expand Down
4 changes: 2 additions & 2 deletions ou_dedetai/gui_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,9 @@ def update_app_button(self, evt=None):
def update_latest_lli_release_button(self, evt=None):
msg = None
result = utils.compare_logos_linux_installer_version(self)
if system.get_runmode() != 'binary':
if constants.RUNMODE != 'binary':
state = 'disabled'
msg = "This button is disabled. Can't run self-update from script."
msg = f"This button is disabled. Can't run self-update from {constants.RUNMODE}." # noqa: E501
elif result == utils.VersionComparison.OUT_OF_DATE:
state = '!disabled'
elif result == utils.VersionComparison.UP_TO_DATE:
Expand Down
93 changes: 47 additions & 46 deletions ou_dedetai/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,7 @@ def ensure_launcher_executable(app: App):
app.installer_step_count += 1
ensure_config_file(app=app)
app.installer_step += 1
runmode = system.get_runmode()
if runmode == 'binary':
if constants.RUNMODE == 'binary':
app.status(f"Copying launcher to {app.conf.install_dir}…")

# Copy executable into install dir.
Expand All @@ -282,14 +281,13 @@ def ensure_launcher_shortcuts(app: App):
ensure_launcher_executable(app=app)
app.installer_step += 1
app.status("Creating launcher shortcuts…")
runmode = system.get_runmode()
if runmode == 'binary':
if constants.RUNMODE == 'binary':
app.status("Creating launcher shortcuts…")
create_launcher_shortcuts(app)
else:
# This is done so devs can run this without it clobbering their install
app.status(
"Running from source. Won't clobber your desktop shortcuts",
f"Runmode is '{constants.RUNMODE}'. Won't create desktop shortcuts",
)

def install(app: App):
Expand Down Expand Up @@ -349,8 +347,28 @@ def create_wine_appimage_symlinks(app: App):
p.symlink_to(f"./{app.conf.wine_appimage_link_file_name}")


def create_desktop_file(name, contents):
launcher_path = Path(f"~/.local/share/applications/{name}").expanduser()
def create_desktop_file(
filename: str,
app_name: str,
exec_cmd: str,
icon_path: str,
wm_class: str,
):
contents = f"""[Desktop Entry]
Name={app_name}
GenericName=FaithLife Wine App Installer
Comment=Manages FaithLife Bible Software via Wine
Exec={exec_cmd}
Icon={icon_path}
Terminal=false
Type=Application
StartupWMClass={wm_class}
Categories=Education;
Keywords=Logos;Verbum;Bible;Control;
"""
local_share = Path.home() / '.local' / 'share'
xdg_data_home = Path(os.getenv('XDG_DATA_HOME', local_share))
launcher_path = xdg_data_home / 'applications' / filename
ctrlaltf24 marked this conversation as resolved.
Show resolved Hide resolved
if launcher_path.is_file():
logging.info(f"Removing desktop launcher at {launcher_path}.")
launcher_path.unlink()
Expand All @@ -361,6 +379,7 @@ def create_desktop_file(name, contents):
with launcher_path.open('w') as f:
f.write(contents)
os.chmod(launcher_path, 0o755)
return launcher_path


def create_launcher_shortcuts(app: App):
Expand All @@ -376,8 +395,10 @@ def create_launcher_shortcuts(app: App):
logos_icon_path = app_dir / logos_icon_src.name
app_icon_path = app_dir / app_icon_src.name

if system.get_runmode() == 'binary':
if constants.RUNMODE == 'binary':
lli_executable = f"{installdir}/{constants.BINARY_NAME}"
elif constants.RUNMODE == 'snap':
lli_executable = f"{os.getenv('SNAP')}/bin/{constants.BINARY_NAME}"
else:
script = Path(sys.argv[0]).expanduser().resolve()
repo_dir = None
Expand All @@ -401,41 +422,21 @@ def create_launcher_shortcuts(app: App):
else:
logging.info(f"Icon found at {path}.")

# Set launcher file names and content.
desktop_files = [
(
f"{flproduct}Bible.desktop",
f"""[Desktop Entry]
Name={flproduct}Bible
Comment=A Bible Study Library with Built-In Tools
Exec={lli_executable} --run-installed-app
Icon={logos_icon_path}
Terminal=false
Type=Application
StartupWMClass={flproduct.lower()}.exe
Categories=Education;
Keywords={flproduct};Logos;Bible;Control;
"""
),
(
f"{constants.BINARY_NAME}.desktop",
f"""[Desktop Entry]
Name={constants.APP_NAME}
GenericName=FaithLife Wine App Installer
Comment=Manages FaithLife Bible Software via Wine
Exec={lli_executable}
Icon={app_icon_path}
Terminal=false
Type=Application
StartupWMClass={constants.BINARY_NAME}
Categories=Education;
Keywords={flproduct};Logos;Bible;Control;
"""
),
]

# Create the files.
for file_name, content in desktop_files:
create_desktop_file(file_name, content)
fpath = Path.home() / '.local' / 'share' / 'applications' / file_name
logging.debug(f"> File exists?: {fpath}: {fpath.is_file()}")
# Create Logos/Verbum desktop file.
logos_path = create_desktop_file(
f"{flproduct}Bible.desktop",
f"{flproduct}Bible",
f"{lli_executable} --run-installed-app",
logos_icon_path,
f"{flproduct.lower()}.exe",
)
logging.debug(f"> File exists?: {logos_path}: {logos_path.is_file()}")
# Create Ou Dedetai desktop file.
app_path = create_desktop_file(
f"{constants.BINARY_NAME}.desktop",
constants.APP_NAME,
lli_executable,
app_icon_path,
constants.BINARY_NAME,
)
logging.debug(f"> File exists?: {app_path}: {app_path.is_file()}")
2 changes: 1 addition & 1 deletion ou_dedetai/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def parse_args(args, parser) -> Tuple[EphemeralConfiguration, Callable[[Ephemera
# if network.check_for_updates:
# ephemeral_config.check_updates_now = True

if args.skip_dependencies:
if args.skip_dependencies or constants.RUNMODE == 'snap':
ephemeral_config.install_dependencies_skip = True

if args.force_root:
Expand Down
5 changes: 2 additions & 3 deletions ou_dedetai/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def load(cls) -> "CachedRequests":
def _write(self) -> None:
"""Writes the cache to disk. Done internally when there are changes"""
path = Path(constants.NETWORK_CACHE_PATH)
path.parent.mkdir(exist_ok=True)
path.parent.mkdir(exist_ok=True, parents=True)
with open(path, "w") as f:
json.dump(self.__dict__, f, indent=4, sort_keys=True, default=vars)
f.write("\n")
Expand Down Expand Up @@ -305,8 +305,7 @@ def logos_reuse_download(
status_messages: bool = True
):
dirs = [
app.conf.install_dir,
ctrlaltf24 marked this conversation as resolved.
Show resolved Hide resolved
os.getcwd(),
app.conf.user_download_dir,
app.conf.download_dir,
]
found = 1
Expand Down
19 changes: 5 additions & 14 deletions ou_dedetai/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,10 @@
import sys
import time

from ou_dedetai import constants
from ou_dedetai.app import App


from . import constants


def get_runmode():
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
return 'binary'
else:
return 'script'


def fix_ld_library_path(env: Optional[MutableMapping[str, str]]) -> dict[str, str]: #noqa: E501
"""Removes pyinstaller bundled dynamic linked libraries when executing commands

Expand All @@ -42,7 +33,7 @@ def fix_ld_library_path(env: Optional[MutableMapping[str, str]]) -> dict[str, st
# Only do this when running in binary mode
# Since LD_LIBRARY_PATH is core to how binaries work, we don't want it
# modified unless we know we need to.
if get_runmode() == 'binary':
if constants.RUNMODE == 'binary':
# *BSD uses LD_LIBRARY_PATH, AIX uses LIBPATH
for lp_key in ["LD_LIBRARY_PATH", "LIBPATH"]:
lp_orig = env.pop(lp_key + '_ORIG', None)
Expand Down Expand Up @@ -264,8 +255,7 @@ def get_dialog() -> str:
dialog - tk (graphical), curses (terminal ui), or cli (command line)
"""
if not os.environ.get('DISPLAY'):
print("The installer does not work unless you are running a display", file=sys.stderr) # noqa: E501
sys.exit(1)
logging.critical("The installer does not work unless you are running a display") # noqa: E501

dialog = os.getenv('DIALOG')
# find dialog
Expand Down Expand Up @@ -528,6 +518,7 @@ def get_package_manager() -> PackageManager | None:
"libva mpg123 v4l-utils " # video
"libxslt sqlite " # misc
)
logos_9_packages = ""
incompatible_packages = "" # appimagelauncher handled separately
else:
# Add more conditions for other package managers as needed.
Expand All @@ -544,7 +535,7 @@ def get_package_manager() -> PackageManager | None:
logos_9_packages=logos_9_packages,
query_prefix=query_prefix
)
logging.debug("Package Manager: {output}")
logging.debug(f"Package Manager: {output}")
return output


Expand Down
2 changes: 1 addition & 1 deletion ou_dedetai/tui_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ def which_dialog_options(self, labels: list[str]) -> list[Any]: #noqa: E501

def set_tui_menu_options(self):
labels = []
if system.get_runmode() == "binary":
if constants.RUNMODE == "binary":
status = utils.compare_logos_linux_installer_version(self)
if status == utils.VersionComparison.OUT_OF_DATE:
labels.append(f"Update {constants.APP_NAME}")
Expand Down
7 changes: 4 additions & 3 deletions ou_dedetai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ def clean_all():

def get_user_downloads_dir() -> str:
home = Path.home()
downloads_path = str(home / 'Downloads')

xdg_config = Path(os.getenv('XDG_CONFIG_HOME', home / '.config'))
user_dirs_file = xdg_config / 'user-dirs.dirs'
downloads_path = str(home / 'Downloads')
if user_dirs_file.is_file():
with user_dirs_file.open() as f:
for line in f.readlines():
Expand Down Expand Up @@ -540,8 +541,8 @@ def set_appimage_symlink(app: App):
def update_to_latest_lli_release(app: App):
result = compare_logos_linux_installer_version(app)

if system.get_runmode() != 'binary':
logging.error(f"Can't update {constants.APP_NAME} when run as a script.")
if constants.RUNMODE != 'binary':
logging.error(f"Can't update {constants.APP_NAME} when run as {constants.RUNMODE}.") # noqa: E501
elif result == VersionComparison.OUT_OF_DATE:
network.update_lli_binary(app=app)
elif result == VersionComparison.UP_TO_DATE:
Expand Down
25 changes: 25 additions & 0 deletions snap/bin/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash

# Ensure correct environment.
if [[ $SNAP_NAME != oudedetai ]]; then
echo "ERROR: Not running in oudedetai snap environment."
exit 1
fi

export FLPRODUCT="$1"
shift
echo "Starting $FLPRODUCT"
export TARGETVERSION="10"

# Ensure Faithlife app is installed.
app_exe="$(find "${INSTALLDIR}/wine64_bottle" -wholename "*${FLPRODUCT}/${FLPRODUCT}.exe" 2>/dev/null)"
if [[ -z $app_exe ]]; then
oudedetai --install-app --assume-yes $@
ec=$?
if [[ $ec -ne 0 ]]; then
exit $ec
fi
fi

# Run Faithlife app.
oudedetai --run-installed-app $@
1 change: 1 addition & 0 deletions snap/gui/logos.png
Loading