diff --git a/.github/workflows/dl-stats.yml b/.github/workflows/dl-stats.yml new file mode 100644 index 000000000..0d9ca5dd7 --- /dev/null +++ b/.github/workflows/dl-stats.yml @@ -0,0 +1,63 @@ +name: Report Download Stats + +on: + schedule: + - cron: '0/15 * * * *' # run every 15 min - don't forget to adjust the "interval" in the json sent to the metrics endpoint + +jobs: + report-download-stats: + runs-on: ubuntu-latest + steps: + - name: Get download count of latest releases + id: get-stats + uses: actions/github-script@v6 + with: + script: | + const query = `query($owner:String!, $name:String!) { + repository(owner:$owner, name:$name){ + releases(first: 10, orderBy: {field: CREATED_AT, direction: DESC}) { + nodes { + isPrerelease + tagName + releaseAssets(first: 20) { + nodes { + name + downloadCount + } + } + } + } + } + }`; + const variables = { + owner: context.repo.owner, + name: context.repo.repo + } + return await github.graphql(query, variables) + - name: Transform Results + id: transform-stats + run: | + TIME=$(($(date +%s) / $INTERVAL * $INTERVAL)) + echo ${JSON_DATA} | jq --arg TIME "$TIME" --arg INTERVAL "$INTERVAL" -c '.repository.releases.nodes[] | select(.isPrerelease == false) | .tagName as $tagName | .releaseAssets.nodes[] | {filename: .name, downloads: .downloadCount, release: $tagName, time: ($TIME|tonumber), interval: ($INTERVAL|tonumber)}' > input.json + + jq -c 'select(.filename|endswith("-x86_64.AppImage")) | {name: "github.releases.downloads", tags: ["file=AppImage", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json + jq -c 'select(.filename|endswith("_amd64.deb")) | {name: "github.releases.downloads", tags: ["file=deb", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json + jq -c 'select(.filename|endswith("-x64.msi")) | {name: "github.releases.downloads", tags: ["file=msi", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json + jq -c 'select(.filename|endswith("-x64.exe")) | {name: "github.releases.downloads", tags: ["file=exe", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json + jq -c 'select(.filename|endswith("-arm64.dmg")) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=arm64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json + jq -c 'select(.filename|endswith(".dmg")) | select(.filename|endswith("-arm64.dmg")|not) | {name: "github.releases.downloads", tags: ["file=dmg", "version=\(.release)", "arch=amd64"], value: .downloads, interval: .interval, time: .time}' input.json >> output.json + + RESULT=$(jq -s -c "." output.json) + echo "::set-output name=result::${RESULT}" + env: + INTERVAL: 900 + JSON_DATA: ${{ steps.get-stats.outputs.result }} + - name: Upload Results + uses: fjogeleit/http-request-action@v1 + with: + url: 'https://graphite-us-central1.grafana.net/metrics' + method: 'POST' + contentType: 'application/json' + bearerToken: ${{ secrets.GRAFANA_GRAPHITE_TOKEN }} + data: ${{ steps.transform-stats.outputs.result }} + continue-on-error: true # currently there seems to be a problem with the metrics endpoint, failing every now and then diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 56ea3e94a..1ba5822e3 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -91,6 +91,7 @@ jobs: --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" --java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\"" --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\"" --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\"" --mac-package-identifier org.cryptomator @@ -188,33 +189,14 @@ jobs: Cryptomator-${VERSION_NO}.dmg dmg env: VERSION_NO: ${{ steps.versions.outputs.semVerNum }} - - name: Install notarization credentials - if: startsWith(github.ref, 'refs/tags/') - run: | - # create temporary keychain - KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db - security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} - security set-keychain-settings -lut 900 ${KEYCHAIN_PATH} - security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} - - # import credentials from secrets - sudo xcode-select -s /Applications/Xcode_13.0.app - xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}" - env: - NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} - NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} - NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }} - NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} - NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }} - name: Notarize .dmg if: startsWith(github.ref, 'refs/tags/') - run: | - KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db - sudo xcode-select -s /Applications/Xcode_13.0.app - xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait - xcrun stapler staple Cryptomator-*.dmg - env: - NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} + uses: cocoalibs/xcode-notarization-action@v1 + with: + app-path: 'Cryptomator-*.dmg' + apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} + password: ${{ secrets.MACOS_NOTARIZATION_PW }} + team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} - name: Add possible alpha/beta tags to installer name run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg - name: Create detached GPG signature with key 615D449FE6E6A235 @@ -228,10 +210,6 @@ jobs: if: ${{ always() }} run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db continue-on-error: true - - name: Clean up notarization credentials - if: ${{ always() }} - run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db - continue-on-error: true - name: Upload artifacts uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 94e13c514..1c9195c43 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -98,6 +98,7 @@ jobs: --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\"" + --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\"" --resource-dir dist/win/resources --icon dist/win/resources/Cryptomator.ico - name: Patch Application Directory @@ -189,6 +190,19 @@ jobs: semVerStr: ${{ steps.versions.outputs.semVerStr }} revNum: ${{ steps.versions.outputs.revNum }} + publish-winget: + name: Publish on winget repo + runs-on: windows-latest + needs: [build-msi] + steps: + - name: Submit package to Windows Package Manager Community Repository + run: | + iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe + $github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json + $installerUrl = $github.release.assets | Where-Object -Property name -match 'Cryptomator-*.msi' | Select -ExpandProperty browser_download_url -First 1 + .\wingetcreate.exe update Cryptomator.Cryptomator -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.CRYPTOBOT_WINGET_TOKEN }} + shell: pwsh + build-exe: name: Build .exe installer runs-on: windows-latest diff --git a/.gitignore b/.gitignore index 5c84c0dfb..796458fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,25 +5,9 @@ *.war *.ear -# Eclipse Settings Files # -.settings -.project -.classpath - # Maven # target/ pom.xml.versionsBackup -# IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) # -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/shelf -.idea/dictionaries/** -!.idea/dictionaries/dict_* -.idea/compiler.xml -.idea/jarRepositories.xml -.idea/uiDesigner.xml -.idea/**/libraries/ -*.iml - +# Java Crash Logs hs_err_pid*.log \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..8e78cfe58 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,13 @@ +# see https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems + +# Default ignored files +/shelf/ +/workspace.xml +/usage.statistics.xml +/dictionaries/ + +# generated from Maven +/jarRepositories.xml +/modules.xml +/*.iml +/libraries/*.xml \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..18be437d6 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index 865ac739e..329e03d0b 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -66,6 +66,7 @@ + diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index 0d2db1219..76606b51f 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -14,9 +14,17 @@ while getopts ":s:" o; do done shift "$((OPTIND-1))" -# prepare working dir and variables +# prepare working dir cd $(dirname $0) -rm -rf runtime dmg +rm -rf runtime dmg *.app *.dmg + +# set variables +APP_NAME="Cryptomator" +VENDOR="Skymatic GmbH" +COPYRIGHT_YEARS="2016 - 2022" +PACKAGE_IDENTIFIER="org.cryptomator" +MAIN_JAR_GLOB="cryptomator-*.jar" +MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" REVISION_NO=`git rev-list --count HEAD` VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'` @@ -31,7 +39,7 @@ fi # compile mvn -B -f../../../pom.xml clean package -DskipTests -Pmac -cp ../../../target/cryptomator-*.jar ../../../target/mods +cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods # add runtime ${JAVA_HOME}/bin/jlink \ @@ -51,11 +59,11 @@ ${JAVA_HOME}/bin/jpackage \ --runtime-image runtime \ --input ../../../target/libs \ --module-path ../../../target/mods \ - --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator \ + --module ${MODULE_AND_MAIN_CLASS} \ --dest . \ - --name Cryptomator \ - --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2022 Skymatic GmbH" \ + --name ${APP_NAME} \ + --vendor "${VENDOR}" \ + --copyright "(C) ${COPYRIGHT_YEARS} ${VENDOR}" \ --app-version "${VERSION_NO}" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ @@ -63,20 +71,21 @@ ${JAVA_HOME}/bin/jpackage \ --java-options "-Dapple.awt.enableTemplateImages=true" \ --java-options "-Dsun.java2d.metal=true" \ --java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \ - --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" \ - --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" \ - --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" \ - --java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/Cryptomator/key.p12\"" \ - --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" \ + --java-options "-Dcryptomator.logDir=\"~/Library/Logs/${APP_NAME}\"" \ + --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/${APP_NAME}/Plugins\"" \ + --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/${APP_NAME}/settings.json\"" \ + --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/${APP_NAME}/ipc.socket\"" \ + --java-options "-Dcryptomator.p12Path=\"~/Library/Application Support/${APP_NAME}/key.p12\"" \ + --java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"${APP_NAME}\"" \ --java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.buildNumber=\"dmg-${REVISION_NO}\"" \ - --mac-package-identifier org.cryptomator \ + --mac-package-identifier ${PACKAGE_IDENTIFIER} \ --resource-dir ../resources # transform app dir -cp ../resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ -sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist -sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist +cp ../resources/${APP_NAME}-Vault.icns ${APP_NAME}.app/Contents/Resources/ +sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" ${APP_NAME}.app/Contents/Info.plist +sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist # generate license mvn -B -f../../../pom.xml license:add-third-party \ @@ -90,8 +99,8 @@ mvn -B -f../../../pom.xml license:add-third-party \ # codesign if [ -n "${CODESIGN_IDENTITY}" ]; then - find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do + find ${APP_NAME}.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + for JAR_PATH in `find ${APP_NAME}.app -name "*.jar"`; do if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then JAR_FILENAME=$(basename ${JAR_PATH}) OUTPUT_PATH=${JAR_PATH%.*} @@ -106,25 +115,25 @@ if [ -n "${CODESIGN_IDENTITY}" ]; then rm -r ${OUTPUT_PATH} fi done - echo "Codesigning Cryptomator.app..." - codesign --force --deep --entitlements ../Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app + echo "Codesigning ${APP_NAME}.app..." + codesign --force --deep --entitlements ../${APP_NAME}.entitlements -o runtime -s ${CODESIGN_IDENTITY} ${APP_NAME}.app fi # prepare dmg contents mkdir dmg -mv Cryptomator.app dmg +mv ${APP_NAME}.app dmg cp resources/macFUSE.webloc dmg # create dmg create-dmg \ - --volname Cryptomator \ - --volicon "resources/Cryptomator-Volume.icns" \ - --background "resources/Cryptomator-background.tiff" \ + --volname ${APP_NAME} \ + --volicon "resources/${APP_NAME}-Volume.icns" \ + --background "resources/${APP_NAME}-background.tiff" \ --window-pos 400 100 \ --window-size 640 694 \ --icon-size 128 \ - --icon "Cryptomator.app" 128 245 \ - --hide-extension "Cryptomator.app" \ + --icon "${APP_NAME}.app" 128 245 \ + --hide-extension "${APP_NAME}.app" \ --icon "macFUSE.webloc" 320 501 \ --hide-extension "macFUSE.webloc" \ --app-drop-link 512 245 \ @@ -132,4 +141,4 @@ create-dmg \ --icon ".background" 128 758 \ --icon ".fseventsd" 320 758 \ --icon ".VolumeIcon.icns" 512 758 \ - Cryptomator-${VERSION_NO}.dmg dmg + ${APP_NAME}-${VERSION_NO}.dmg dmg diff --git a/dist/win/build.bat b/dist/win/build.bat index 8ca9183b4..c97ebbb35 100644 --- a/dist/win/build.bat +++ b/dist/win/build.bat @@ -1,2 +1,23 @@ @echo off -powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1 \ No newline at end of file +:: Default values for Cryptomator builds +SET APPNAME="Cryptomator" +SET MAIN_JAR_GLOB="cryptomator-*" +SET UPGRADE_UUID="bda45523-42b1-4cae-9354-a45475ed4775" +SET VENDOR="Skymatic GmbH" +SET FIRST_COPYRIGHT_YEAR=2016 +SET ABOUT_URL="https://cryptomator.org" +SET UPDATE_URL="https://cryptomator.org/downloads/" +SET HELP_URL="https://cryptomator.org/contact/" +SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator" + +powershell -NoLogo -NoExit -ExecutionPolicy Unrestricted -Command .\build.ps1^ + -AppName %APPNAME%^ + -MainJarGlob "%MAIN_JAR_GLOB%"^ + -ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^ + -UpgradeUUID "%UPGRADE_UUID%"^ + -Vendor ""%VENDOR%""^ + -CopyrightStartYear %FIRST_COPYRIGHT_YEAR%^ + -AboutUrl "%ABOUT_URL%"^ + -HelpUrl "%HELP_URL%"^ + -UpdateUrl "%UPDATE_URL%"^ + -Clean 1 \ No newline at end of file diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index e491769e1..38c4dc4ae 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -1,5 +1,15 @@ -# check parameters -$clean = $args[0] -eq "fresh" +Param( + [Parameter(Mandatory, HelpMessage="Please provide a name for the app")][string] $AppName, + [Parameter(Mandatory, HelpMessage="Please provide the glob pattern to identify the main jar")][string] $MainJarGlob, + [Parameter(Mandatory, HelpMessage="Please provide the module- and main class path to start the app")][string] $ModuleAndMainClass, + [Parameter(Mandatory, HelpMessage="Please provide the windows upgrade uuid for the installer")][string] $UpgradeUUID, + [Parameter(Mandatory, HelpMessage="Please provide the name of the vendor")][string] $Vendor, + [Parameter(Mandatory, HelpMessage="Please provide the starting year for the copyright notice")][int] $CopyrightStartYear, + [Parameter(Mandatory, HelpMessage="Please provide a help url")][string] $HelpUrl, + [Parameter(Mandatory, HelpMessage="Please provide an update url")][string] $UpdateUrl, + [Parameter(Mandatory, HelpMessage="Please provide an about url")][string] $AboutUrl, + [bool] $clean +) # check preconditions if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null) @@ -24,12 +34,11 @@ Write-Output "`$revisionNo=$revisionNo" Write-Output "`$buildDir=$buildDir" Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME" -$vendor = "Skymatic GmbH" -$copyright = "(C) 2016 - 2022 Skymatic GmbH" +$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor" # compile &mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin -Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\..\target\mods" +Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods" # add runtime $runtimeImagePath = '.\runtime' @@ -48,7 +57,7 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) { --strip-debug ` --compress=1 -$appPath = '.\Cryptomator' +$appPath = ".\$AppName" if ($clean -and (Test-Path -Path $appPath)) { Remove-Item -Path $appPath -Force -Recurse } @@ -60,27 +69,28 @@ if ($clean -and (Test-Path -Path $appPath)) { --runtime-image runtime ` --input ../../target/libs ` --module-path ../../target/mods ` - --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator ` + --module $ModuleAndMainClass ` --dest . ` - --name Cryptomator ` - --vendor $vendor ` + --name $AppName ` + --vendor $Vendor ` --copyright $copyright ` --java-options "-Xss5m" ` --java-options "-Xmx256m" ` --java-options "-Dcryptomator.appVersion=`"$semVerNo`"" ` --app-version "$semVerNo.$revisionNo" ` --java-options "-Dfile.encoding=`"utf-8`"" ` - --java-options "-Dcryptomator.logDir=`"~/AppData/Roaming/Cryptomator`"" ` - --java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/Cryptomator/Plugins`"" ` - --java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/Cryptomator/settings.json`"" ` - --java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/Cryptomator/key.p12`"" ` - --java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/Cryptomator/ipc.socket`"" ` - --java-options "-Dcryptomator.keychainPath=`"~/AppData/Roaming/Cryptomator/keychain.json`"" ` - --java-options "-Dcryptomator.mountPointsDir=`"~/Cryptomator`"" ` + --java-options "-Dcryptomator.logDir=`"~/AppData/Roaming/$AppName`"" ` + --java-options "-Dcryptomator.pluginDir=`"~/AppData/Roaming/$AppName/Plugins`"" ` + --java-options "-Dcryptomator.settingsPath=`"~/AppData/Roaming/$AppName/settings.json`"" ` + --java-options "-Dcryptomator.ipcSocketPath=`"~/AppData/Roaming/$AppName/ipc.socket`"" ` + --java-options "-Dcryptomator.keychainPath=`"~/AppData/Roaming/$AppName/keychain.json`"" ` + --java-options "-Dcryptomator.p12Path=`"~/AppData/Roaming/$AppName/key.p12`"" ` + --java-options "-Dcryptomator.mountPointsDir=`"~/$AppName`"" ` + --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=`"$AppName`"" ` --java-options "-Dcryptomator.showTrayIcon=true" ` --java-options "-Dcryptomator.buildNumber=`"msi-$revisionNo`"" ` --resource-dir resources ` - --icon resources/Cryptomator.ico + --icon resources/$AppName.ico #Create RTF license for msi &mvn -B -f $buildDir/../../pom.xml license:add-third-party ` @@ -93,33 +103,29 @@ if ($clean -and (Test-Path -Path $appPath)) { "-Dlicense.licenseMergesUrl=file:///$buildDir/../../license/merges" # patch app dir -Copy-Item "contrib\*" -Destination "Cryptomator" -attrib -r "Cryptomator\Cryptomator.exe" - -$aboutUrl="https://cryptomator.org" -$updateUrl="https://cryptomator.org/downloads/" -$helpUrl="https://cryptomator.org/contact/" +Copy-Item "contrib\*" -Destination "$AppName" +attrib -r "$AppName\$AppName.exe" # create .msi $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" & "$Env:JAVA_HOME\bin\jpackage" ` --verbose ` --type msi ` - --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 ` - --app-image Cryptomator ` + --win-upgrade-uuid $UpgradeUUID ` + --app-image $AppName ` --dest installer ` - --name Cryptomator ` - --vendor $vendor ` + --name $AppName ` + --vendor $Vendor ` --copyright $copyright ` --app-version "$semVerNo" ` --win-menu ` --win-dir-chooser ` --win-shortcut-prompt ` - --win-update-url $updateUrl ` - --win-menu-group Cryptomator ` + --win-menu-group $AppName ` --resource-dir resources ` - --about-url $aboutUrl ` --license-file resources/license.rtf ` + --win-update-url $UpdateUrl ` + --about-url $AboutUrl ` --file-associations resources/FAvaultFile.properties #Create RTF license for bundle @@ -140,14 +146,14 @@ Write-Output "Downloading ${winfspMsiUrl}..." Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default # copy MSI to bundle resources -Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Cryptomator.msi" +Copy-Item ".\installer\$AppName-*.msi" -Destination ".\bundle\resources\$AppName.msi" # create bundle including winfsp & "$env:WIX\bin\candle.exe" .\bundle\bundleWithWinfsp.wxs -ext WixBalExtension -out bundle\ ` -dBundleVersion="$semVerNo.$revisionNo" ` - -dBundleVendor="$vendor" ` + -dBundleVendor="$Vendor" ` -dBundleCopyright="$copyright" ` - -dAboutUrl="$aboutUrl" ` - -dHelpUrl="$helpUrl" ` - -dUpdateUrl="$updateUrl" -& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\Cryptomator-Installer.exe \ No newline at end of file + -dAboutUrl="$AboutUrl" ` + -dHelpUrl="$HelpUrl" ` + -dUpdateUrl="$UpdateUrl" +& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\$AppName-Installer.exe \ No newline at end of file diff --git a/dist/win/contrib/patchWebDAV.bat b/dist/win/contrib/patchWebDAV.bat index e31249831..1726147d2 100644 --- a/dist/win/contrib/patchWebDAV.bat +++ b/dist/win/contrib/patchWebDAV.bat @@ -1,3 +1,7 @@ @echo off +:: Default values for Cryptomator builds +SET LOOPBACK_ALIAS="cryptomator-vault" + cd %~dp0 -powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1 \ No newline at end of file +powershell -NoLogo -NonInteractive -ExecutionPolicy Unrestricted -Command .\patchWebDAV.ps1^ + -LoopbackAlias %LOOPBACK_ALIAS% \ No newline at end of file diff --git a/dist/win/contrib/patchWebDAV.ps1 b/dist/win/contrib/patchWebDAV.ps1 index 51b063560..9d79ab900 100644 --- a/dist/win/contrib/patchWebDAV.ps1 +++ b/dist/win/contrib/patchWebDAV.ps1 @@ -1,10 +1,16 @@ #Requires -RunAsAdministrator +Param( + [Parameter(Mandatory, HelpMessage="Please provide an alias for 127.0.0.1")][string] $LoopbackAlias +) -# Adds for address 127.0.0.1 the 'cryptomator-vault' alias to the hosts file +# Adds an alias for 127.0.0.1 to the hosts file function Add-AliasToHost { + param ( + [string]$LoopbackAlias + ) $sysdir = [Environment]::SystemDirectory $hostsFile = "$sysdir\drivers\etc\hosts" - $aliasLine = '127.0.0.1 cryptomator-vault' + $aliasLine = "127.0.0.1 $LoopbackAlias" foreach ($line in Get-Content $hostsFile) { if ($line -eq $aliasLine){ @@ -49,7 +55,7 @@ function Edit-ProviderOrder { } -Add-AliasToHost +Add-AliasToHost $LoopbackAlias Write-Output 'Ensured alias exists in hosts file' Set-WebDAVFileSizeLimit diff --git a/dist/win/resources/customWizard.wxi b/dist/win/resources/customWizard.wxi index ce82c2933..0cd34e44f 100644 --- a/dist/win/resources/customWizard.wxi +++ b/dist/win/resources/customWizard.wxi @@ -91,7 +91,7 @@ FOUNDRUNNINGAPP diff --git a/dist/win/resources/main.wxs b/dist/win/resources/main.wxs index 09b555caa..b8703a14d 100644 --- a/dist/win/resources/main.wxs +++ b/dist/win/resources/main.wxs @@ -86,12 +86,12 @@ - - - - + + + + - + @@ -130,12 +130,12 @@ diff --git a/dist/win/resources/overrides.wxi b/dist/win/resources/overrides.wxi new file mode 100644 index 000000000..60133a35b --- /dev/null +++ b/dist/win/resources/overrides.wxi @@ -0,0 +1,39 @@ + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 23a97717d..2be9cc560 100644 --- a/pom.xml +++ b/pom.xml @@ -28,12 +28,12 @@ 2.1.0-beta3 - 2.4.1 + 2.4.2 1.1.0 - 1.1.0 - 1.1.0 + 1.1.1 + 1.1.1 1.1.0 - 1.3.3 + 1.3.4 1.3.3 1.2.7 diff --git a/src/main/java/org/cryptomator/common/CatchingExecutors.java b/src/main/java/org/cryptomator/common/CatchingExecutors.java new file mode 100644 index 000000000..900f81ec9 --- /dev/null +++ b/src/main/java/org/cryptomator/common/CatchingExecutors.java @@ -0,0 +1,92 @@ +package org.cryptomator.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javafx.application.Platform; +import javafx.concurrent.Task; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +//Inspired by: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html#afterExecute(java.lang.Runnable,java.lang.Throwable) +public final class CatchingExecutors { + + private static final Logger LOG = LoggerFactory.getLogger(CatchingExecutors.class); + + private CatchingExecutors() { /* NO-OP */ } + + public static class CatchingScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { + + public CatchingScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { + super(corePoolSize, threadFactory); + } + + @Override + protected void afterExecute(Runnable runnable, Throwable throwable) { + super.afterExecute(runnable, throwable); + afterExecuteInternal(runnable, throwable); + } + } + + public static class CatchingThreadPoolExecutor extends ThreadPoolExecutor { + + public CatchingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); + } + + @Override + protected void afterExecute(Runnable runnable, Throwable throwable) { + super.afterExecute(runnable, throwable); + afterExecuteInternal(runnable, throwable); + } + } + + private static void afterExecuteInternal(Runnable runnable, Throwable throwable) { + if (throwable != null) { + callHandler(Thread.currentThread(), throwable); + } else if (runnable instanceof Task t) { + afterExecuteTask(t); + } else if (runnable instanceof Future f) { + afterExecuteFuture(f); + } + //Errors in this method are delegated to the UncaughtExceptionHandler of the current thread + } + + private static void callHandler(Thread thread, Throwable throwable) { + Objects.requireNonNullElseGet(thread.getUncaughtExceptionHandler(), CatchingExecutors::fallbackHandler).uncaughtException(thread, throwable); + } + + private static Thread.UncaughtExceptionHandler fallbackHandler() { + return (thread, throwable) -> LOG.error("FALLBACK: Uncaught exception in " + thread.getName(), throwable); + } + + private static void afterExecuteTask(Task task) { + var caller = Thread.currentThread(); + Platform.runLater(() -> { + if (task.getOnFailed() == null) { + callHandler(caller, task.getException()); + } + }); + } + + private static void afterExecuteFuture(Future future) { + assert future.isDone(); + try { + future.get(); + } catch (CancellationException ce) { + callHandler(Thread.currentThread(), ce); + } catch (ExecutionException ee) { + callHandler(Thread.currentThread(), ee.getCause()); + } catch (InterruptedException ie) { + //Ignore/Reset + Thread.currentThread().interrupt(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/common/CommonsModule.java b/src/main/java/org/cryptomator/common/CommonsModule.java index eb7e6cd13..edb4f063b 100644 --- a/src/main/java/org/cryptomator/common/CommonsModule.java +++ b/src/main/java/org/cryptomator/common/CommonsModule.java @@ -12,9 +12,7 @@ import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.keychain.KeychainModule; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.SettingsProvider; -import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultComponent; -import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultListModule; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.frontend.webdav.WebDavServer; @@ -25,16 +23,13 @@ import javax.inject.Named; import javax.inject.Singleton; import javafx.beans.binding.Binding; import javafx.beans.binding.Bindings; -import javafx.collections.ObservableList; import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Comparator; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -93,7 +88,7 @@ public abstract class CommonsModule { @Singleton static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) { final AtomicInteger threadNumber = new AtomicInteger(1); - ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> { + ScheduledExecutorService executorService = new CatchingExecutors.CatchingScheduledThreadPoolExecutor(NUM_SCHEDULER_THREADS, r -> { String name = String.format("App Scheduled Executor %02d", threadNumber.getAndIncrement()); Thread t = new Thread(r); t.setName(name); @@ -110,7 +105,7 @@ public abstract class CommonsModule { @Singleton static ExecutorService provideExecutorService(ShutdownHook shutdownHook) { final AtomicInteger threadNumber = new AtomicInteger(1); - ExecutorService executorService = new ThreadPoolExecutor(NUM_CORE_BG_THREADS, Integer.MAX_VALUE, BG_THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> { + ExecutorService executorService = new CatchingExecutors.CatchingThreadPoolExecutor(NUM_CORE_BG_THREADS, Integer.MAX_VALUE, BG_THREAD_KEEPALIVE_SECONDS, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> { String name = String.format("App Background Thread %03d", threadNumber.getAndIncrement()); Thread t = new Thread(r); t.setName(name); diff --git a/src/main/java/org/cryptomator/common/Constants.java b/src/main/java/org/cryptomator/common/Constants.java index 90bd3c8ec..5069002e7 100644 --- a/src/main/java/org/cryptomator/common/Constants.java +++ b/src/main/java/org/cryptomator/common/Constants.java @@ -6,6 +6,7 @@ public interface Constants { String MASTERKEY_BACKUP_SUFFIX = ".bkup"; String VAULTCONFIG_FILENAME = "vault.cryptomator"; String CRYPTOMATOR_FILENAME_EXT = ".cryptomator"; + String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator"; byte[] PEPPER = new byte[0]; } diff --git a/src/main/java/org/cryptomator/common/LocationPreset.java b/src/main/java/org/cryptomator/common/LocationPreset.java new file mode 100644 index 000000000..17d276bf8 --- /dev/null +++ b/src/main/java/org/cryptomator/common/LocationPreset.java @@ -0,0 +1,64 @@ +package org.cryptomator.common; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +/** + * Enum of common cloud providers and their default local storage location path. + */ +public enum LocationPreset { + + DROPBOX("Dropbox", "~/Dropbox"), + ICLOUDDRIVE("iCloud Drive", "~/Library/Mobile Documents/com~apple~CloudDocs", "~/iCloudDrive"), + GDRIVE("Google Drive", "~/Google Drive/My Drive", "~/Google Drive"), + MEGA("MEGA", "~/MEGA"), + ONEDRIVE("OneDrive", "~/OneDrive"), + PCLOUD("pCloud", "~/pCloudDrive"), + + LOCAL("local"); + + private final String name; + private final List candidates; + + LocationPreset(String name, String... candidates) { + this.name = name; + this.candidates = Arrays.stream(candidates).map(UserHome::resolve).map(Path::of).toList(); + } + + /** + * Checks for this LocationPreset if any of the associated paths exist. + * + * @return the first existing path or null, if none exists. + */ + public Path existingPath() { + return candidates.stream().filter(Files::isDirectory).findFirst().orElse(null); + } + + public String getDisplayName() { + return name; + } + + @Override + public String toString() { + return getDisplayName(); + } + + //this contruct is needed, since static members are initialized after every enum member is initialized + //TODO: refactor this to normal class and use this also in different parts of the project + private static class UserHome { + + private static final String USER_HOME = System.getProperty("user.home"); + + private static String resolve(String path) { + if (path.startsWith("~/")) { + return UserHome.USER_HOME + path.substring(1); + } else { + return path; + } + } + } + +} + diff --git a/src/main/java/org/cryptomator/common/settings/Settings.java b/src/main/java/org/cryptomator/common/settings/Settings.java index 8cc23bbfc..36d4d16aa 100644 --- a/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/src/main/java/org/cryptomator/common/settings/Settings.java @@ -36,7 +36,7 @@ public class Settings { public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV; public static final boolean DEFAULT_DEBUG_MODE = false; - public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE; + public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = VolumeImpl.FUSE; public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT; @Deprecated // to be changed to "whatever is available" eventually public static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess"; diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index 96bf3b252..5bba0b79b 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -10,6 +10,7 @@ package org.cryptomator.common.vaults; import com.google.common.base.Strings; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.Constants; import org.cryptomator.common.mountpoint.InvalidMountPointException; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.vaults.Volume.VolumeException; @@ -125,6 +126,7 @@ public class Vault { .withKeyLoader(keyLoader) // .withFlags(flags) // .withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) // + .withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) // .build(); return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps); } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultComponent.java b/src/main/java/org/cryptomator/common/vaults/VaultComponent.java index be844f510..ae525d7b0 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultComponent.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultComponent.java @@ -19,22 +19,13 @@ public interface VaultComponent { Vault vault(); - @Subcomponent.Builder - interface Builder { + @Subcomponent.Factory + interface Factory { - @BindsInstance - Builder vaultSettings(VaultSettings vaultSettings); + VaultComponent create(@BindsInstance VaultSettings vaultSettings, // + @BindsInstance VaultConfigCache configCache, // + @BindsInstance VaultState.Value vaultState, // + @BindsInstance @Nullable @Named("lastKnownException") Exception initialErrorCause); - @BindsInstance - Builder vaultConfigCache(VaultConfigCache configCache); - - @BindsInstance - Builder initialVaultState(VaultState.Value vaultState); - - @BindsInstance - Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause); - - VaultComponent build(); } - -} +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 6a8c31d4f..138d74e36 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -38,15 +38,15 @@ public class VaultListManager { private static final Logger LOG = LoggerFactory.getLogger(VaultListManager.class); private final AutoLocker autoLocker; - private final VaultComponent.Builder vaultComponentBuilder; + private final VaultComponent.Factory vaultComponentFactory; private final ObservableList vaultList; private final String defaultVaultName; @Inject - public VaultListManager(ObservableList vaultList, AutoLocker autoLocker, VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) { + public VaultListManager(ObservableList vaultList, AutoLocker autoLocker, VaultComponent.Factory vaultComponentFactory, ResourceBundle resourceBundle, Settings settings) { this.vaultList = vaultList; this.autoLocker = autoLocker; - this.vaultComponentBuilder = vaultComponentBuilder; + this.vaultComponentFactory = vaultComponentFactory; this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName"); addAll(settings.getDirectories()); @@ -93,21 +93,17 @@ public class VaultListManager { } private Vault create(VaultSettings vaultSettings) { - VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings); + var wrapper = new VaultConfigCache(vaultSettings); try { - VaultState.Value vaultState = determineVaultState(vaultSettings.path().get()); - VaultConfigCache wrapper = new VaultConfigCache(vaultSettings); - compBuilder.vaultConfigCache(wrapper); //first set the wrapper in the builder, THEN try to load config + var vaultState = determineVaultState(vaultSettings.path().get()); if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state wrapper.reloadConfig(); } - compBuilder.initialVaultState(vaultState); + return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault(); } catch (IOException e) { LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e); - compBuilder.initialVaultState(ERROR); - compBuilder.initialErrorCause(e); + return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault(); } - return compBuilder.build().vault(); } public static VaultState.Value redetermineVaultState(Vault vault) { diff --git a/src/main/java/org/cryptomator/common/vaults/VaultState.java b/src/main/java/org/cryptomator/common/vaults/VaultState.java index 51365fbd2..ff09c8b82 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultState.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultState.java @@ -83,7 +83,7 @@ public class VaultState extends ObservableValueBase implements if (success) { fireValueChangedEvent(); } else { - LOG.debug("Failed transiting into state {}: Expected state was not{}.", fromState, toState); + LOG.debug("Failed transiting into state {}: Expected state was not {}.", fromState, toState); } return success; } diff --git a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java index d473dcdf8..ffff48a8a 100644 --- a/src/main/java/org/cryptomator/launcher/SupportedLanguages.java +++ b/src/main/java/org/cryptomator/launcher/SupportedLanguages.java @@ -17,7 +17,7 @@ public class SupportedLanguages { // these are BCP 47 language codes, not ISO. Note the "-" instead of the "_": public static final List LANGUAGAE_TAGS = List.of("en", "ar", "bn", "bs", "ca", "cs", "de", "el", "es", "fil", "fr", "gl", "he", // "hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "mk", "nb", "nl", "nn", "no", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", // - "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); + "sr-Latn", "sv", "sw", "ta", "te", "th", "tr", "uk", "zh", "zh-HK", "zh-TW"); @Nullable private final String preferredLanguage; diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index e9b5865d5..432007c99 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -25,6 +25,8 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ResourceBundle; +import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; + @AddVaultWizardScoped public class ChooseExistingVaultController implements FxController { @@ -73,7 +75,7 @@ public class ChooseExistingVaultController implements FxController { public void chooseFileAndNext() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Vault", "*.cryptomator")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("addvaultwizard.existing.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB)); File masterkeyFile = fileChooser.showOpenDialog(window); if (masterkeyFile != null) { vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent()); diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index 1fd463432..cadccc091 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -46,7 +46,7 @@ public class CreateNewVaultLocationController implements FxController { private final Stage window; private final Lazy chooseNameScene; private final Lazy choosePasswordScene; - private final LocationPresets locationPresets; + private final ObservedLocationPresets locationPresets; private final ObjectProperty vaultPath; private final StringProperty vaultName; private final ResourceBundle resourceBundle; @@ -71,7 +71,7 @@ public class CreateNewVaultLocationController implements FxController { public FontAwesome5IconView badLocation; @Inject - CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy choosePasswordScene, LocationPresets locationPresets, ObjectProperty vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) { + CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy choosePasswordScene, ObservedLocationPresets locationPresets, ObjectProperty vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) { this.window = window; this.chooseNameScene = chooseNameScene; this.choosePasswordScene = choosePasswordScene; @@ -197,7 +197,7 @@ public class CreateNewVaultLocationController implements FxController { return validVaultPath.get(); } - public LocationPresets getLocationPresets() { + public ObservedLocationPresets getObservedLocationPresets() { return locationPresets; } diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ObservedLocationPresets.java similarity index 64% rename from src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java rename to src/main/java/org/cryptomator/ui/addvaultwizard/ObservedLocationPresets.java index 313a31dc9..1c988dc04 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ObservedLocationPresets.java @@ -1,23 +1,15 @@ package org.cryptomator.ui.addvaultwizard; +import org.cryptomator.common.LocationPreset; + import javax.inject.Inject; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; @AddVaultWizardScoped -public class LocationPresets { - - private static final String USER_HOME = System.getProperty("user.home"); - private static final String[] ICLOUDDRIVE_LOCATIONS = {"~/Library/Mobile Documents/iCloud~com~setolabs~Cryptomator/Documents", "~/iCloudDrive/iCloud~com~setolabs~Cryptomator"}; - private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"}; - private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive/My Drive", "~/Google Drive"}; - private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"}; - private static final String[] MEGA_LOCATIONS = {"~/MEGA"}; - private static final String[] PCLOUD_LOCATIONS = {"~/pCloudDrive"}; +public class ObservedLocationPresets { private final ReadOnlyObjectProperty iclouddriveLocation; private final ReadOnlyObjectProperty dropboxLocation; @@ -33,13 +25,13 @@ public class LocationPresets { private final BooleanBinding foundPcloud; @Inject - public LocationPresets() { - this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS)); - this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS)); - this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS)); - this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS)); - this.megaLocation = new SimpleObjectProperty<>(existingWritablePath(MEGA_LOCATIONS)); - this.pcloudLocation = new SimpleObjectProperty<>(existingWritablePath(PCLOUD_LOCATIONS)); + public ObservedLocationPresets() { + this.iclouddriveLocation = new SimpleObjectProperty<>(LocationPreset.ICLOUDDRIVE.existingPath()); + this.dropboxLocation = new SimpleObjectProperty<>(LocationPreset.DROPBOX.existingPath()); + this.gdriveLocation = new SimpleObjectProperty<>(LocationPreset.GDRIVE.existingPath()); + this.onedriveLocation = new SimpleObjectProperty<>(LocationPreset.ONEDRIVE.existingPath()); + this.megaLocation = new SimpleObjectProperty<>(LocationPreset.MEGA.existingPath()); + this.pcloudLocation = new SimpleObjectProperty<>(LocationPreset.PCLOUD.existingPath()); this.foundIclouddrive = iclouddriveLocation.isNotNull(); this.foundDropbox = dropboxLocation.isNotNull(); this.foundGdrive = gdriveLocation.isNotNull(); @@ -48,24 +40,6 @@ public class LocationPresets { this.foundPcloud = pcloudLocation.isNotNull(); } - private static Path existingWritablePath(String... candidates) { - for (String candidate : candidates) { - Path path = Paths.get(resolveHomePath(candidate)); - if (Files.isDirectory(path)) { - return path; - } - } - return null; - } - - private static String resolveHomePath(String path) { - if (path.startsWith("~/")) { - return USER_HOME + path.substring(1); - } else { - return path; - } - } - /* Observables */ public ReadOnlyObjectProperty iclouddriveLocationProperty() { diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java index 8c9f7fb20..58b8653da 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerModule.java @@ -43,7 +43,9 @@ public abstract class UpdateCheckerModule { @FxApplicationScoped static Optional provideHttpClient() { try { - return Optional.of(HttpClient.newHttpClient()); + return Optional.of(HttpClient.newBuilder() // + .followRedirects(HttpClient.Redirect.NORMAL) // from version 1.6.11 onwards, Cryptomator can follow redirects, in case this URL ever changes + .build()); } catch (UncheckedIOException e) { LOG.error("HttpClient for update check cannot be created.", e); return Optional.empty(); diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java index 11cf7bd6b..d47f4e5b3 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java @@ -15,6 +15,8 @@ import java.nio.file.Path; import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; +import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; + @ChooseMasterkeyFileScoped public class ChooseMasterkeyFileController implements FxController { @@ -46,7 +48,7 @@ public class ChooseMasterkeyFileController implements FxController { LOG.trace("proceed()"); FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(resourceBundle.getString("unlock.chooseMasterkey.filePickerTitle")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("unlock.chooseMasterkey.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB)); File masterkeyFile = fileChooser.showOpenDialog(window); if (masterkeyFile != null) { LOG.debug("Chose masterkey file: {}", masterkeyFile); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java index dab1f7a54..cf1009ea9 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java @@ -21,13 +21,13 @@ public class VaultDetailLockedController implements FxController { private final ReadOnlyObjectProperty vault; private final FxApplicationWindows appWindows; - private final VaultOptionsComponent.Builder vaultOptionsWindow; + private final VaultOptionsComponent.Factory vaultOptionsWindow; private final KeychainManager keychain; private final Stage mainWindow; private final BooleanExpression passwordSaved; @Inject - VaultDetailLockedController(ObjectProperty vault, FxApplicationWindows appWindows, VaultOptionsComponent.Builder vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) { + VaultDetailLockedController(ObjectProperty vault, FxApplicationWindows appWindows, VaultOptionsComponent.Factory vaultOptionsWindow, KeychainManager keychain, @MainWindow Stage mainWindow) { this.vault = vault; this.appWindows = appWindows; this.vaultOptionsWindow = vaultOptionsWindow; @@ -47,12 +47,12 @@ public class VaultDetailLockedController implements FxController { @FXML public void showVaultOptions() { - vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow(SelectedVaultOptionsTab.ANY); + vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY); } @FXML public void showKeyVaultOptions() { - vaultOptionsWindow.vault(vault.get()).build().showVaultOptionsWindow(SelectedVaultOptionsTab.KEY); + vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.KEY); } /* Getter/Setter */ diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index 8d9f192b5..bc372341a 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -13,6 +13,8 @@ import javafx.stage.Stage; import java.io.File; import java.util.ResourceBundle; +import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; + @MainWindowScoped public class VaultDetailMissingVaultController implements FxController { @@ -45,7 +47,7 @@ public class VaultDetailMissingVaultController implements FxController { // copied from ChooseExistingVaultController class FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(resourceBundle.getString("addvaultwizard.existing.filePickerMimeDesc"), CRYPTOMATOR_FILENAME_GLOB)); File masterkeyFile = fileChooser.showOpenDialog(window); if (masterkeyFile != null) { vault.get().getVaultSettings().path().setValue(masterkeyFile.toPath().toAbsolutePath().getParent()); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java index c9d788b90..23f8e889d 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -31,7 +31,7 @@ public class VaultListContextMenuController implements FxController { private final VaultService vaultService; private final KeychainManager keychain; private final RemoveVaultComponent.Builder removeVault; - private final VaultOptionsComponent.Builder vaultOptionsWindow; + private final VaultOptionsComponent.Factory vaultOptionsWindow; private final OptionalBinding selectedVaultState; private final Binding selectedVaultPassphraseStored; private final Binding selectedVaultRemovable; @@ -39,7 +39,7 @@ public class VaultListContextMenuController implements FxController { private final Binding selectedVaultLockable; @Inject - VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Builder vaultOptionsWindow) { + VaultListContextMenuController(ObjectProperty selectedVault, @MainWindow Stage mainWindow, FxApplicationWindows appWindows, VaultService vaultService, KeychainManager keychain, RemoveVaultComponent.Builder removeVault, VaultOptionsComponent.Factory vaultOptionsWindow) { this.selectedVault = EasyBind.wrapNullable(selectedVault); this.mainWindow = mainWindow; this.appWindows = appWindows; @@ -69,7 +69,7 @@ public class VaultListContextMenuController implements FxController { @FXML public void didClickShowVaultOptions() { selectedVault.ifValuePresent(v -> { - vaultOptionsWindow.vault(v).build().showVaultOptionsWindow(SelectedVaultOptionsTab.ANY); + vaultOptionsWindow.create(v).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY); }); } diff --git a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java index 8f9f6f6da..4af86dc85 100644 --- a/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/VolumePreferencesController.java @@ -48,11 +48,6 @@ public class VolumePreferencesController implements FxController { webDavPortField.setText(String.valueOf(settings.port().get())); changeWebDavPortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(webDavPortField.textProperty())); changeWebDavPortButton.disableProperty().bind(Bindings.createBooleanBinding(this::validateWebDavPort, webDavPortField.textProperty()).not()); - webDavPortField.focusedProperty().addListener((observableValue, wasFocused, isFocused) -> { - if(!isFocused) { - webDavPortField.setText(String.valueOf(settings.port().get())); - } - }); webDavUrlSchemeChoiceBox.getItems().addAll(WebDavUrlScheme.values()); webDavUrlSchemeChoiceBox.valueProperty().bindBidirectional(settings.preferredGvfsScheme()); diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsComponent.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsComponent.java index e56c30f5c..ac749132a 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsComponent.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsComponent.java @@ -36,13 +36,10 @@ public interface VaultOptionsComponent { stage.requestFocus(); } - @Subcomponent.Builder - interface Builder { + @Subcomponent.Factory + interface Factory { - @BindsInstance - Builder vault(@VaultOptionsWindow Vault vault); - - VaultOptionsComponent build(); + VaultOptionsComponent create(@BindsInstance @VaultOptionsWindow Vault vault); } } diff --git a/src/main/resources/css/dark_theme.css b/src/main/resources/css/dark_theme.css index a00e46845..f55509cd8 100644 --- a/src/main/resources/css/dark_theme.css +++ b/src/main/resources/css/dark_theme.css @@ -16,10 +16,6 @@ src: url('opensans-bold.ttf'); } -@font-face { - src: url('quicksand-bold.ttf'); -} - /******************************************************************************* * * * Root Styling & Colors * @@ -46,6 +42,8 @@ GRAY_8: #D3DCE1; GRAY_9: #EDF3F7; + GREEN_3: PRIMARY_D1; + GREEN_5: PRIMARY; RED_5: #E74C3C; ORANGE_5: #E67E22; YELLOW_5: #F1C40F; @@ -179,14 +177,6 @@ -fx-background-insets: 0, 0 0 1px 0; } -.main-window .title .label { - -fx-font-family: 'Quicksand'; - -fx-font-size: 16px; - -fx-font-style: normal; - -fx-font-weight: 700; - -fx-text-fill: TITLE_TEXT_FILL; -} - .main-window .title .button { -fx-pref-height: 30px; -fx-pref-width: 30px; @@ -405,7 +395,6 @@ -fx-background-color: MUTED_BG; } - /* Note: These values below are kinda random such that it looks ok. I'm pretty sure there is room for improvement. Additionally, fx-text-fill does not work*/ .badge-debug { -fx-font-family: 'Open Sans Bold'; @@ -439,11 +428,11 @@ } .password-strength-indicator.strength-3 .segment.active { - -fx-background-color: PRIMARY; + -fx-background-color: GREEN_5; } .password-strength-indicator.strength-4 .segment.active { - -fx-background-color: PRIMARY_D1; + -fx-background-color: GREEN_3; } /******************************************************************************* @@ -834,6 +823,7 @@ -fx-background-color: PROGRESS_BAR_BG; -fx-background-radius: 4px; } + /******************************************************************************* * * * I/O Statistics * @@ -884,6 +874,7 @@ .default-color0.chart-series-area-line { -fx-stroke: PRIMARY; } + .default-color0.chart-series-area-fill { -fx-fill: linear-gradient(to bottom, PRIMARY, transparent); -fx-stroke: transparent; diff --git a/src/main/resources/css/light_theme.css b/src/main/resources/css/light_theme.css index 09c743705..06899c907 100644 --- a/src/main/resources/css/light_theme.css +++ b/src/main/resources/css/light_theme.css @@ -16,10 +16,6 @@ src: url('opensans-bold.ttf'); } -@font-face { - src: url('quicksand-bold.ttf'); -} - /******************************************************************************* * * * Root Styling & Colors * @@ -46,6 +42,8 @@ GRAY_8: #E1E1E1; GRAY_9: #F7F7F7; + GREEN_3: PRIMARY_D1; + GREEN_5: PRIMARY; RED_5: #E74C3C; ORANGE_5: #E67E22; YELLOW_5: #F1C40F; @@ -178,14 +176,6 @@ -fx-background-color: TITLE_BG; } -.main-window .title .label { - -fx-font-family: 'Quicksand'; - -fx-font-size: 16px; - -fx-font-style: normal; - -fx-font-weight: 700; - -fx-text-fill: TITLE_TEXT_FILL; -} - .main-window .title .button { -fx-pref-height: 30px; -fx-pref-width: 30px; @@ -437,11 +427,11 @@ } .password-strength-indicator.strength-3 .segment.active { - -fx-background-color: PRIMARY; + -fx-background-color: GREEN_5; } .password-strength-indicator.strength-4 .segment.active { - -fx-background-color: PRIMARY_D1; + -fx-background-color: GREEN_3; } /******************************************************************************* @@ -832,22 +822,12 @@ -fx-background-color: PROGRESS_BAR_BG; -fx-background-radius: 4px; } + /******************************************************************************* * * * I/O Statistics * * * ******************************************************************************/ -.chart { - -fx-padding: 10px; -} - -.chart-plot-background { - -fx-background-color: MAIN_BG; - -fx-padding: 20px; -} - -/* content */ - .cache-arc-background { -fx-fill: transparent; @@ -893,6 +873,7 @@ .default-color0.chart-series-area-line { -fx-stroke: PRIMARY; } + .default-color0.chart-series-area-fill { -fx-fill: linear-gradient(to bottom, PRIMARY, transparent); -fx-stroke: transparent; diff --git a/src/main/resources/fxml/addvault_new_location.fxml b/src/main/resources/fxml/addvault_new_location.fxml index 3c25ba569..343a2d580 100644 --- a/src/main/resources/fxml/addvault_new_location.fxml +++ b/src/main/resources/fxml/addvault_new_location.fxml @@ -31,12 +31,12 @@