diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index ec8b315d..56d1b219 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -1,5 +1,5 @@ # build-linux.yml -# Reusable workflow that builds the Linux and MacOS versions of Libation. +# Reusable workflow that builds the Linux and MacOS (x64 and arm64) versions of Libation. --- name: build @@ -19,6 +19,7 @@ on: env: DOTNET_CONFIGURATION: 'Release' DOTNET_VERSION: '7.0.x' + RELEASE_NAME: 'chardonnay' jobs: build: @@ -26,8 +27,7 @@ jobs: strategy: matrix: os: [Linux, MacOS] - ui: [Avalonia] - release_name: [chardonnay] + arch: [x64, arm64] steps: - uses: actions/checkout@v3 - name: Setup .NET @@ -57,25 +57,50 @@ jobs: - name: Publish working-directory: ./Source run: | - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} Libation${{ matrix.ui }}/Libation${{ matrix.ui }}.csproj -p:PublishProfile=Libation${{ matrix.ui }}/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj -p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} LibationCli/LibationCli.csproj -p:PublishProfile=LibationCli/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} Hangover${{ matrix.ui }}/Hangover${{ matrix.ui }}.csproj -p:PublishProfile=Hangover${{ matrix.ui }}/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml - - - name: Zip artifact - id: zip - working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} + os=${{ matrix.os }} + RUNTIME_IDENTIFIER="$(echo ${os,} | sed 's/macOS/osx/')-${{ matrix.arch }}" + echo "$RUNTIME_IDENTIFIER" + dotnet publish \ + LibationAvalonia/LibationAvalonia.csproj \ + --runtime "$RUNTIME_IDENTIFIER" \ + --configuration ${{ env.DOTNET_CONFIGURATION }} \ + --output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ + -p:PublishProfile=LibationAvalonia/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish \ + LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj \ + --runtime "$RUNTIME_IDENTIFIER" \ + --configuration ${{ env.DOTNET_CONFIGURATION }} \ + --output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ + -p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish \ + LibationCli/LibationCli.csproj \ + --runtime "$RUNTIME_IDENTIFIER" \ + --configuration ${{ env.DOTNET_CONFIGURATION }} \ + --output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ + -p:PublishProfile=LibationCli/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish \ + HangoverAvalonia/HangoverAvalonia.csproj \ + --runtime "$RUNTIME_IDENTIFIER" \ + --configuration ${{ env.DOTNET_CONFIGURATION }} \ + --output bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} \ + -p:PublishProfile=HangoverAvalonia/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml + + - name: Build bundle + id: bundle + working-directory: ./Source/bin/Publish/${{ matrix.os }}-${{ matrix.arch }}-${{ env.RELEASE_NAME }} run: | - delfiles=("libmp3lame.x86.dll" "libmp3lame.x64.dll" "ffmpegaac.x86.dll" "ffmpegaac.x64.dll") - for n in "${delfiles[@]}"; do rm "$n"; done - osbuild="$(echo '${{ matrix.os }}' | tr '[:upper:]' '[:lower:]')" - artifact="Libation.${{ steps.get_version.outputs.version }}-${osbuild}-${{ matrix.release_name }}" + BUNDLE_DIR=$(pwd) + echo "Bundle dir: ${BUNDLE_DIR}" + cd .. + SCRIPT=../../../Scripts/Bundle_${{ matrix.os }}.sh + chmod +rx ${SCRIPT} + ${SCRIPT} "${BUNDLE_DIR}" "${{ steps.get_version.outputs.version }}" "${{ matrix.arch }}" + artifact=$(ls ./bundle) echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}" - tar -zcvf "../${artifact}.tar.gz" . - - - name: Publish artifact + + - name: Publish bundle uses: actions/upload-artifact@v3 with: - name: ${{ steps.zip.outputs.artifact }}.tar.gz - path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.tar.gz + name: ${{ steps.bundle.outputs.artifact }} + path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }} if-no-files-found: error diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index f82aa21e..caa91e33 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -60,21 +60,49 @@ jobs: - name: Publish working-directory: ./Source run: | - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} Libation${{ matrix.ui }}/Libation${{ matrix.ui }}.csproj -p:PublishProfile=Libation${{ matrix.ui }}/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj -p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} LibationCli/LibationCli.csproj -p:PublishProfile=LibationCli/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml - dotnet publish -c ${{ env.DOTNET_CONFIGURATION }} -o bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} Hangover${{ matrix.ui }}/Hangover${{ matrix.ui }}.csproj -p:PublishProfile=Hangover${{ matrix.ui }}/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish ` + Libation${{ matrix.ui }}/Libation${{ matrix.ui }}.csproj ` + --configuration ${{ env.DOTNET_CONFIGURATION }} ` + --output bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} ` + -p:PublishProfile=Libation${{ matrix.ui }}/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish ` + LoadByOS/${{ matrix.os }}ConfigApp/${{ matrix.os }}ConfigApp.csproj ` + --configuration ${{ env.DOTNET_CONFIGURATION }} ` + --output bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} ` + -p:PublishProfile=LoadByOS/Properties/${{ matrix.os }}ConfigApp/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish ` + LibationCli/LibationCli.csproj ` + --configuration ${{ env.DOTNET_CONFIGURATION }} ` + --output bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} ` + -p:PublishProfile=LibationCli/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml + dotnet publish ` + Hangover${{ matrix.ui }}/Hangover${{ matrix.ui }}.csproj ` + --configuration ${{ env.DOTNET_CONFIGURATION }} ` + --output bin/Publish/${{ matrix.os }}-${{ matrix.release_name }} ` + -p:PublishProfile=Hangover${{ matrix.ui }}/Properties/PublishProfiles/${{ matrix.os }}Profile.pubxml - name: Zip artifact id: zip working-directory: ./Source/bin/Publish run: | - $dir = "${{ matrix.os }}-${{ matrix.release_name }}\" - $delfiles = @("libmp3lame.so", "ffmpegaac.so", "glass-with-glow_256.svg", "Libation.desktop") - foreach ($file in $delfiles){ if (test-path $dir$file){ Remove-Item $dir$file } } + $bin_dir = "${{ matrix.os }}-${{ matrix.release_name }}\" + $delfiles = @( + "libmp3lame.x64.so", + "libmp3lame.arm64.so", + "libmp3lame.x64.dylib", + "libmp3lame.arm64.dylib", + "ffmpegaac.x64.so", + "ffmpegaac.arm64.so", + "ffmpegaac.x64.dylib", + "ffmpegaac.arm64.dylib", + "WindowsConfigApp.exe", + "WindowsConfigApp.runtimeconfig.json", + "WindowsConfigApp.deps.json" + ) + foreach ($file in $delfiles){ if (test-path $bin_dir$file){ Remove-Item $bin_dir$file } } $artifact="${{ matrix.prefix }}Libation.${{ steps.get_version.outputs.version }}-" + "${{ matrix.os }}".ToLower() + "-${{ matrix.release_name }}" "artifact=$artifact" >> $env:GITHUB_OUTPUT - Compress-Archive -Path "${dir}*" -DestinationPath "$artifact.zip" + Compress-Archive -Path "${bin_dir}*" -DestinationPath "$artifact.zip" - name: Publish artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/bundle-linux.yml b/.github/workflows/bundle-linux.yml deleted file mode 100644 index 1a187276..00000000 --- a/.github/workflows/bundle-linux.yml +++ /dev/null @@ -1,43 +0,0 @@ -# build-linux.yml -# Reusable workflow that builds the Libation installation bundles for Linux and MacOS. ---- -name: bundle-linux - -on: - workflow_call: - inputs: - version: - type: string - description: 'Version number' - required: true - -jobs: - bundle: - runs-on: ubuntu-latest - strategy: - matrix: - os: [linux, macos] - release_name: [chardonnay] - steps: - - uses: actions/checkout@v3 - - - name: Download Artifact - uses: actions/download-artifact@v3 - with: - name: "Libation.${{ inputs.version }}-${{ matrix.os }}-${{ matrix.release_name }}.tar.gz" - - - name: Build bundle - id: build - run: | - SCRIPT=targz2${{ matrix.os }}bundle.sh - chmod +rwx ./Scripts/${SCRIPT} - ./Scripts/${SCRIPT} "Libation.${{ inputs.version }}-${{ matrix.os }}-${{ matrix.release_name }}.tar.gz" ${{ inputs.version }} - artifact=$(ls ./bundle) - echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}" - - - name: Publish bundle - uses: actions/upload-artifact@v3 - with: - name: ${{ steps.build.outputs.artifact }} - path: ./bundle/${{ steps.build.outputs.artifact }} - if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d9e5fca..8495289e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,15 +33,9 @@ jobs: with: version_override: ${{ needs.prerelease.outputs.version }} run_unit_tests: false - - bundle: - needs: [prerelease,build] - uses: ./.github/workflows/bundle-linux.yml - with: - version: ${{ needs.prerelease.outputs.version }} release: - needs: [prerelease,build,bundle] + needs: [prerelease,build] runs-on: ubuntu-latest steps: - name: Download artifacts diff --git a/.releaseindex.json b/.releaseindex.json index 870c942e..5e44f230 100644 --- a/.releaseindex.json +++ b/.releaseindex.json @@ -1,6 +1,8 @@ { - "WindowsClassic": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip", - "WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip", - "LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay\\.deb", - "MacOSAvalonia": "Libation\\.app-macOS-x64-\\d+\\.\\d+\\.\\d+\\.tgz" + "WindowsClassic": "Classic-Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip", + "WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip", + "LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-amd64\\.deb", + "MacOSAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-chardonnay-x64\\.tgz", + "LinuxAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-chardonnay-arm64\\.deb", + "MacOSAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-chardonnay-arm64\\.tgz" } diff --git a/Images/libation_cheers.svg b/Images/libation_cheers.svg new file mode 100644 index 00000000..9b60dba2 --- /dev/null +++ b/Images/libation_cheers.svg @@ -0,0 +1,32 @@ + + + + diff --git a/Images/libation_glass.svg b/Images/libation_glass.svg new file mode 100644 index 00000000..d3a0a092 --- /dev/null +++ b/Images/libation_glass.svg @@ -0,0 +1,28 @@ + + + + + diff --git a/Images/libation_hangover.svg b/Images/libation_hangover.svg new file mode 100644 index 00000000..e1759e86 --- /dev/null +++ b/Images/libation_hangover.svg @@ -0,0 +1,30 @@ + + + + + + + diff --git a/Images/libation_slosh.svg b/Images/libation_slosh.svg new file mode 100644 index 00000000..d97b41ee --- /dev/null +++ b/Images/libation_slosh.svg @@ -0,0 +1,33 @@ + + + + diff --git a/Scripts/targz2linuxbundle.sh b/Scripts/Bundle_Linux.sh similarity index 52% rename from Scripts/targz2linuxbundle.sh rename to Scripts/Bundle_Linux.sh index b4806ad8..a5f842ef 100644 --- a/Scripts/targz2linuxbundle.sh +++ b/Scripts/Bundle_Linux.sh @@ -1,17 +1,18 @@ #!/bin/bash -FILE=$1; shift +BIN_DIR=$1; shift VERSION=$1; shift +ARCH=$1; shift -if [ -z "$FILE" ] +if [ -z "$BIN_DIR" ] then - echo "This script must be called with a the Libation Linux bin zip file as an argument." + echo "This script must be called with a the Libation Linux bins directory as an argument." exit fi -if [ ! -f "$FILE" ] +if [ ! -d "$BIN_DIR" ] then - echo "The file \"$FILE\" does not exist." + echo "The directory \"$BIN_DIR\" does not exist." exit fi @@ -21,57 +22,69 @@ then exit fi +if [ -z "$ARCH" ] +then + echo "This script must be called with the Libation cpu architecture as an argument." + exit +fi + contains() { case "$1" in *"$2"*) true ;; *) false ;; esac } -if ! contains "$FILE" "$VERSION" +if ! contains "$BIN_DIR" "$ARCH" then - echo "This script must be called with a Libation version number that is present in the filename passed." + echo "This script must be called with a Libation binaries for ${ARCH}." exit fi -# remove trailing ".tar.gz" -FOLDER_MAIN=${FILE::-7} -echo "Working dir: $FOLDER_MAIN" +ARCH=$(echo $ARCH | sed 's/x64/amd64/') -if [[ -d "$FOLDER_MAIN" ]] -then - echo "$FOLDER_MAIN directory already exists, aborting." - exit -fi +DEB_DIR=./deb -FOLDER_EXEC="$FOLDER_MAIN/usr/lib/libation" +FOLDER_EXEC=$DEB_DIR/usr/lib/libation echo "Exec dir: $FOLDER_EXEC" +mkdir -p $FOLDER_EXEC -FOLDER_ICON="$FOLDER_MAIN/usr/share/icons/hicolor/scalable/apps/" -echo "Icon dir: $FOLDER_ICON" - -FOLDER_DESKTOP="$FOLDER_MAIN/usr/share/applications" -echo "Desktop dir: $FOLDER_DESKTOP" - -FOLDER_DEBIAN="$FOLDER_MAIN/DEBIAN" -echo "Debian dir: $FOLDER_DEBIAN" - -mkdir -p "$FOLDER_EXEC" -mkdir -p "$FOLDER_ICON" -mkdir -p "$FOLDER_DESKTOP" -mkdir -p "$FOLDER_DEBIAN" - -echo "Extracting $FILE to $FOLDER_EXEC..." -tar -xzf ${FILE} -C ${FOLDER_EXEC} +echo "Moving bins from $BIN_DIR to $FOLDER_EXEC" +mv "${BIN_DIR}/"* $FOLDER_EXEC if [ $? -ne 0 ] - then echo "Error extracting ${FILE}" + then echo "Error moving ${BIN_DIR} files" exit fi + +delfiles=('libmp3lame.arm64.dylib' 'libmp3lame.x64.dylib' 'libmp3lame.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.arm64.dylib' 'ffmpegaac.x64.dylib' 'ffmpegaac.x64.dll' 'ffmpegaac.x86.dll' 'LinuxConfigApp' 'LinuxConfigApp.deps.json' 'LinuxConfigApp.runtimeconfig.json') +if [[ "$ARCH" == "arm64" ]] +then + delfiles+=('libmp3lame.x64.so' 'ffmpegaac.x64.so') +else + delfiles+=('libmp3lame.arm64.so' 'ffmpegaac.arm64.so') +fi + +for n in "${delfiles[@]}" +do + echo "Deleting $n" + rm $FOLDER_EXEC/$n +done + +FOLDER_ICON=$DEB_DIR/usr/share/icons/hicolor/scalable/apps/ +echo "Icon dir: $FOLDER_ICON" + +FOLDER_DESKTOP=$DEB_DIR/usr/share/applications +echo "Desktop dir: $FOLDER_DESKTOP" + +FOLDER_DEBIAN=$DEB_DIR/DEBIAN +echo "Debian dir: $FOLDER_DEBIAN" + +mkdir -p $FOLDER_ICON +mkdir -p $FOLDER_DESKTOP +mkdir -p $FOLDER_DEBIAN + echo "Copying icon..." -cp "$FOLDER_EXEC/glass-with-glow_256.svg" "$FOLDER_ICON/libation.svg" +cp $FOLDER_EXEC/libation_glass.svg $FOLDER_ICON/libation.svg echo "Copying desktop file..." -cp "$FOLDER_EXEC/Libation.desktop" "$FOLDER_DESKTOP/Libation.desktop" - -echo "Workaround for desktop file..." -sed -i '/^Exec=Libation/c\Exec=/usr/bin/libation' "$FOLDER_DESKTOP/Libation.desktop" +cp $FOLDER_EXEC/Libation.desktop $FOLDER_DESKTOP/Libation.desktop echo "Creating pre-install file..." echo "#!/bin/bash @@ -81,20 +94,16 @@ echo "#!/bin/bash echo \"Removing previously created symlinks...\" rm /usr/bin/libation -rm /usr/bin/Libation rm /usr/bin/hangover -rm /usr/bin/Hangover rm /usr/bin/libationcli -rm /usr/bin/LibationCli echo \"Removing previously installed Libation files...\" rm -r /usr/lib/libation -rm -r /usr/lib/Libation # making sure it won't stop installation exit 0 -" >> "$FOLDER_DEBIAN/preinst" +" >> $FOLDER_DEBIAN/preinst echo "Creating post-install file..." echo "#!/bin/bash @@ -114,29 +123,30 @@ fi # workaround until this file is moved to the user's home directory touch /usr/lib/libation/appsettings.json chmod 666 /usr/lib/libation/appsettings.json -" >> "$FOLDER_DEBIAN/postinst" +" >> $FOLDER_DEBIAN/postinst echo "Creating control file..." echo "Package: Libation Version: $VERSION -Architecture: all +Architecture: $ARCH Essential: no Priority: optional Maintainer: github.com/rmcrackan Description: liberate your audiobooks -" >> "$FOLDER_DEBIAN/control" +" >> $FOLDER_DEBIAN/control echo "Changing permissions for pre- and post-install files..." chmod +x "$FOLDER_DEBIAN/preinst" chmod +x "$FOLDER_DEBIAN/postinst" -echo "Creating .deb file..." -dpkg-deb -Zxz --build $FOLDER_MAIN +DEB_FILE=Libation.${VERSION}-linux-chardonnay-${ARCH}.deb +echo "Creating $DEB_FILE" +dpkg-deb -Zxz --build $DEB_DIR ./$DEB_FILE +echo "moving to ./bundle/$DEB_FILE" mkdir bundle -echo "moving to ./bundle/$FOLDER_MAIN.deb" -mv "$FOLDER_MAIN.deb" "./bundle/$FOLDER_MAIN.deb" +mv $DEB_FILE ./bundle/$DEB_FILE -rm -r "$FOLDER_MAIN" +rm -r "$BIN_DIR" echo "Done!" diff --git a/Scripts/Bundle_MacOS.sh b/Scripts/Bundle_MacOS.sh new file mode 100644 index 00000000..d9bd31b9 --- /dev/null +++ b/Scripts/Bundle_MacOS.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +BIN_DIR=$1; shift +VERSION=$1; shift +ARCH=$1; shift + +if [ -z "$BIN_DIR" ] +then + echo "This script must be called with a the Libation macos bins directory as an argument." + exit +fi + +if [ ! -d "$BIN_DIR" ] +then + echo "The directory \"$BIN_DIR\" does not exist." + exit +fi + +if [ -z $VERSION ] +then + echo "This script must be called with the Libation version number as an argument." + exit +fi + +if [ -z $ARCH ] +then + echo "This script must be called with the Libation cpu architecture as an argument." + exit +fi + +contains() { case "$1" in *"$2"*) true ;; *) false ;; esac } + +if ! contains "$BIN_DIR" $ARCH +then + echo "This script must be called with a Libation binaries for ${ARCH}." + exit +fi + +BUNDLE=./Libation.app +echo "Bundle dir: $BUNDLE" + +if [[ -d $BUNDLE ]] +then + echo "$BUNDLE directory already exists, aborting." + exit +fi + +BUNDLE_CONTENTS=$BUNDLE/Contents +echo "Bundle Contents dir: $BUNDLE_CONTENTS" + +BUNDLE_RESOURCES=$BUNDLE_CONTENTS/Resources +echo "Resources dir: $BUNDLE_RESOURCES" + +BUNDLE_MACOS=$BUNDLE_CONTENTS/MacOS +echo "MacOS dir: $BUNDLE_MACOS" + +mkdir -p $BUNDLE_CONTENTS +mkdir -p $BUNDLE_RESOURCES +mkdir -p $BUNDLE_MACOS + +mv "${BIN_DIR}/"* $BUNDLE_MACOS + +if [ $? -ne 0 ] + then echo "Error moving ${BIN_DIR} files" + exit +fi + +echo "Moving icon..." +mv $BUNDLE_MACOS/libation.icns $BUNDLE_RESOURCES/libation.icns + +echo "Moving Info.plist file..." +mv $BUNDLE_MACOS/Info.plist $BUNDLE_CONTENTS/Info.plist + +PLIST_ARCH=$(echo $ARCH | sed 's/x64/x86_64/') +echo "Set LSArchitecturePriority to $PLIST_ARCH" +sed -i -e "s/ARCHITECTURE_STRING/$PLIST_ARCH/" $BUNDLE_CONTENTS/Info.plist + +echo "Set CFBundleVersion to $VERSION" +sed -i -e "s/VERSION_STRING/$VERSION/" $BUNDLE_CONTENTS/Info.plist + + +delfiles=( 'libmp3lame.arm64.so' 'libmp3lame.x64.so' 'libmp3lame.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.arm64.so' 'ffmpegaac.x64.so' 'ffmpegaac.x64.dll' 'ffmpegaac.x86.dll' 'MacOSConfigApp' 'MacOSConfigApp.deps.json' 'MacOSConfigApp.runtimeconfig.json') +if [[ "$ARCH" == "arm64" ]] +then + delfiles+=('libmp3lame.x64.dylib' 'ffmpegaac.x64.dylib') +else + delfiles+=('libmp3lame.arm64.dylib' 'ffmpegaac.arm64.dylib') +fi + + +for n in "${delfiles[@]}" +do + echo "Deleting $n" + rm $BUNDLE_MACOS/$n +done + +APP_FILE=Libation.${VERSION}-macOS-chardonnay-${ARCH}.tgz + +echo "Creating app bundle: $APP_FILE" +tar -czvf $APP_FILE $BUNDLE + +mkdir bundle +echo "moving to ./bundle/$APP_FILE" +mv $APP_FILE ./bundle/$APP_FILE + +rm -r $BUNDLE + +echo "Done!" diff --git a/Scripts/targz2macosbundle.sh b/Scripts/targz2macosbundle.sh deleted file mode 100644 index e772ed31..00000000 --- a/Scripts/targz2macosbundle.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -FILE=$1; shift -VERSION=$1; shift - -if [ -z "$FILE" ] -then - echo "This script must be called with a the Libation macos bin zip file as an argument." - exit -fi - -if [ ! -f "$FILE" ] -then - echo "The file \"$FILE\" does not exist." - exit -fi - -if [ -z "$VERSION" ] -then - echo "This script must be called with the Libation version number as an argument." - exit -fi - -contains() { case "$1" in *"$2"*) true ;; *) false ;; esac } - -if ! contains "$FILE" "$VERSION" -then - echo "This script must be called with a Libation version number that is present in the filename passed." - exit -fi - -BUNDLE="Libation.app" -echo "Bundle dir: $BUNDLE" - -if [[ -d "$BUNDLE" ]] -then - echo "$BUNDLE directory already exists, aborting." - exit -fi - -BUNDLE_CONTENTS="$BUNDLE/Contents" -echo "Bundle Contents dir: $BUNDLE_CONTENTS" - -BUNDLE_RESOURCES="$BUNDLE_CONTENTS/Resources" -echo "Resources dir: $BUNDLE_RESOURCES" - -BUNDLE_MACOS="$BUNDLE_CONTENTS/MacOS" -echo "MacOS dir: $BUNDLE_MACOS" - -mkdir -p "$BUNDLE_CONTENTS" -mkdir -p "$BUNDLE_RESOURCES" -mkdir -p "$BUNDLE_MACOS" - -echo "Extracting $FILE to $BUNDLE_MACOS..." -tar -xzf ${FILE} -C ${BUNDLE_MACOS} - -if [ $? -ne 0 ] - then echo "Error extracting ${FILE}" - exit -fi - -echo "Copying icon..." -cp "$BUNDLE_MACOS/libation.icns" "$BUNDLE_RESOURCES/libation.icns" - -echo "Copying Info.plist file..." -cp "$BUNDLE_MACOS/Info.plist" "$BUNDLE_CONTENTS/Info.plist" - -echo "Set Libation version number..." -sed -i -e "s/VERSION_STRING/$VERSION/" "$BUNDLE_CONTENTS/Info.plist" - -echo "deleting unneeded files.." -delfiles=("libmp3lame.x64.so" "ffmpegaac.x64.so" "libation.icns" "Info.plist") -for n in "${delfiles[@]}"; do rm "$BUNDLE_MACOS/$n"; done - -echo "Creating app bundle: $BUNDLE-$VERSION.tar.gz" -tar -czvf "$BUNDLE-$VERSION.tar.gz" "$BUNDLE" - -mkdir bundle -echo "moving to ./bundle/$BUNDLE-$VERSION.tar.gz" -mv "$BUNDLE-$VERSION.tar.gz" "./bundle/$BUNDLE-macOS-x64-$VERSION.tgz" - -rm -r "$BUNDLE" - -echo "Done!" diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj index 8b8bd394..ea3d58f3 100644 --- a/Source/AaxDecrypter/AaxDecrypter.csproj +++ b/Source/AaxDecrypter/AaxDecrypter.csproj @@ -13,7 +13,7 @@ - + diff --git a/Source/AaxDecrypter/AudiobookDownloadBase.cs b/Source/AaxDecrypter/AudiobookDownloadBase.cs index 43ceb5e0..fbbb3716 100644 --- a/Source/AaxDecrypter/AudiobookDownloadBase.cs +++ b/Source/AaxDecrypter/AudiobookDownloadBase.cs @@ -3,6 +3,7 @@ using Dinah.Core.Net.Http; using Dinah.Core.StepRunner; using FileManager; using System; +using System.Diagnostics; using System.IO; using System.Threading.Tasks; @@ -58,7 +59,7 @@ namespace AaxDecrypter { BytesReceived = 0, ProgressPercentage = 0, - TotalBytesToReceive = InputFileStream.Length + TotalBytesToReceive = 0 }; OnDecryptProgressUpdate(zeroProgress); @@ -66,6 +67,7 @@ namespace AaxDecrypter public async Task RunAsync() { + await InputFileStream.BeginDownloadingAsync(); var progressTask = Task.Run(reportProgress); AsyncSteps[$"Cleanup"] = CleanupAsync; diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs index c7283ee8..53f1753e 100644 --- a/Source/AaxDecrypter/NetworkFileStream.cs +++ b/Source/AaxDecrypter/NetworkFileStream.cs @@ -136,10 +136,10 @@ namespace AaxDecrypter /// Begins downloading to in a background thread. /// The downloader - private Task BeginDownloading() + public async Task BeginDownloadingAsync() { if (ContentLength != 0 && WritePosition == ContentLength) - return Task.CompletedTask; + return; if (ContentLength != 0 && WritePosition > ContentLength) throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); @@ -149,7 +149,7 @@ namespace AaxDecrypter foreach (var header in RequestHeaders) request.Headers.Add(header.Key, header.Value); - var response = new HttpClient().Send(request, HttpCompletionOption.ResponseHeadersRead, _cancellationSource.Token); + var response = await new HttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _cancellationSource.Token); if (response.StatusCode != HttpStatusCode.PartialContent) throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); @@ -159,11 +159,11 @@ namespace AaxDecrypter if (WritePosition == 0) ContentLength = response.Content.Headers.ContentLength.GetValueOrDefault(); - var networkStream = response.Content.ReadAsStream(_cancellationSource.Token); + var networkStream = await response.Content.ReadAsStreamAsync(_cancellationSource.Token); _downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset); //Download the file in the background. - return Task.Run(() => DownloadFile(networkStream), _cancellationSource.Token); + _backgroundDownloadTask = Task.Run(() => DownloadFile(networkStream), _cancellationSource.Token); } /// Download to . @@ -251,7 +251,8 @@ namespace AaxDecrypter { get { - _backgroundDownloadTask ??= BeginDownloading(); + if (_backgroundDownloadTask is null) + throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}"); return ContentLength; } } @@ -274,7 +275,8 @@ namespace AaxDecrypter public override int Read(byte[] buffer, int offset, int count) { - _backgroundDownloadTask ??= BeginDownloading(); + if (_backgroundDownloadTask is null) + throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}"); var toRead = Math.Min(count, Length - Position); WaitToPosition(Position + toRead); diff --git a/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs b/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs index ad9144ee..138c8ea0 100644 --- a/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs +++ b/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs @@ -26,8 +26,7 @@ namespace AaxDecrypter protected override async Task Step_DownloadAndDecryptAudiobookAsync() { - // MUST put InputFileStream.Length first, because it starts background downloader. - while (InputFileStream.Length > InputFilePosition && !InputFileStream.IsCancelled) + while (InputFilePosition < InputFileStream.Length && !InputFileStream.IsCancelled) await Task.Delay(200); if (IsCanceled) diff --git a/Source/AppScaffolding/LibationScaffolding.cs b/Source/AppScaffolding/LibationScaffolding.cs index a44fe2d3..1756da77 100644 --- a/Source/AppScaffolding/LibationScaffolding.cs +++ b/Source/AppScaffolding/LibationScaffolding.cs @@ -9,7 +9,7 @@ using Dinah.Core; using Dinah.Core.IO; using Dinah.Core.Logging; using LibationFileManager; -using Microsoft.EntityFrameworkCore; +using System.Runtime.InteropServices; using Newtonsoft.Json.Linq; using Serilog; @@ -18,14 +18,22 @@ namespace AppScaffolding public enum ReleaseIdentifier { None, - WindowsClassic, - WindowsAvalonia, - LinuxAvalonia, - MacOSAvalonia + WindowsClassic = OS.Windows | Variety.Classic | Architecture.X64, + WindowsAvalonia = OS.Windows | Variety.Chardonnay | Architecture.X64, + LinuxAvalonia = OS.Linux | Variety.Chardonnay | Architecture.X64, + MacOSAvalonia = OS.MacOS | Variety.Chardonnay | Architecture.X64, + LinuxAvalonia_Arm64 = OS.Linux | Variety.Chardonnay | Architecture.Arm64, + MacOSAvalonia_Arm64 = OS.MacOS | Variety.Chardonnay | Architecture.Arm64 } // I know I'm taking the wine metaphor a bit far by naming this "Variety", but I don't know what else to call it - public enum VarietyType { None, Classic, Chardonnay } + [Flags] + public enum Variety + { + None, + Classic = 0x10000, + Chardonnay = 0x20000, + } public static class LibationScaffolding { @@ -33,13 +41,22 @@ namespace AppScaffolding public const string WebsiteUrl = "ht" + "tps://getlibation.com"; public const string RepositoryLatestUrl = "ht" + "tps://github.com/rmcrackan/Libation/releases/latest"; public static ReleaseIdentifier ReleaseIdentifier { get; private set; } - public static VarietyType Variety - => ReleaseIdentifier == ReleaseIdentifier.WindowsClassic ? VarietyType.Classic - : ReleaseIdentifier.In(ReleaseIdentifier.WindowsAvalonia, ReleaseIdentifier.LinuxAvalonia, ReleaseIdentifier.MacOSAvalonia) ? VarietyType.Chardonnay - : VarietyType.None; + public static Variety Variety { get; private set; } - public static void SetReleaseIdentifier(ReleaseIdentifier releaseID) - => ReleaseIdentifier = releaseID; + public static void SetReleaseIdentifier(Variety varietyType) + { + Variety = Enum.IsDefined(varietyType) ? varietyType : Variety.None; + + var releaseID = (ReleaseIdentifier)((int)varietyType | (int)Configuration.OS | (int)RuntimeInformation.ProcessArchitecture); + + if (Enum.IsDefined(releaseID)) + ReleaseIdentifier = releaseID; + else + { + ReleaseIdentifier = ReleaseIdentifier.None; + Serilog.Log.Logger.Warning("Unknown release identifier @{DebugInfo}", new { Variety = varietyType, Configuration.OS, RuntimeInformation.ProcessArchitecture }); + } + } // AppScaffolding private static Assembly _executingAssembly; @@ -296,8 +313,8 @@ namespace AppScaffolding } private static async System.Threading.Tasks.Task<(Octokit.Release, Octokit.ReleaseAsset)> getLatestRelease() { - var ownerAccount = "rmcrackan"; - var repoName = "Libation"; + const string ownerAccount = "rmcrackan"; + const string repoName = "Libation"; var gitHubClient = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(repoName)); @@ -305,12 +322,11 @@ namespace AppScaffolding var bts = await gitHubClient.Repository.Content.GetRawContent(ownerAccount, repoName, ".releaseindex.json"); var releaseIndex = JObject.Parse(System.Text.Encoding.ASCII.GetString(bts)); var regexPattern = releaseIndex.Value(ReleaseIdentifier.ToString()); - - // https://octokitnet.readthedocs.io/en/latest/releases/ - var releases = await gitHubClient.Repository.Release.GetAll(ownerAccount, repoName); - var regex = new System.Text.RegularExpressions.Regex(regexPattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase); - var latestRelease = releases.FirstOrDefault(r => !r.Draft && !r.Prerelease && r.Assets.Any(a => regex.IsMatch(a.Name))); + + //https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-the-latest-release + var latestRelease = await gitHubClient.Repository.Release.GetLatest(ownerAccount, repoName); + return (latestRelease, latestRelease?.Assets?.FirstOrDefault(a => regex.IsMatch(a.Name))); } } diff --git a/Source/AppScaffolding/OSConfigBase.cs b/Source/AppScaffolding/OSConfigBase.cs deleted file mode 100644 index d07eb639..00000000 --- a/Source/AppScaffolding/OSConfigBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace AppScaffolding -{ - public abstract class OSConfigBase - { - public abstract Type InteropFunctionsType { get; } - public virtual Type[] ReferencedTypes { get; } = new Type[0]; - - public void Run() - { - //Each of these types belongs to a different windows-only assembly that's needed by - //the WinInterop methods. By referencing these types in main we force the runtime to - //load their assemblies before execution reaches inside main. This allows the calling - //process to find these assemblies in its module list. - _ = ReferencedTypes; - _ = InteropFunctionsType; - - //Wait for the calling process to be ready to read the WriteLine() - Console.ReadLine(); - - // Signal the calling process that execution has reached inside main, and that all referenced assemblies have been loaded. - Console.WriteLine(); - - // Wait for the calling process to finish reading the process module list, then exit. - Console.ReadLine(); - } - } -} diff --git a/Source/FileLiberator/Processable.cs b/Source/FileLiberator/Processable.cs index 37a971b2..49ecd61b 100644 --- a/Source/FileLiberator/Processable.cs +++ b/Source/FileLiberator/Processable.cs @@ -54,6 +54,8 @@ namespace FileLiberator = (await ProcessAsync(libraryBook)) ?? new StatusHandler { "Processable should never return a null status" }; + GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true); + return status; } diff --git a/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml.cs b/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml.cs index 658e0e0b..f5ff3746 100644 --- a/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml.cs +++ b/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml.cs @@ -133,7 +133,7 @@ namespace LibationAvalonia.Controls : Configuration.GetKnownDirectoryPath(directorySelectControl.SelectedDirectory); selectedDir ??= string.Empty; - Directory = customStates.CustomChecked ? selectedDir : System.IO.Path.Combine(selectedDir, SubDirectory); + Directory = customStates.CustomChecked ? selectedDir : System.IO.Path.Combine(selectedDir, SubDirectory ?? ""); } private void DirectoryOrCustomSelectControl_PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) diff --git a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs index 41967e0d..da450e0d 100644 --- a/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/AccountsDialog.axaml.cs @@ -117,10 +117,13 @@ namespace LibationAvalonia.Dialogs { Title = $"Select the audible-cli [account].json file", AllowMultiple = false, - SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)), FileTypeFilter = new FilePickerFileType[] { - new("JSON files (*.json)") { Patterns = new[] { "*.json" } }, + new("JSON files (*.json)") + { + Patterns = new[] { "*.json" }, + AppleUniformTypeIdentifiers = new[] { "public.json" } + } } }; @@ -274,13 +277,16 @@ namespace LibationAvalonia.Dialogs var options = new FilePickerSaveOptions { Title = $"Save Sover Image", - SuggestedStartLocation = new BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)), SuggestedFileName = $"{acc.AccountId}.json", DefaultExtension = "json", ShowOverwritePrompt = true, FileTypeChoices = new FilePickerFileType[] { - new("JSON files (*.json)") { Patterns = new[] { "*.json" } }, + new("JSON files (*.json)") + { + Patterns = new[] { "*.json" }, + AppleUniformTypeIdentifiers = new[] { "public.json" } + } } }; diff --git a/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs index 57b9961b..fc4a4012 100644 --- a/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs @@ -153,10 +153,22 @@ namespace LibationAvalonia.Dialogs ShowOverwritePrompt = true, FileTypeChoices = new FilePickerFileType[] { - new("Excel Workbook (*.xlsx)") { Patterns = new[] { "*.xlsx" } }, - new("CSV files (*.csv)") { Patterns = new[] { "*.csv" } }, - new("JSON files (*.json)") { Patterns = new[] { "*.json" } }, - new("All files (*.*)") { Patterns = new[] { "*" } } + new("Excel Workbook (*.xlsx)") + { + Patterns = new[] { "*.xlsx" }, + AppleUniformTypeIdentifiers = new[] { "org.openxmlformats.spreadsheetml.sheet" } + }, + new("CSV files (*.csv)") + { + Patterns = new[] { "*.csv" }, + AppleUniformTypeIdentifiers = new[] { "public.comma-separated-values-text" } + }, + new("JSON files (*.json)") + { + Patterns = new[] { "*.json" }, + AppleUniformTypeIdentifiers = new[] { "public.json" } + }, + new("All files (*.*)") { Patterns = new[] { "*" } }, } }); diff --git a/Source/LibationAvalonia/Dialogs/ImageDisplayDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/ImageDisplayDialog.axaml.cs index f6014fe2..c42cabce 100644 --- a/Source/LibationAvalonia/Dialogs/ImageDisplayDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/ImageDisplayDialog.axaml.cs @@ -56,7 +56,11 @@ namespace LibationAvalonia.Dialogs ShowOverwritePrompt = true, FileTypeChoices = new FilePickerFileType[] { - new("Jpeg (*.jpg)") { Patterns = new[] { "jpg" } } + new("Jpeg (*.jpg)") + { + Patterns = new[] { "jpg" }, + AppleUniformTypeIdentifiers = new[] { "public.jpeg" } + } } }; diff --git a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml index 04e1b655..c2b17889 100644 --- a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml @@ -365,7 +365,6 @@ Text="{Binding DownloadDecryptSettings.InProgressDescriptionText}" /> diff --git a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs index f3c721de..dea9cd97 100644 --- a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml.cs @@ -141,7 +141,7 @@ namespace LibationAvalonia.Dialogs public void LoadSettings(Configuration config) { - BooksDirectory = config.Books; + BooksDirectory = config.Books.PathWithoutPrefix; SavePodcastsToParentFolder = config.SavePodcastsToParentFolder; LoggingLevel = config.LogLevel; BetaOptIn = config.BetaOptIn; diff --git a/Source/LibationAvalonia/Program.cs b/Source/LibationAvalonia/Program.cs index 4c5f861b..6481df23 100644 --- a/Source/LibationAvalonia/Program.cs +++ b/Source/LibationAvalonia/Program.cs @@ -2,9 +2,9 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using ApplicationServices; +using AppScaffolding; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.ReactiveUI; @@ -23,18 +23,15 @@ namespace LibationAvalonia //We can do this because we're already executing inside the sandbox. //Any process created in the sandbox executes in the same sandbox. //Unfortunately, all sandbox files are read/execute, so no writing! - - Assembly asm = Assembly.GetExecutingAssembly(); - string path = Path.GetDirectoryName(asm.Location); - Process.Start("Hangover" + (Configuration.IsWindows ? ".exe" : "")); + Process.Start("Hangover"); return; } if (Configuration.IsMacOs && args?.Length > 0 && args[0] == "cli") { //Open a new Terminal in the sandbox - Assembly asm2 = Assembly.GetExecutingAssembly(); - string libationProgramFiles = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - Process.Start("/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", $"\"{libationProgramFiles}\""); + Process.Start( + "/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", + $"\"{Configuration.ProcessDirectory}\""); return; } @@ -44,7 +41,7 @@ namespace LibationAvalonia // // //***********************************************// // Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration - var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations(); + var config = LibationScaffolding.RunPreConfigMigrations(); App.SetupRequired = !config.LibationSettingsAreValid; @@ -52,13 +49,10 @@ namespace LibationAvalonia var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime()); var appBuilderTask = Task.Run(BuildAvaloniaApp); - if (Configuration.IsWindows) - AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.ReleaseIdentifier.WindowsAvalonia); - else if (Configuration.IsLinux) - AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.ReleaseIdentifier.LinuxAvalonia); - else if (Configuration.IsMacOs) - AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.ReleaseIdentifier.MacOSAvalonia); - else return; + LibationScaffolding.SetReleaseIdentifier(Variety.Chardonnay); + + if (LibationScaffolding.ReleaseIdentifier is ReleaseIdentifier.None) + return; if (!App.SetupRequired) @@ -85,8 +79,8 @@ namespace LibationAvalonia try { // most migrations go in here - AppScaffolding.LibationScaffolding.RunPostConfigMigrations(config); - AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config); + LibationScaffolding.RunPostConfigMigrations(config); + LibationScaffolding.RunPostMigrationScaffolding(config); return true; } diff --git a/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs b/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs index f4597741..ef7fe809 100644 --- a/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs @@ -7,6 +7,7 @@ using AudibleApi; using AudibleApi.Common; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Threading; using DataLayer; using Dinah.Core; using Dinah.Core.ErrorHandling; @@ -60,12 +61,12 @@ namespace LibationAvalonia.ViewModels #region Properties exposed to the view public ProcessBookResult Result { get => _result; set { this.RaiseAndSetIfChanged(ref _result, value); this.RaisePropertyChanged(nameof(StatusText)); } } public ProcessBookStatus Status { get => _status; set { this.RaiseAndSetIfChanged(ref _status, value); this.RaisePropertyChanged(nameof(BackgroundColor)); this.RaisePropertyChanged(nameof(IsFinished)); this.RaisePropertyChanged(nameof(IsDownloading)); this.RaisePropertyChanged(nameof(Queued)); } } - public string Narrator { get => _narrator; set { this.RaiseAndSetIfChanged(ref _narrator, value); } } - public string Author { get => _author; set { this.RaiseAndSetIfChanged(ref _author, value); } } - public string Title { get => _title; set { this.RaiseAndSetIfChanged(ref _title, value); } } - public int Progress { get => _progress; private set { this.RaiseAndSetIfChanged(ref _progress, value); } } - public string ETA { get => _eta; private set { this.RaiseAndSetIfChanged(ref _eta, value); } } - public Bitmap Cover { get => _cover; private set { this.RaiseAndSetIfChanged(ref _cover, value); } } + public string Narrator { get => _narrator; set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _narrator, value)); } + public string Author { get => _author; set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _author, value)); } + public string Title { get => _title; set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _title, value)); } + public int Progress { get => _progress; private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _progress, value)); } + public string ETA { get => _eta; private set => Dispatcher.UIThread.Post(() =>this.RaiseAndSetIfChanged(ref _eta, value)); } + public Bitmap Cover { get => _cover; private set => Dispatcher.UIThread.Post(() => this.RaiseAndSetIfChanged(ref _cover, value)); } public bool IsFinished => Status is not ProcessBookStatus.Queued and not ProcessBookStatus.Working; public bool IsDownloading => Status is ProcessBookStatus.Working; public bool Queued => Status is ProcessBookStatus.Queued; @@ -131,6 +132,7 @@ namespace LibationAvalonia.ViewModels public async Task ProcessOneAsync() { string procName = CurrentProcessable.Name; + ProcessBookResult result = ProcessBookResult.None; try { LinkProcessable(CurrentProcessable); @@ -138,32 +140,34 @@ namespace LibationAvalonia.ViewModels var statusHandler = await CurrentProcessable.ProcessSingleAsync(LibraryBook, validate: true); if (statusHandler.IsSuccess) - return Result = ProcessBookResult.Success; + result = ProcessBookResult.Success; else if (statusHandler.Errors.Contains("Cancelled")) { Logger.Info($"{procName}: Process was cancelled - {LibraryBook.Book}"); - return Result = ProcessBookResult.Cancelled; + result = ProcessBookResult.Cancelled; } else if (statusHandler.Errors.Contains("Validation failed")) { Logger.Info($"{procName}: Validation failed - {LibraryBook.Book}"); - return Result = ProcessBookResult.ValidationFail; + result = ProcessBookResult.ValidationFail; + } + else + { + foreach (var errorMessage in statusHandler.Errors) + Logger.Error($"{procName}: {errorMessage}"); } - - foreach (var errorMessage in statusHandler.Errors) - Logger.Error($"{procName}: {errorMessage}"); } catch (ContentLicenseDeniedException ldex) { if (ldex.AYCL?.RejectionReason is null or RejectionReason.GenericError) { Logger.Info($"{procName}: Content license was denied, but this error appears to be caused by a temporary interruption of service. - {LibraryBook.Book}"); - return Result = ProcessBookResult.LicenseDeniedPossibleOutage; + result = ProcessBookResult.LicenseDeniedPossibleOutage; } else { Logger.Info($"{procName}: Content license denied. Check your Audible account to see if you have access to this title. - {LibraryBook.Book}"); - return Result = ProcessBookResult.LicenseDenied; + result = ProcessBookResult.LicenseDenied; } } catch (Exception ex) @@ -172,18 +176,21 @@ namespace LibationAvalonia.ViewModels } finally { - if (Result == ProcessBookResult.None) - Result = await showRetry(LibraryBook); + if (result == ProcessBookResult.None) + result = await showRetry(LibraryBook); - Status = Result switch + var status = result switch { ProcessBookResult.Success => ProcessBookStatus.Completed, ProcessBookResult.Cancelled => ProcessBookStatus.Cancelled, _ => ProcessBookStatus.Failed, }; + + await Dispatcher.UIThread.InvokeAsync(() => Status = status); } - return Result; + await Dispatcher.UIThread.InvokeAsync(() => Result = result); + return result; } public async Task CancelAsync() @@ -294,9 +301,9 @@ namespace LibationAvalonia.ViewModels #region Processable event handlers - private void Processable_Begin(object sender, LibraryBook libraryBook) + private async void Processable_Begin(object sender, LibraryBook libraryBook) { - Status = ProcessBookStatus.Working; + await Dispatcher.UIThread.InvokeAsync(() => Status = ProcessBookStatus.Working); Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}"); diff --git a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs index 2083768c..db25a07e 100644 --- a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs @@ -10,8 +10,6 @@ using ApplicationServices; using AudibleUtilities; using LibationAvalonia.Dialogs.Login; using Avalonia.Collections; -using LibationSearchEngine; -using Octokit.Internal; namespace LibationAvalonia.ViewModels { @@ -62,6 +60,7 @@ namespace LibationAvalonia.ViewModels { var existingSeriesEntries = SOURCE.SeriesEntries().ToList(); + FilteredInGridEntries?.Clear(); SOURCE.Clear(); SOURCE.AddRange(CreateGridEntries(dbBooks)); @@ -164,7 +163,7 @@ namespace LibationAvalonia.ViewModels return FilteredInGridEntries.Contains(item); } - private static List QueryResults(List entries, string searchString) + private static List QueryResults(IEnumerable entries, string searchString) { if (string.IsNullOrEmpty(searchString)) return null; diff --git a/Source/LibationAvalonia/Views/MainWindow.Export.cs b/Source/LibationAvalonia/Views/MainWindow.Export.cs index 6759b09d..3e8cdc87 100644 --- a/Source/LibationAvalonia/Views/MainWindow.Export.cs +++ b/Source/LibationAvalonia/Views/MainWindow.Export.cs @@ -26,10 +26,23 @@ namespace LibationAvalonia.Views ShowOverwritePrompt = true, FileTypeChoices = new FilePickerFileType[] { - new("Excel Workbook (*.xlsx)") { Patterns = new[] { "*.xlsx" } }, - new("CSV files (*.csv)") { Patterns = new[] { "*.csv" } }, - new("JSON files (*.json)") { Patterns = new[] { "*.json" } }, - new("All files (*.*)") { Patterns = new[] { "*" } }, + new("Excel Workbook (*.xlsx)") + { + Patterns = new[] { "*.xlsx" }, + //https://gist.github.com/RhetTbull/7221ef3cfd9d746f34b2550d4419a8c2 + AppleUniformTypeIdentifiers = new[] { "org.openxmlformats.spreadsheetml.sheet" } + }, + new("CSV files (*.csv)") + { + Patterns = new[] { "*.csv" }, + AppleUniformTypeIdentifiers = new[] { "public.comma-separated-values-text" } + }, + new("JSON files (*.json)") + { + Patterns = new[] { "*.json" }, + AppleUniformTypeIdentifiers = new[] { "public.json" } + }, + new("All files (*.*)") { Patterns = new[] { "*" } } } }; diff --git a/Source/LibationAvalonia/Views/MainWindow.Update.cs b/Source/LibationAvalonia/Views/MainWindow.Update.cs index a6858d94..e466c41a 100644 --- a/Source/LibationAvalonia/Views/MainWindow.Update.cs +++ b/Source/LibationAvalonia/Views/MainWindow.Update.cs @@ -20,13 +20,16 @@ namespace LibationAvalonia.Views { if (upgradeProperties.ZipUrl is null) { - Serilog.Log.Logger.Information("Download link for new version not found"); + Serilog.Log.Logger.Warning("Download link for new version not found"); return null; } //Silently download the update in the background, save it to a temp file. var zipFile = Path.Combine(Path.GetTempPath(), Path.GetFileName(upgradeProperties.ZipUrl)); + + Serilog.Log.Logger.Information($"Downloading {zipFile}"); + try { System.Net.Http.HttpClient cli = new(); @@ -55,6 +58,9 @@ namespace LibationAvalonia.Views var interop = InteropFactory.Create(); + if (!interop.CanUpdate) + Serilog.Log.Logger.Information("Can't perform update automatically"); + var notificationResult = await new UpgradeNotificationDialog(upgradeProperties, interop.CanUpdate).ShowDialog(this); if (notificationResult == DialogResult.Ignore) @@ -68,7 +74,9 @@ namespace LibationAvalonia.Views if (string.IsNullOrEmpty(updateBundle) || !File.Exists(updateBundle)) return; //Install the update + Serilog.Log.Logger.Information($"Begin running auto-updater"); interop.InstallUpdate(updateBundle); + Serilog.Log.Logger.Information($"Completed running auto-updater"); } catch (Exception ex) { diff --git a/Source/LibationFileManager/Configuration.Environment.cs b/Source/LibationFileManager/Configuration.Environment.cs index f18c9944..1eec30a1 100644 --- a/Source/LibationFileManager/Configuration.Environment.cs +++ b/Source/LibationFileManager/Configuration.Environment.cs @@ -6,16 +6,25 @@ using System.Threading.Tasks; namespace LibationFileManager { - public partial class Configuration + [Flags] + public enum OS + { + Unknown, + Windows = 0x100000, + Linux = 0x200000, + MacOS = 0x400000, + } + + public partial class Configuration { - public static bool IsWindows { get; } = OperatingSystem.IsWindows(); + public static bool IsWindows { get; } = OperatingSystem.IsWindows(); public static bool IsLinux { get; } = OperatingSystem.IsLinux(); public static bool IsMacOs { get; } = OperatingSystem.IsMacOS(); - public static string OS { get; } - = IsLinux ? "Linux" - : IsMacOs ? "MacOS" - : IsWindows ? "Windows" - : "UNKNOWN_OS"; + public static OS OS { get; } + = IsLinux ? OS.Linux + : IsMacOs ? OS.MacOS + : IsWindows ? OS.Windows + : OS.Unknown; } } diff --git a/Source/LibationFileManager/Configuration.KnownDirectories.cs b/Source/LibationFileManager/Configuration.KnownDirectories.cs index 5ad6a75e..d989b616 100644 --- a/Source/LibationFileManager/Configuration.KnownDirectories.cs +++ b/Source/LibationFileManager/Configuration.KnownDirectories.cs @@ -9,8 +9,9 @@ namespace LibationFileManager { public partial class Configuration { - public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}"; - public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES_KEY)); + public static string ProcessDirectory { get; } = Path.GetDirectoryName(Exe.FileLocationOnDisk); + public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}"; + public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LIBATION_FILES_KEY)); public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation")); public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation")); public static string UserProfile => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Libation")); diff --git a/Source/LibationFileManager/Configuration.LibationFiles.cs b/Source/LibationFileManager/Configuration.LibationFiles.cs index 7608f6c8..553c6a5f 100644 --- a/Source/LibationFileManager/Configuration.LibationFiles.cs +++ b/Source/LibationFileManager/Configuration.LibationFiles.cs @@ -76,7 +76,7 @@ namespace LibationFileManager //Possible appsettings.json locations, in order of preference. string[] possibleAppsettingsFiles = new[] { - Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), appsettings_filename), + Path.Combine(ProcessDirectory, appsettings_filename), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation", appsettings_filename), Path.Combine(UserProfile, appsettings_filename), Path.Combine(Path.GetTempPath(), "Libation", appsettings_filename) diff --git a/Source/LibationFileManager/InteropFactory.cs b/Source/LibationFileManager/InteropFactory.cs index ac2928a9..eddc97be 100644 --- a/Source/LibationFileManager/InteropFactory.cs +++ b/Source/LibationFileManager/InteropFactory.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using Dinah.Core; namespace LibationFileManager @@ -25,21 +23,29 @@ namespace LibationFileManager instance ??= InteropFunctionsType is null ? new NullInteropFunctions() - //: values is null || values.Length == 0 ? Activator.CreateInstance(InteropFunctionsType) as IInteropFunctions : Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions; return instance; } - #region load types + #region load types - public static Func MatchesOS { get; } + private const string CONFIG_APP_ENDING = "ConfigApp.dll"; + + public static Func MatchesOS { get; } = Configuration.IsWindows ? a => Path.GetFileName(a).StartsWithInsensitive("win") : Configuration.IsLinux ? a => Path.GetFileName(a).StartsWithInsensitive("linux") : Configuration.IsMacOs ? a => Path.GetFileName(a).StartsWithInsensitive("mac") || Path.GetFileName(a).StartsWithInsensitive("osx") : _ => false; - private const string CONFIG_APP_ENDING = "ConfigApp.dll"; - private static List ModuleList { get; } = new(); + private static readonly EnumerationOptions enumerationOptions = new() + { + MatchType = MatchType.Simple, + MatchCasing = MatchCasing.CaseInsensitive, + IgnoreInaccessible = true, + RecurseSubdirectories = false, + ReturnSpecialDirectories = false + }; + static InteropFactory() { // searches file names for potential matches; doesn't run anything @@ -52,94 +58,36 @@ namespace LibationFileManager return; } - /* - * Commented code used to locate assemblies from the *ConfigApp.exe's module list. - * Use this method to locate dependencies when they are not in Libation's program files directory. -#if DEBUG - - // runs the exe and gets the exe's loaded modules - ModuleList = LoadModuleList(Path.GetFileNameWithoutExtension(configApp)) - .OrderBy(x => x.ModuleName) - .ToList(); -#endif - */ - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; var configAppAssembly = Assembly.LoadFrom(configApp); var type = typeof(IInteropFunctions); InteropFunctionsType = configAppAssembly .GetTypes() - .FirstOrDefault(t => type.IsAssignableFrom(t)); + .FirstOrDefault(type.IsAssignableFrom); } private static string getOSConfigApp() { - var here = Path.GetDirectoryName(Environment.ProcessPath); - // find '*ConfigApp.dll' files var appName = - Directory.EnumerateFiles(here, $"*{CONFIG_APP_ENDING}", SearchOption.TopDirectoryOnly) - // sanity check. shouldn't ever be true - .Except(new[] { Environment.ProcessPath }) + Directory.EnumerateFiles(Configuration.ProcessDirectory, $"*{CONFIG_APP_ENDING}", enumerationOptions) .FirstOrDefault(exe => MatchesOS(exe)); return appName; } - /* - * Use this method to locate dependencies when they are not in Libation's program files directory. - * - private static List LoadModuleList(string exeName) - { - var proc = new Process - { - StartInfo = new() - { - FileName = exeName, - RedirectStandardInput = true, - RedirectStandardOutput = true, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = false - } - }; - - var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); - - proc.OutputDataReceived += (_, _) => waitHandle.Set(); - proc.Start(); - proc.BeginOutputReadLine(); - - //Let the win process know we're ready to receive its standard output - proc.StandardInput.WriteLine(); - - if (!waitHandle.WaitOne(2000)) - throw new Exception("Failed to start program"); - - //The win process has finished loading and is now waiting inside Main(). - //Copy it process module list. - var modules = proc.Modules.Cast().ToList(); - - //Let the win process know we're done reading its module list - proc.StandardInput.WriteLine(); - - return modules; - } - */ - private static Dictionary lowEffortCache { get; } = new(); private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { - // e.g. "System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" - var asmName = args.Name.Split(',')[0] + ".dll"; - var here = Path.GetDirectoryName(Environment.ProcessPath); + var asmName = new AssemblyName(args.Name); + var here = Configuration.ProcessDirectory; var key = $"{asmName}|{here}"; if (lowEffortCache.TryGetValue(key, out var value)) return value; - var assembly = CurrentDomain_AssemblyResolve_internal(asmName: asmName, here: here); + var assembly = CurrentDomain_AssemblyResolve_internal(asmName, here: here); lowEffortCache[key] = assembly; //Let the runtime handle any dll not found exceptions. @@ -149,27 +97,22 @@ namespace LibationFileManager return assembly; } - private static Assembly CurrentDomain_AssemblyResolve_internal(string asmName, string here) + private static Assembly CurrentDomain_AssemblyResolve_internal(AssemblyName asmName, string here) { /* - * Commented code used to locate assemblies from the *ConfigApp.exe's module list. - * Use this method to locate dependencies when they are not in Libation's program files directory. - #if DEBUG - - var modulePath = ModuleList.SingleOrDefault(m => m.ModuleName.EqualsInsensitive(asmName))?.FileName; - #else - */ - - // find the requested assembly in the program files directory + * Find the requested assembly in the program files directory. + * Assumes that all assemblies are in this application's directory. + * If they're not (e.g. the app is not self-contained), you will need + * to located them. The original way of doing this was to execute the + * config app, wait for the runtime to load all dependencies, and + * then seach the Process.Modules for the assembly name. Code for + * this approach is still in the _Demos projects. + */ var modulePath = - Directory.EnumerateFiles(here, asmName, SearchOption.TopDirectoryOnly) + Directory.EnumerateFiles(here, $"{asmName.Name}.dll", enumerationOptions) .SingleOrDefault(); -//#endif - if (modulePath is null) - return null; - - return Assembly.LoadFrom(modulePath); + return modulePath is null ? null : Assembly.LoadFrom(modulePath); } #endregion diff --git a/Source/LibationWinForms/Program.cs b/Source/LibationWinForms/Program.cs index 333114e3..28148e21 100644 --- a/Source/LibationWinForms/Program.cs +++ b/Source/LibationWinForms/Program.cs @@ -30,7 +30,7 @@ namespace LibationWinForms ApplicationConfiguration.Initialize(); - AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.ReleaseIdentifier.WindowsClassic); + AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.Variety.Classic); //***********************************************// // // diff --git a/Source/LoadByOS/LinuxConfigApp/Libation.desktop b/Source/LoadByOS/LinuxConfigApp/Libation.desktop index 0b935ce8..2890481c 100644 --- a/Source/LoadByOS/LinuxConfigApp/Libation.desktop +++ b/Source/LoadByOS/LinuxConfigApp/Libation.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Name=Libation -Exec=libation +Exec=/usr/bin/libation Icon=libation Comment=Liberate your Audiobooks Terminal=false diff --git a/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj b/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj index 9d9a1b3e..855b7034 100644 --- a/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj +++ b/Source/LoadByOS/LinuxConfigApp/LinuxConfigApp.csproj @@ -35,27 +35,15 @@ - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - + Always Always + + Always + - \ No newline at end of file + diff --git a/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs b/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs index 7c36dda8..bd6458b0 100644 --- a/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs +++ b/Source/LoadByOS/LinuxConfigApp/LinuxInterop.cs @@ -3,41 +3,40 @@ using System.Diagnostics; namespace LinuxConfigApp { - internal class LinuxInterop : IInteropFunctions - { + internal class LinuxInterop : IInteropFunctions + { //Different terminal apps possibly installed on a linux system // [0] console executable // [1] argument to set the concole's title // [2] argument to pass a command to be executed to the terminal static readonly string[][] consoleCommands = - { - new[] {"konsole", "--title", "-e"}, - new[] {"gnome-terminal", "--title", "--"}, - new[] {"mate-terminal", "--title", "-x"}, - new[] {"xterm", "-T", "-e"}, - }; + { + new[] {"konsole", "--title", "-e"}, + new[] {"gnome-terminal", "--title", "--"}, + new[] {"mate-terminal", "--title", "-x"}, + new[] {"xterm", "-T", "-e"}, + }; public LinuxInterop() { } - public LinuxInterop(params object[] values) { } + public LinuxInterop(params object[] values) { } - public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException(); - public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException(); + public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException(); + public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException(); - //only run the audo updater is the current app was installed from the - //.deb package. Try to detect this by checking if the symlink exists. - public bool CanUpdate => Directory.Exists("/usr/lib/libation"); - public void InstallUpdate(string updateBundle) + //only run the auto updater if the current app was installed from the + //.deb package. Try to detect this by checking if the symlink exists. + public bool CanUpdate => Directory.Exists("/usr/lib/libation"); + public void InstallUpdate(string updateBundle) { RunAsRoot("apt", $"install '{updateBundle}'"); } public Process RunAsRoot(string exe, string args) - { + { //cribbed this script from VirtualBox's guest additions installer. //It's designed to launch the system's gui superuser password //prompt across multiple distributions and desktop environments. - const string runasroot = "/tmp/runasroot.sh"; - File.WriteAllBytes(runasroot, Properties.Resources.runasroot); + const string runasroot = "runasroot.sh"; string command = $"{exe ?? ""} {args ?? ""}".Trim(); @@ -50,24 +49,23 @@ namespace LinuxConfigApp ArgumentList = { console[1], - $"Running '{exe}' as root", + $"Running '{exe}' as root", // console title console[2], "/bin/sh", - runasroot, - "Installing libation.deb", - command, - $"Please run '{command}' manually" + Path.Combine(Configuration.ProcessDirectory, runasroot), //script file + "Installing libation.deb", //command title + command, // command to execute vis /bin/sh + $"Please run '{command}' manually" // error message to display in the terminal } }; - try { return Process.Start(psi); } catch { } } - return null; + throw new PlatformNotSupportedException($"Could not start any of the supported terminals: {string.Join(", ", consoleCommands.Select(c => c[0]))}"); } } } diff --git a/Source/LoadByOS/LinuxConfigApp/Program.cs b/Source/LoadByOS/LinuxConfigApp/Program.cs index e98c8ff9..81d89b03 100644 --- a/Source/LoadByOS/LinuxConfigApp/Program.cs +++ b/Source/LoadByOS/LinuxConfigApp/Program.cs @@ -1,11 +1,7 @@ -using AppScaffolding; - -namespace LinuxConfigApp +namespace LinuxConfigApp { - class Program : OSConfigBase + class Program { - public override Type InteropFunctionsType => typeof(LinuxInterop); - - static void Main() => new Program().Run(); + static void Main() { } } } diff --git a/Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs b/Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs deleted file mode 100644 index b48060ab..00000000 --- a/Source/LoadByOS/LinuxConfigApp/Properties/Resources.Designer.cs +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace LinuxConfigApp.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LinuxConfigApp.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Byte[]. - /// - internal static byte[] runasroot { - get { - object obj = ResourceManager.GetObject("runasroot", resourceCulture); - return ((byte[])(obj)); - } - } - } -} diff --git a/Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx b/Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx deleted file mode 100644 index ef269a0f..00000000 --- a/Source/LoadByOS/LinuxConfigApp/Properties/Resources.resx +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\Resources\runasroot.sh;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Source/LoadByOS/LinuxConfigApp/glass-with-glow_256.svg b/Source/LoadByOS/LinuxConfigApp/glass-with-glow_256.svg deleted file mode 100644 index df935c14..00000000 --- a/Source/LoadByOS/LinuxConfigApp/glass-with-glow_256.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - diff --git a/Source/LoadByOS/LinuxConfigApp/libation_glass.svg b/Source/LoadByOS/LinuxConfigApp/libation_glass.svg new file mode 100644 index 00000000..db08f07f --- /dev/null +++ b/Source/LoadByOS/LinuxConfigApp/libation_glass.svg @@ -0,0 +1,28 @@ + + + + + diff --git a/Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh b/Source/LoadByOS/LinuxConfigApp/runasroot.sh similarity index 100% rename from Source/LoadByOS/LinuxConfigApp/Resources/runasroot.sh rename to Source/LoadByOS/LinuxConfigApp/runasroot.sh diff --git a/Source/LoadByOS/MacOSConfigApp/Info.plist b/Source/LoadByOS/MacOSConfigApp/Info.plist index 90cbcaf1..b9d85062 100644 --- a/Source/LoadByOS/MacOSConfigApp/Info.plist +++ b/Source/LoadByOS/MacOSConfigApp/Info.plist @@ -7,6 +7,10 @@ Libation CFBundleName Libation + LSArchitecturePriority + ARCHITECTURE_STRING + LSMinimumSystemVersion + 10.15.0 CFBundleIdentifier org.libation.macos NSHighResolutionCapable diff --git a/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs b/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs index 8bf6f9a3..a8993345 100644 --- a/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs +++ b/Source/LoadByOS/MacOSConfigApp/MacOSInterop.cs @@ -21,7 +21,7 @@ namespace MacOSConfigApp Serilog.Log.Information($"Extracting update bundle to {AppPath}"); //tar wil overwrite existing without elevated privileges - Process.Start("tar", $"-xzf \"{updateBundle}\" -C \"/Applications\"").WaitForExit(); + Process.Start("tar", $"-xf \"{updateBundle}\" -C \"/Applications\"").WaitForExit(); //For now, it seems like this step is unnecessary. We can overwrite and //run Libation without needing to re-add the exception. This is insurance. diff --git a/Source/LoadByOS/MacOSConfigApp/Program.cs b/Source/LoadByOS/MacOSConfigApp/Program.cs index bab9d875..dcc8ffbc 100644 --- a/Source/LoadByOS/MacOSConfigApp/Program.cs +++ b/Source/LoadByOS/MacOSConfigApp/Program.cs @@ -1,11 +1,7 @@ -using AppScaffolding; - -namespace MacOSConfigApp +namespace MacOSConfigApp { - class Program : OSConfigBase + class Program { - public override Type InteropFunctionsType => typeof(MacOSInterop); - - static void Main() => new Program().Run(); + static void Main() { } } } diff --git a/Source/LoadByOS/WindowsConfigApp/Program.cs b/Source/LoadByOS/WindowsConfigApp/Program.cs index 1786ce7b..72473a31 100644 --- a/Source/LoadByOS/WindowsConfigApp/Program.cs +++ b/Source/LoadByOS/WindowsConfigApp/Program.cs @@ -1,18 +1,7 @@ -using AppScaffolding; - namespace WindowsConfigApp { - class Program : OSConfigBase + class Program { - public override Type InteropFunctionsType => typeof(WinInterop); - public override Type[] ReferencedTypes => new Type[] - { - typeof(Bitmap), - typeof(Dinah.Core.WindowsDesktop.GitClient), - typeof(Accessibility.IAccIdentity), - typeof(Microsoft.Win32.SystemEvents) - }; - - static void Main() => new Program().Run(); + static void Main() { } } } \ No newline at end of file