From 57f19174b5e78d7dc563f6348ef8d3cf5cfe7799 Mon Sep 17 00:00:00 2001 From: amuza2 Date: Mon, 10 Nov 2025 23:48:43 +0100 Subject: [PATCH] Add AppImage support and fix data storage for portable deployment --- .gitignore | 9 ++ ProductivityCake.AppDir/.DirIcon | 1 + ProductivityCake.AppDir/AppRun | 20 +++ .../ProductivityCake.desktop | 9 ++ ProductivityCake.AppDir/productivitycake.png | Bin 0 -> 7207 bytes ProductivityCake/App.axaml.cs | 5 +- ProductivityCake/ProductivityCake.csproj | 4 +- ProductivityCake/Services/JsonDataService.cs | 4 +- README.md | 40 +++++- build-appimage.sh | 116 ++++++++++++++++++ 10 files changed, 200 insertions(+), 8 deletions(-) create mode 120000 ProductivityCake.AppDir/.DirIcon create mode 100755 ProductivityCake.AppDir/AppRun create mode 100644 ProductivityCake.AppDir/ProductivityCake.desktop create mode 100644 ProductivityCake.AppDir/productivitycake.png create mode 100755 build-appimage.sh diff --git a/.gitignore b/.gitignore index f7c1890..d85a17b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,15 @@ riderModule.iml # Build and publish output publish/ +*.AppImage +appimagetool +appimagetool-*.AppImage +ProductivityCake.AppDir/usr/ +squashfs-root/ + +# Debug symbols +*.dbg +*.pdb # Ignore all markdown files except README.md and .github folder diff --git a/ProductivityCake.AppDir/.DirIcon b/ProductivityCake.AppDir/.DirIcon new file mode 120000 index 0000000..700bf81 --- /dev/null +++ b/ProductivityCake.AppDir/.DirIcon @@ -0,0 +1 @@ +productivitycake.png \ No newline at end of file diff --git a/ProductivityCake.AppDir/AppRun b/ProductivityCake.AppDir/AppRun new file mode 100755 index 0000000..303b16e --- /dev/null +++ b/ProductivityCake.AppDir/AppRun @@ -0,0 +1,20 @@ +#!/bin/bash + +# AppRun script for ProductivityCake +SELF=$(readlink -f "$0") +HERE=${SELF%/*} + +# Export library paths - include both usr/lib and usr/bin for .so files +export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/bin:${LD_LIBRARY_PATH}" +export PATH="${HERE}/usr/bin:${PATH}" + +# Set data directory to user's home to avoid read-only filesystem issues +# This ensures the app writes data to ~/.local/share/ProductivityCake instead of the AppImage mount +export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}" +export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" + +# Change to user's home directory (not the AppImage mount point) +cd "$HOME" + +# Run the application +exec "${HERE}/usr/bin/ProductivityCake" "$@" diff --git a/ProductivityCake.AppDir/ProductivityCake.desktop b/ProductivityCake.AppDir/ProductivityCake.desktop new file mode 100644 index 0000000..8f3155f --- /dev/null +++ b/ProductivityCake.AppDir/ProductivityCake.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=ProductivityCake +Exec=ProductivityCake +Icon=productivitycake +Type=Application +Categories=Utility;Office;ProjectManagement; +Comment=A simple and elegant Pomodoro timer with project management +Terminal=false +StartupWMClass=ProductivityCake diff --git a/ProductivityCake.AppDir/productivitycake.png b/ProductivityCake.AppDir/productivitycake.png new file mode 100644 index 0000000000000000000000000000000000000000..4af642a9ce6fe3daf20510a910af4d626c3fdd1c GIT binary patch literal 7207 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 pJ)P7^ozzL4)JdJxN#LEL{|CbA^(); diff --git a/ProductivityCake/ProductivityCake.csproj b/ProductivityCake/ProductivityCake.csproj index 810243a..a8f3e20 100644 --- a/ProductivityCake/ProductivityCake.csproj +++ b/ProductivityCake/ProductivityCake.csproj @@ -23,8 +23,8 @@ - - + + PreserveNewest PreserveNewest diff --git a/ProductivityCake/Services/JsonDataService.cs b/ProductivityCake/Services/JsonDataService.cs index 9249c97..4ff363c 100644 --- a/ProductivityCake/Services/JsonDataService.cs +++ b/ProductivityCake/Services/JsonDataService.cs @@ -18,7 +18,9 @@ public class JsonDataService : IJsonDataService public JsonDataService(string fileName) { - var dataDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data"); + // Use user's home directory for data storage (works with AppImage) + var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var dataDirectory = Path.Combine(homeDir, ".local", "share", "ProductivityCake", "Data"); Directory.CreateDirectory(dataDirectory); _filePath = Path.Combine(dataDirectory, fileName); diff --git a/README.md b/README.md index 10440c5..417558b 100644 --- a/README.md +++ b/README.md @@ -67,13 +67,21 @@ A modern, lightweight desktop application for managing projects, tasks, and time ### Linux -1. Download `ProductivityCake-linux-x64.tar.gz` from [Releases](https://github.com/amuza2/ProductivityCake/releases) -2. Extract the archive: +**Option 1: AppImage (Recommended - Works on all distros)** + +1. Download `ProductivityCake-x.x.x-x86_64.AppImage` from [Releases](https://github.com/amuza2/ProductivityCake/releases) +2. Make it executable and run: ```bash - tar -xzf ProductivityCake-linux-x64.tar.gz + chmod +x ProductivityCake-*.AppImage + ./ProductivityCake-*.AppImage ``` -3. Run the application: + +**Option 2: Standalone Binary** + +1. Download `ProductivityCake-linux-x64.tar.gz` from [Releases](https://github.com/amuza2/ProductivityCake/releases) +2. Extract and run: ```bash + tar -xzf ProductivityCake-linux-x64.tar.gz ./ProductivityCake ``` @@ -157,6 +165,30 @@ cd publish/linux-x64 tar -czf ProductivityCake-linux-x64.tar.gz ProductivityCake alarm.mp3 ``` +### Build AppImage (Universal Linux Package) + +**Prerequisites for AppImage:** +```bash +# On Arch/EndeavourOS +sudo pacman -S fuse2 + +# On Ubuntu/Debian +sudo apt install fuse libfuse2 +``` + +**Build the AppImage:** +```bash +chmod +x build-appimage.sh +./build-appimage.sh +``` + +This creates a universal `ProductivityCake-1.1.0-x86_64.AppImage` that works on: +- ✅ Arch Linux / EndeavourOS / Manjaro +- ✅ Ubuntu / Debian / Linux Mint +- ✅ Fedora / RHEL / CentOS +- ✅ openSUSE +- ✅ Any Linux distro with FUSE support + ## 🏗️ Technology Stack diff --git a/build-appimage.sh b/build-appimage.sh new file mode 100755 index 0000000..1411103 --- /dev/null +++ b/build-appimage.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# ProductivityCake AppImage Build Script +# This script builds a universal AppImage for all Linux distributions + +set -e # Exit on error + +VERSION="1.1.0" +ARCH="x86_64" +APP_NAME="ProductivityCake" +APPDIR="${APP_NAME}.AppDir" + +echo "🎂 Building ${APP_NAME} AppImage v${VERSION}..." +echo "" + +# Check for required tools +if ! command -v appimagetool &> /dev/null; then + echo "⚠️ appimagetool not found. Downloading..." + wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${ARCH}.AppImage -O appimagetool + chmod +x appimagetool + APPIMAGETOOL="./appimagetool" +else + APPIMAGETOOL="appimagetool" +fi + +# Clean previous builds +echo "🧹 Cleaning previous builds..." +rm -rf ./publish/linux-x64 +rm -rf ./${APPDIR}/usr + +# Publish the application +echo "📦 Publishing native AOT binary..." +dotnet publish ProductivityCake/ProductivityCake.csproj \ + -c Release \ + -r linux-x64 \ + --self-contained \ + -o ./publish/linux-x64 + +echo "" +echo "✅ Build completed successfully!" +echo "" + +# Create AppDir structure +echo "📁 Creating AppImage directory structure..." +mkdir -p ${APPDIR}/usr/bin +mkdir -p ${APPDIR}/usr/lib +mkdir -p ${APPDIR}/usr/share/applications +mkdir -p ${APPDIR}/usr/share/icons/hicolor/256x256/apps + +# Copy application files +echo "📋 Copying application files..." +cp ./publish/linux-x64/ProductivityCake ${APPDIR}/usr/bin/ + +# Copy shared libraries +if [ -f "./publish/linux-x64/libSkiaSharp.so" ]; then + cp ./publish/linux-x64/libSkiaSharp.so ${APPDIR}/usr/lib/ + echo "✅ Copied libSkiaSharp.so" +fi +if [ -f "./publish/linux-x64/libHarfBuzzSharp.so" ]; then + cp ./publish/linux-x64/libHarfBuzzSharp.so ${APPDIR}/usr/lib/ + echo "✅ Copied libHarfBuzzSharp.so" +fi + +# Copy any other .so files +for lib in ./publish/linux-x64/*.so; do + if [ -f "$lib" ]; then + cp "$lib" ${APPDIR}/usr/lib/ + echo "✅ Copied $(basename $lib)" + fi +done + +# Copy alarm.mp3 (check multiple locations) +if [ -f "./publish/linux-x64/alarm.mp3" ]; then + cp ./publish/linux-x64/alarm.mp3 ${APPDIR}/usr/bin/ + echo "✅ Copied alarm.mp3 from publish directory" +elif [ -f "./publish/linux-x64/Assets/alarm.mp3" ]; then + cp ./publish/linux-x64/Assets/alarm.mp3 ${APPDIR}/usr/bin/ + echo "✅ Copied alarm.mp3 from Assets directory" +elif [ -f "./ProductivityCake/Assets/alarm.mp3" ]; then + cp ./ProductivityCake/Assets/alarm.mp3 ${APPDIR}/usr/bin/ + echo "✅ Copied alarm.mp3 from source Assets directory" +else + echo "⚠️ Warning: alarm.mp3 not found, sound notifications may not work" +fi + +# Copy icon (convert from PNG to use as app icon) +if [ -f "ProductivityCake/Assets/icons8-cake-96.png" ]; then + cp ProductivityCake/Assets/icons8-cake-96.png ${APPDIR}/productivitycake.png + cp ProductivityCake/Assets/icons8-cake-96.png ${APPDIR}/usr/share/icons/hicolor/256x256/apps/productivitycake.png +else + echo "⚠️ Warning: Icon file not found" +fi + +# Copy desktop file +cp ${APPDIR}/ProductivityCake.desktop ${APPDIR}/usr/share/applications/ + +# Make AppRun executable +chmod +x ${APPDIR}/AppRun + +# Build AppImage +echo "" +echo "🔨 Building AppImage..." +ARCH=${ARCH} ${APPIMAGETOOL} ${APPDIR} ${APP_NAME}-${VERSION}-${ARCH}.AppImage + +echo "" +echo "✅ AppImage created successfully!" +echo "" +echo "📦 Output: ./${APP_NAME}-${VERSION}-${ARCH}.AppImage" +echo "" +echo "🚀 To run the AppImage:" +echo " chmod +x ${APP_NAME}-${VERSION}-${ARCH}.AppImage" +echo " ./${APP_NAME}-${VERSION}-${ARCH}.AppImage" +echo "" +echo "📤 To distribute:" +echo " Upload ${APP_NAME}-${VERSION}-${ARCH}.AppImage to GitHub Releases" +echo ""