feat: add ci for lint and release , fix several bugs (#5)

* feat: add save_settings_with_update_port command and update related components

* refactor: remove state parameter from API key retrieval and update related functions

* feat: enhance release workflow with tag validation and improve build scripts

* feat: update release workflow to use version input from auto-version workflow

* 📦 Chore(custom): add Clippy configuration for consistent linting across platforms

* chore: add Clippy configuration for consistent linting across platforms

* 🐛 Fix(custom): fix ci

* 🚧 WIP(custom): fix clippy error

* 🐛 Fix(custom): add default openlist and rclone version

* 🚧 WIP(custom): fix clippy errors

* 🚧 WIP(custom): fix clippy errors

* 🐛 Fix(custom): fix ci bugs
This commit is contained in:
Kuingsmile
2025-06-28 13:06:32 +08:00
committed by GitHub
parent bd8e54aa42
commit 5ade6a2c01
36 changed files with 1374 additions and 793 deletions

View File

@@ -1,134 +0,0 @@
name: 'Auto Version and Release'
on:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
check-commits:
runs-on: ubuntu-latest
outputs:
should-release: ${{ steps.check.outputs.should-release }}
version-type: ${{ steps.check.outputs.version-type }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for conventional commits
id: check
run: |
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
echo "No previous tag found, will create initial release"
echo "should-release=true" >> $GITHUB_OUTPUT
echo "version-type=minor" >> $GITHUB_OUTPUT
exit 0
fi
COMMITS=$(git log $LAST_TAG..HEAD --oneline)
if echo "$COMMITS" | grep -qE "^[a-f0-9]+ (feat|fix|BREAKING CHANGE)"; then
echo "Found commits that warrant a release"
echo "should-release=true" >> $GITHUB_OUTPUT
# Determine version bump type
if echo "$COMMITS" | grep -q "BREAKING CHANGE"; then
echo "version-type=major" >> $GITHUB_OUTPUT
elif echo "$COMMITS" | grep -q "feat"; then
echo "version-type=minor" >> $GITHUB_OUTPUT
else
echo "version-type=patch" >> $GITHUB_OUTPUT
fi
else
echo "No commits found that warrant a release"
echo "should-release=false" >> $GITHUB_OUTPUT
fi
auto-release:
needs: check-commits
if: needs.check-commits.outputs.should-release == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Calculate new version
id: version
run: |
# Get current version from package.json
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current version: $CURRENT_VERSION"
# Parse version
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
# Bump version based on commit type
case "${{ needs.check-commits.outputs.version-type }}" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "New version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Update version in files
run: |
# Update package.json
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
# Update Cargo.toml
sed -i 's/^version = "[^"]*"/version = "${{ steps.version.outputs.version }}"/' src-tauri/Cargo.toml
# Update tauri.conf.json
sed -i 's/"version": "[^"]*"/"version": "${{ steps.version.outputs.version }}"/' src-tauri/tauri.conf.json
- name: Commit version bump
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json
git commit -m "chore: bump version to ${{ steps.version.outputs.version }} [skip ci]"
git push
- name: Create and push tag
run: |
git tag ${{ steps.version.outputs.tag }}
git push origin ${{ steps.version.outputs.tag }}
- name: Trigger release workflow
run: |
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/actions/workflows/release.yml/dispatches \
-d '{"ref":"main","inputs":{"version":"${{ steps.version.outputs.tag }}"}}'

197
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,197 @@
name: 'CI - Lint and Test'
on:
workflow_dispatch:
pull_request:
branches: [ main, dev ]
paths:
- 'src/**'
- 'src-tauri/**'
- 'package.json'
- 'package-lock.json'
- 'yarn.lock'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
push:
branches: [ main, dev ]
paths:
- 'src/**'
- 'src-tauri/**'
- 'package.json'
- 'package-lock.json'
- 'yarn.lock'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/ci.yml'
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
frontend-lint:
name: Frontend Lint & Type Check
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Run ESLint
run: yarn lint
- name: Run TypeScript type check
run: yarn web:build --mode=production
rust-check:
name: Rust Check & Lint
runs-on: ${{ matrix.platform.os }}
strategy:
fail-fast: false
matrix:
platform:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: macos-13
target: x86_64-apple-darwin
- os: macos-latest
target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform.target }}
components: rustfmt, clippy
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-on-failure: true
- name: Install system dependencies (Linux)
if: matrix.platform.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install frontend dependencies
run: yarn install
- name: Run prebuild script
run: yarn prebuild:dev
- name: Check Rust formatting
working-directory: src-tauri
run: cargo fmt --all -- --check
- name: Run Clippy
working-directory: src-tauri
run: cargo clippy --target ${{ matrix.platform.target }} --all-targets --all-features -- -D warnings
- name: Run Cargo check
working-directory: src-tauri
run: cargo check --target ${{ matrix.platform.target }} --all-targets --all-features
- name: Run Rust tests
working-directory: src-tauri
run: cargo test --target ${{ matrix.platform.target }} --all-features
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Run npm audit
run: yarn audit --audit-level moderate
build-test:
name: Build Test
runs-on: ${{ matrix.platform.os }}
needs: [frontend-lint, rust-check]
strategy:
fail-fast: false
matrix:
platform:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: macos-13
target: x86_64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform.target }}
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-on-failure: true
- name: Install system dependencies (Linux)
if: matrix.platform.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- name: Install frontend dependencies
run: yarn install
- name: Run prebuild script
run: yarn prebuild:dev
- name: Build frontend
run: yarn web:build
- name: Build Tauri application (test build)
working-directory: src-tauri
run: cargo build --target ${{ matrix.platform.target }} --release

View File

@@ -4,23 +4,248 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g. v1.0.0)'
description: 'Version to release (e.g., v1.0.0). Leave empty for auto-version based on conventional commits.'
required: false
type: string
skip_version_check:
description: 'Skip automatic version detection and use manual version'
required: false
type: boolean
default: false
workflow_call:
inputs:
version:
required: true
type: string
permissions: write-all
env:
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# macOS signing and notarization
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
concurrency:
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check-commits:
name: Check Commits and Determine Version
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && (inputs.version == '' || inputs.skip_version_check == false)
outputs:
should-release: ${{ steps.check.outputs.should-release }}
version-type: ${{ steps.check.outputs.version-type }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for conventional commits
id: check
run: |
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
echo "No previous tag found, will create initial release"
echo "should-release=true" >> $GITHUB_OUTPUT
echo "version-type=minor" >> $GITHUB_OUTPUT
exit 0
fi
COMMITS=$(git log $LAST_TAG..HEAD --oneline)
if echo "$COMMITS" | grep -qE "^[a-f0-9]+ (feat|fix|BREAKING CHANGE)"; then
echo "Found commits that warrant a release"
echo "should-release=true" >> $GITHUB_OUTPUT
# Determine version bump type
if echo "$COMMITS" | grep -q "BREAKING CHANGE"; then
echo "version-type=major" >> $GITHUB_OUTPUT
elif echo "$COMMITS" | grep -q "feat"; then
echo "version-type=minor" >> $GITHUB_OUTPUT
else
echo "version-type=patch" >> $GITHUB_OUTPUT
fi
else
echo "No commits found that warrant a release"
echo "should-release=false" >> $GITHUB_OUTPUT
fi
auto-version:
name: Calculate and Update Version
needs: check-commits
runs-on: ubuntu-latest
if: |
always() &&
(needs.check-commits.result == 'skipped' ||
(needs.check-commits.result == 'success' && needs.check-commits.outputs.should-release == 'true'))
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Calculate new version
id: version
run: |
# If manual version is provided, use it
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.version }}" ]; then
MANUAL_VERSION="${{ inputs.version }}"
# Remove 'v' prefix if present
NEW_VERSION="${MANUAL_VERSION#v}"
echo "Using manual version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
# If triggered by workflow_call, use the provided version
if [ "${{ github.event_name }}" = "workflow_call" ]; then
CALL_VERSION="${{ inputs.version }}"
NEW_VERSION="${CALL_VERSION#v}"
echo "Using workflow_call version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
# Auto-calculate version based on commits
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "Current version: $CURRENT_VERSION"
# Parse version
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
# Bump version based on commit type
case "${{ needs.check-commits.outputs.version-type }}" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "New version: $NEW_VERSION"
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
- name: Update version in files
if: github.event_name == 'workflow_dispatch' && inputs.version == ''
run: |
# Update package.json
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
# Update Cargo.toml
sed -i 's/^version = "[^"]*"/version = "${{ steps.version.outputs.version }}"/' src-tauri/Cargo.toml
# Update tauri.conf.json
sed -i 's/"version": "[^"]*"/"version": "${{ steps.version.outputs.version }}"/' src-tauri/tauri.conf.json
- name: Commit version bump
if: github.event_name == 'workflow_dispatch' && inputs.version == ''
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json
git commit -m "chore: bump version to ${{ steps.version.outputs.version }} [skip ci]"
git push
- name: Create and push tag
if: github.event_name == 'workflow_dispatch' && inputs.version == ''
run: |
git tag ${{ steps.version.outputs.tag }}
git push origin ${{ steps.version.outputs.tag }}
check_tag_version:
name: Check Tag and All Version Files Consistency
needs: auto-version
runs-on: ubuntu-latest
if: github.event_name != 'workflow_dispatch' || inputs.version != ''
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Ensure jq and grep are installed
run: sudo apt-get update && sudo apt-get install -y jq
- name: Validate tag matches versions in package.json, Cargo.toml, tauri.conf.json
run: |
# Get the tag to validate
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.version }}" ]; then
TAG_REF="${{ inputs.version }}"
elif [ "${{ github.event_name }}" = "workflow_call" ]; then
TAG_REF="${{ inputs.version }}"
else
TAG_REF="${GITHUB_REF##*/}" # e.g., v1.2.3
fi
TAG_VERSION="${TAG_REF#v}" # Remove "v" prefix for direct comparison
echo "Tag to validate: $TAG_REF"
# Get version from package.json
PKG_VERSION=$(jq -r .version package.json)
echo "package.json version: $PKG_VERSION"
# Get version from tauri.conf.json
TAURI_VERSION=$(jq -r .package.version src-tauri/tauri.conf.json)
echo "tauri.conf.json version: $TAURI_VERSION"
# Get version from Cargo.toml using grep/sed
CARGO_VERSION=$(grep '^version' src-tauri/Cargo.toml | head -n1 | sed 's/version = "\(.*\)"/\1/')
echo "Cargo.toml version: $CARGO_VERSION"
# Check all match
if [[ "$TAG_VERSION" != "$PKG_VERSION" ]]; then
echo "❌ Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)."
exit 1
fi
if [[ "$TAG_VERSION" != "$TAURI_VERSION" ]]; then
echo "❌ Tag version ($TAG_VERSION) does not match tauri.conf.json version ($TAURI_VERSION)."
exit 1
fi
if [[ "$TAG_VERSION" != "$CARGO_VERSION" ]]; then
echo "❌ Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)."
exit 1
fi
echo "✅ Tag version matches all version files."
changelog:
name: Generate Changelog
needs: [auto-version, check_tag_version]
if: always() && (needs.auto-version.result == 'success' && (needs.check_tag_version.result == 'success' || needs.check_tag_version.result == 'skipped'))
runs-on: ubuntu-latest
outputs:
changelog: ${{ steps.changelog.outputs.changelog }}
@@ -34,8 +259,12 @@ jobs:
- name: Get tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
if [ -n "${{ needs.auto-version.outputs.tag }}" ]; then
echo "tag=${{ needs.auto-version.outputs.tag }}" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ inputs.version }}" ]; then
echo "tag=${{ inputs.version }}" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" = "workflow_call" ]; then
echo "tag=${{ inputs.version }}" >> $GITHUB_OUTPUT
else
echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
@@ -70,56 +299,62 @@ jobs:
path: changelog.md
build:
needs: changelog
needs: [changelog, auto-version]
if: always() && needs.changelog.result == 'success'
strategy:
fail-fast: false
matrix:
include:
- platform: 'macos-latest'
args: '--target aarch64-apple-darwin'
target: 'aarch64-apple-darwin'
- platform: 'macos-latest'
args: '--target x86_64-apple-darwin'
target: 'x86_64-apple-darwin'
- platform: 'ubuntu-20.04'
args: '--target x86_64-unknown-linux-gnu'
target: 'x86_64-unknown-linux-gnu'
- platform: 'windows-latest'
args: '--target x86_64-pc-windows-msvc'
target: 'x86_64-pc-windows-msvc'
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
- name: Install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04'
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
- name: Sync node version and setup cache
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'npm'
node-version: "22"
- name: Install frontend dependencies
run: npm ci
- name: Run install
uses: borales/actions-yarn@v4
with:
cmd: install
- name: install and check
run: |
yarn install
yarn run prebuild:dev --target=${{ matrix.target }}
- name: Import Apple Developer Certificate (macOS only)
if: matrix.platform == 'macos-latest'
if: matrix.os == 'macos-latest'
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
@@ -128,7 +363,10 @@ jobs:
- name: Build the app
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
# macOS signing and notarization environment variables
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -137,19 +375,143 @@ jobs:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Enable signing and notarization for macOS
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
ENABLE_CODE_SIGNING: ${{ matrix.platform == 'macos-latest' && 'true' || 'false' }}
with:
tagName: ${{ needs.changelog.outputs.tag }}
releaseName: 'OpenList Desktop ${{ needs.changelog.outputs.tag }}'
releaseBody: ${{ needs.changelog.outputs.changelog }}
releaseDraft: false
prerelease: false
args: ${{ matrix.args }}
args: --target ${{ matrix.target }}
release-for-linux-arm:
name: Release Build for Linux ARM
needs: [changelog, auto-version]
if: always() && needs.changelog.result == 'success'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
arch: arm64
- os: ubuntu-22.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Run install
uses: borales/actions-yarn@v4
with:
cmd: install
- name: install and check
run: |
yarn install
yarn run prebuild:dev --target=${{ matrix.target }}
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
cat > /tmp/sources.list << EOF
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt update
sudo apt install -y \
libxslt1.1:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
- name: "Install aarch64 tools"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu
- name: "Install armv7 tools"
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
fi
pnpm build --target ${{ matrix.target }}
env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.changelog.outputs.tag }}
name: 'OpenList Desktop ${{ needs.changelog.outputs.tag }}'
body: ${{ needs.changelog.outputs.changelog }}
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
publish:
needs: [changelog, build]
name: Publish Release
needs: [changelog, build, release-for-linux-arm, auto-version]
runs-on: ubuntu-latest
if: always() && needs.build.result == 'success'
if: always() && needs.build.result == 'success' && needs.changelog.result == 'success'
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -169,60 +531,3 @@ jobs:
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update latest.json for auto-updater
run: |
# Get the latest release info
RELEASE_INFO=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/releases/latest")
# Extract version and download URLs
VERSION=$(echo "$RELEASE_INFO" | jq -r '.tag_name')
RELEASE_NOTES=$(echo "$RELEASE_INFO" | jq -r '.body')
RELEASE_DATE=$(echo "$RELEASE_INFO" | jq -r '.published_at')
# Create latest.json for Tauri updater
cat > latest.json << EOF
{
"version": "$VERSION",
"notes": $(echo "$RELEASE_NOTES" | jq -R -s .),
"pub_date": "$RELEASE_DATE",
"platforms": {
"darwin-x86_64": {
"signature": "",
"url": "https://github.com/${{ github.repository }}/releases/download/$VERSION/OpenList-Desktop_${VERSION}_x64_mac.dmg.tar.gz"
},
"darwin-aarch64": {
"signature": "",
"url": "https://github.com/${{ github.repository }}/releases/download/$VERSION/OpenList-Desktop_${VERSION}_aarch64_mac.dmg.tar.gz"
},
"linux-x86_64": {
"signature": "",
"url": "https://github.com/${{ github.repository }}/releases/download/$VERSION/openlist-desktop_${VERSION}_amd64.AppImage.tar.gz"
},
"windows-x86_64": {
"signature": "",
"url": "https://github.com/${{ github.repository }}/releases/download/$VERSION/OpenList-Desktop_${VERSION}_x64_en-US.msi.zip"
}
}
}
EOF
echo "Generated latest.json for auto-updater"
cat latest.json
- name: Commit and push latest.json
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# Check if there are changes to commit
if [[ -n $(git status --porcelain) ]]; then
git add latest.json
git commit -m "Update latest.json for auto-updater [skip ci]"
git push origin HEAD:main
else
echo "No changes to commit"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

25
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,25 @@
# Pre-commit hook configuration
# Install with: yarn add -D husky lint-staged
# Then run: npx husky install
repos:
- repo: local
hooks:
- id: frontend-lint
name: Frontend Lint
entry: yarn lint:fix
language: system
files: \.(ts|vue|js)$
- id: rust-fmt
name: Rust Format
entry: bash -c 'cd src-tauri && cargo fmt --all'
language: system
files: \.rs$
- id: rust-clippy
name: Rust Clippy
entry: bash -c 'cd src-tauri && cargo clippy --all-targets --all-features -- -D warnings'
language: system
files: \.rs$
pass_filenames: false

View File

@@ -147,7 +147,7 @@ npm run tauri build
#### Windows
1. 下载 `.msi` 安装程序
1. 下载 `.exe` 安装程序
2. 以管理员身份运行安装程序
3. 按照安装向导进行操作
4. 从开始菜单或桌面快捷方式启动
@@ -161,10 +161,13 @@ npm run tauri build
#### Linux
1. 下载 `.AppImage` 文件
2. 使其可执行:`chmod +x OpenList-Desktop*.AppImage`
3. 运行 AppImage`./OpenList-Desktop*.AppImage`
4. 可选:使用 AppImageLauncher 安装以进行系统集成
1. 下载 `.deb``.rpm`
2. 使用包管理器安装:
```bash
sudo dpkg -i OpenList-Desktop_x.x.x_amd64.deb
# 或者
sudo rpm -i OpenList-Desktop_x.x.x_amd64.rpm
```
## 🚀 使用说明
@@ -179,7 +182,7 @@ npm run tauri build
#### 启动服务
```
```bash
仪表板 → 服务管理 → 启动 OpenList 服务
仪表板 → 快速操作 → 启动 Rclone 后端
```
@@ -227,7 +230,6 @@ npm run tauri build
- **右键单击托盘图标** 进行快速操作
- **双击** 显示/隐藏主窗口
- **服务状态** 通过图标颜色指示
## ⚙️ 配置
@@ -291,7 +293,7 @@ npm run tauri build
#### 先决条件
- **Node.js**v18+ 和 npm
- **Node.js**v22+ 和 yarn
- **Rust**:最新稳定版本
- **Git**:版本控制
@@ -303,7 +305,7 @@ git clone https://github.com/OpenListTeam/openlist-desktop.git
cd openlist-desktop
# 安装 Node.js 依赖
npm install
yarn install
# 安装 Rust 依赖
cd src-tauri
@@ -311,29 +313,36 @@ cargo fetch
# 准备开发环境
cd ..
npm run prepare-dev
yarn run prebuild:dev
# 启动开发服务器
npm run dev
yarn run dev
```
#### 开发命令
```bash
# 启动带热重载的开发服务器
npm run dev
yarn run dev
# 启动不带文件监视的开发
npm run nowatch
yarn run nowatch
# 运行代码检查
npm run lint
yarn run lint
# 修复代码检查问题
npm run lint:fix
yarn run lint:fix
# 类型检查
npm run build --dry-run
yarn run build --dry-run
```
#### 提交PR
```bash
git add .
yarn cz
```
## 🤝 贡献

View File

@@ -147,7 +147,7 @@ npm run tauri build
#### Windows
1. Download the `.msi` installer
1. Download the `.exe` installer
2. Run the installer as Administrator
3. Follow the installation wizard
4. Launch from Start Menu or Desktop shortcut
@@ -161,10 +161,14 @@ npm run tauri build
#### Linux
1. Download the `.AppImage` file
2. Make it executable: `chmod +x OpenList-Desktop*.AppImage`
3. Run the AppImage: `./OpenList-Desktop*.AppImage`
4. Optional: Install using AppImageLauncher for system integration
1. Download the `.deb` or `.rpm` package
2. Use your package manager to install:
```bash
sudo dpkg -i OpenList-Desktop_x.x.x_amd64.deb
# or
sudo rpm -i OpenList-Desktop_x.x.x_amd64.rpm
```
## 🚀 Usage
@@ -179,7 +183,7 @@ npm run tauri build
#### Starting Services
```
```bash
Dashboard → Service Management → Start OpenList Service
Dashboard → Quick Actions → Start Rclone Backend
```
@@ -227,7 +231,6 @@ Add custom Rclone flags for optimal performance:
- **Right-click tray icon** for quick actions
- **Double-click** to show/hide main window
- **Service status** indicated by icon color
## ⚙️ Configuration
@@ -291,7 +294,7 @@ Add custom Rclone flags for optimal performance:
#### Prerequisites
- **Node.js**: v18+ with npm
- **Node.js**: v22+ with yarn
- **Rust**: Latest stable version
- **Git**: Version control
@@ -303,7 +306,7 @@ git clone https://github.com/OpenListTeam/openlist-desktop.git
cd openlist-desktop
# Install Node.js dependencies
npm install
yarn install
# Install Rust dependencies
cd src-tauri
@@ -311,34 +314,35 @@ cargo fetch
# Prepare development environment
cd ..
npm run prepare-dev
yarn run prebuild:dev
# Start development server
npm run dev
yarn run dev
```
#### Development Commands
```bash
# Start development server with hot reload
npm run dev
yarn run dev
# Start development without file watching
npm run nowatch
yarn run nowatch
# Run linting
npm run lint
yarn run lint
# Fix linting issues
npm run lint:fix
yarn run lint:fix
# Type checking
npm run build --dry-run
yarn run build --dry-run
```
## 🤝 Contributing
We welcome contributions from the community!
## 📄 License
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](./LICENSE) file for details.

View File

@@ -19,15 +19,26 @@
"homepage": "https://github.com/OpenListTeam/openlist-desktop",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"web:build": "tsc --noEmit && vite build",
"web:preview": "vite preview",
"build": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build",
"tauri:dev": "cross-env RUST_BACKTRACE=1 tauri dev",
"tauri": "tauri",
"nowatch": "tauri dev --no-watch",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"cz": "git-cz",
"release": "bump-version",
"prepare-dev": "node scripts/prepare.js"
"prebuild:dev": "node scripts/prepare.js",
"check:rust": "cd src-tauri && cargo check --all-targets --all-features",
"check:rust:fmt": "cd src-tauri && cargo fmt --all -- --check",
"check:rust:clippy": "cd src-tauri && cargo clippy --all-targets --all-features -- -D warnings",
"check:rust:all": "yarn check:rust:fmt && yarn check:rust:clippy && yarn check:rust",
"check:frontend": "yarn lint && tsc --noEmit",
"check:all": "yarn check:frontend && yarn check:rust:all",
"fix:rust": "cd src-tauri && cargo fmt --all && cargo clippy --all-targets --all-features --fix --allow-dirty",
"fix:frontend": "yarn lint:fix",
"fix:all": "yarn fix:frontend && yarn fix:rust"
},
"config": {
"commitizen": {
@@ -68,11 +79,14 @@
"@typescript-eslint/parser": "^8.35.0",
"@vitejs/plugin-vue": "^6.0.0",
"adm-zip": "^0.5.16",
"cross-env": "^7.0.3",
"eslint": "^9.29.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^59.0.1",
"fs-extra": "^11.3.0",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"lint-staged": "^16.1.2",
"node-bump-version": "^2.0.0",
"node-fetch": "^3.3.2",
"tar": "^7.4.3",

View File

@@ -27,13 +27,13 @@ if (!getOpenlistArchMap[platformArch]) {
}
// Rclone version management
let rcloneVersion = 'v1.70.0'
let rcloneVersion = 'v1.70.1'
const rcloneVersionUrl = 'https://github.com/rclone/rclone/releases/latest/download/version.txt'
async function getLatestRcloneVersion() {
try {
const response = await fetch(rcloneVersionUrl, getFetchOptions())
rcloneVersion = (await response.text()).trim().replace('rclone ', '')
rcloneVersion = (await response.text()).trim().replace('rclone ', '') || '1.70.1'
console.log(`Latest rclone version: ${rcloneVersion}`)
} catch (error) {
console.log('Error fetching latest rclone version:', error.message)
@@ -41,7 +41,7 @@ async function getLatestRcloneVersion() {
}
// openlist version management
let openlistVersion = 'v4.0.0'
let openlistVersion = 'v4.0.3'
async function getLatestOpenlistVersion() {
try {
@@ -50,7 +50,7 @@ async function getLatestOpenlistVersion() {
getFetchOptions()
)
const data = await response.json()
openlistVersion = data.tag_name
openlistVersion = data.tag_name || 'v4.0.3'
console.log(`Latest OpenList version: ${openlistVersion}`)
} catch (error) {
console.log('Error fetching latest OpenList version:', error.message)

View File

@@ -0,0 +1,16 @@
[target.x86_64-pc-windows-msvc]
# Windows-specific settings
rustflags = ["-C", "target-feature=+crt-static"]
[target.x86_64-unknown-linux-gnu]
# Linux-specific settings
rustflags = ["-C", "link-arg=-Wl,--compress-debug-sections=zlib"]
[target.x86_64-apple-darwin]
# macOS-specific settings
rustflags = ["-C", "link-arg=-Wl,-dead_strip"]
[target.aarch64-apple-darwin]
# macOS ARM-specific settings
rustflags = ["-C", "link-arg=-Wl,-dead_strip"]

9
src-tauri/clippy.toml Normal file
View File

@@ -0,0 +1,9 @@
# Clippy configuration for consistent linting across platforms
# Deny warnings that should be errors
cognitive-complexity-threshold = 30
too-many-arguments-threshold = 7
type-complexity-threshold = 250
# Allow some clippy lints that are too noisy
avoid-breaking-exported-api = false

View File

@@ -3,47 +3,11 @@ hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
edition = "2024"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
use_small_heuristics = "Default"
remove_nested_parens = true
merge_derives = true
use_try_shorthand = true
use_field_init_shorthand = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
fn_single_line = false
where_single_line = false
force_multiline_blocks = false
brace_style = "SameLineWhere"
format_strings = false
format_macro_matchers = true
normalize_comments = true
normalize_doc_attributes = false
wrap_comments = true
comment_width = 80
trailing_comma = "Vertical"
trailing_semicolon = true
spaces_around_ranges = false
binop_separator = "Front"
chain_width = 60
match_block_trailing_comma = false
match_arm_blocks = true
control_brace_style = "AlwaysSameLine"
single_line_if_else_max_width = 50
blank_lines_upper_bound = 1
blank_lines_lower_bound = 0
empty_item_single_line = true
struct_lit_single_line = true
fn_params_layout = "Tall"

View File

@@ -26,8 +26,8 @@ pub async fn get_binary_version(binary_name: Option<String>) -> Result<String, S
.split_whitespace()
.nth(1)
.ok_or("Failed to parse version")?;
return Ok(version.to_string());
Ok(version.to_string())
} else {
return Err("Failed to get OpenList binary version".to_string());
Err("Failed to get OpenList binary version".to_string())
}
}

View File

@@ -1,6 +1,7 @@
use std::fs;
use tauri::State;
use crate::cmd::http_api::{get_process_list, start_process, stop_process};
use crate::conf::config::MergedSettings;
use crate::object::structs::AppState;
use crate::utils::path::app_config_file_path;
@@ -19,6 +20,72 @@ pub async fn save_settings(
Ok(true)
}
#[tauri::command]
pub async fn save_settings_with_update_port(
settings: MergedSettings,
state: State<'_, AppState>,
) -> Result<bool, String> {
save_settings(settings.clone(), state.clone()).await?;
let app_dir = std::env::current_exe()
.map_err(|e| format!("Failed to get current exe path: {e}"))?
.parent()
.ok_or("Failed to get parent directory")?
.to_path_buf();
let data_config_path = app_dir.join("data").join("config.json");
if let Some(parent) = data_config_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
let mut config = if data_config_path.exists() {
let content =
std::fs::read_to_string(data_config_path.clone()).map_err(|e| e.to_string())?;
serde_json::from_str(&content).map_err(|e| e.to_string())?
} else {
serde_json::json!({
"scheme": {
"http_port": settings.openlist.port,
}
})
};
if let Some(scheme) = config.get_mut("scheme") {
if let Some(scheme_obj) = scheme.as_object_mut() {
scheme_obj.insert(
"http_port".to_string(),
serde_json::Value::Number(serde_json::Number::from(settings.openlist.port)),
);
}
} else {
config["scheme"] = serde_json::json!({
"http_port": settings.openlist.port
});
}
let content = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;
std::fs::write(data_config_path, content).map_err(|e| e.to_string())?;
// Stop the OpenList core process
let process_list = get_process_list(state.clone()).await?;
if let Some(existing_process) = process_list
.iter()
.find(|p| p.config.name == "single_openlist_core_process")
{
match stop_process(existing_process.config.id.clone(), state.clone()).await {
Ok(_) => log::info!("OpenList core process stopped successfully"),
Err(e) => log::warn!("Failed to stop OpenList core process: {e}"),
}
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
match start_process(existing_process.config.id.clone(), state.clone()).await {
Ok(_) => log::info!("OpenList core process started successfully with new port"),
Err(e) => {
log::error!("Failed to start OpenList core process: {e}");
return Err(format!(
"Failed to restart OpenList core with new port: {e}"
));
}
}
}
log::info!("Settings saved and OpenList core restarted with new port successfully");
Ok(true)
}
#[tauri::command]
pub async fn load_settings(state: State<'_, AppState>) -> Result<Option<MergedSettings>, String> {
state.load_settings()?;

View File

@@ -67,10 +67,10 @@ fn get_current_platform() -> String {
let arch = env::consts::ARCH;
match os {
"windows" => format!("{}-pc-windows-msvc", arch),
"macos" => format!("{}-apple-darwin", arch),
"linux" => format!("{}-unknown-linux-gnu", arch),
_ => format!("{}-{}", arch, os),
"windows" => format!("{arch}-pc-windows-msvc"),
"macos" => format!("{arch}-apple-darwin"),
"linux" => format!("{arch}-unknown-linux-gnu"),
_ => format!("{arch}-{os}"),
}
}
@@ -178,8 +178,8 @@ pub async fn check_for_updates() -> Result<UpdateCheck, String> {
.send()
.await
.map_err(|e| {
let error_msg = format!("Network error while checking for updates: {}", e);
log::error!("{}", error_msg);
let error_msg = format!("Network error while checking for updates: {e}");
log::error!("{error_msg}");
error_msg
})?;
@@ -196,13 +196,13 @@ pub async fn check_for_updates() -> Result<UpdateCheck, String> {
status.canonical_reason().unwrap_or("Unknown")
)
};
log::error!("{}", error_msg);
log::error!("{error_msg}");
return Err(error_msg);
}
let release: GitHubRelease = response.json().await.map_err(|e| {
log::error!("Failed to parse GitHub response: {}", e);
format!("Failed to parse update information: {}", e)
log::error!("Failed to parse GitHub response: {e}");
format!("Failed to parse update information: {e}")
})?;
let current_version = env!("CARGO_PKG_VERSION");
@@ -235,14 +235,14 @@ pub async fn download_update(
asset_url: String,
asset_name: String,
) -> Result<String, String> {
log::info!("Starting download of update: {}", asset_name);
log::info!("Starting download of update: {asset_name}");
let client = Client::new();
let temp_dir = std::env::temp_dir();
let file_path = temp_dir.join(&asset_name);
log::info!("Downloading to: {:?}", file_path);
log::info!("Downloading to: {file_path:?}");
let mut response = client
.get(&asset_url)
@@ -251,8 +251,8 @@ pub async fn download_update(
.send()
.await
.map_err(|e| {
let error_msg = format!("Failed to start download: {}", e);
log::error!("{}", error_msg);
let error_msg = format!("Failed to start download: {e}");
log::error!("{error_msg}");
error_msg
})?;
@@ -267,16 +267,16 @@ pub async fn download_update(
status.canonical_reason().unwrap_or("Unknown")
)
};
log::error!("{}", error_msg);
log::error!("{error_msg}");
return Err(error_msg);
}
let total_size = response.content_length().unwrap_or(0);
log::info!("Download size: {} bytes", total_size);
log::info!("Download size: {total_size} bytes");
let mut file = tokio::fs::File::create(&file_path).await.map_err(|e| {
log::error!("Failed to create download file: {}", e);
format!("Failed to create file: {}", e)
log::error!("Failed to create download file: {e}");
format!("Failed to create file: {e}")
})?;
let mut downloaded = 0u64;
@@ -284,12 +284,12 @@ pub async fn download_update(
let mut last_downloaded = 0u64;
while let Some(chunk) = response.chunk().await.map_err(|e| {
log::error!("Download chunk error: {}", e);
format!("Download error: {}", e)
log::error!("Download chunk error: {e}");
format!("Download error: {e}")
})? {
file.write_all(&chunk).await.map_err(|e| {
log::error!("File write error: {}", e);
format!("File write error: {}", e)
log::error!("File write error: {e}");
format!("File write error: {e}")
})?;
downloaded += chunk.len() as u64;
@@ -317,7 +317,7 @@ pub async fn download_update(
};
if let Err(e) = app.emit("download-progress", &progress) {
log::error!("Failed to emit download progress: {}", e);
log::error!("Failed to emit download progress: {e}");
}
last_progress_time = now;
@@ -326,14 +326,14 @@ pub async fn download_update(
}
file.flush().await.map_err(|e| {
log::error!("Failed to flush file: {}", e);
format!("File flush error: {}", e)
log::error!("Failed to flush file: {e}");
format!("File flush error: {e}")
})?;
log::info!("Download completed: {} bytes", downloaded);
log::info!("Download completed: {downloaded} bytes");
if let Err(e) = app.emit("update-download-completed", ()) {
log::error!("Failed to emit download completed event: {}", e);
log::error!("Failed to emit download completed event: {e}");
}
Ok(file_path.to_string_lossy().to_string())
@@ -344,18 +344,18 @@ pub async fn install_update_and_restart(
app: AppHandle,
installer_path: String,
) -> Result<(), String> {
log::info!("Installing update from: {}", installer_path);
log::info!("Installing update from: {installer_path}");
let path = PathBuf::from(&installer_path);
if !path.exists() {
let error_msg = "Installer file not found".to_string();
log::error!("{}", error_msg);
log::error!("{error_msg}");
return Err(error_msg);
}
if let Err(e) = app.emit("update-install-started", ()) {
log::error!("Failed to emit install started event: {}", e);
log::error!("Failed to emit install started event: {e}");
}
let result = match env::consts::OS {
@@ -370,20 +370,20 @@ pub async fn install_update_and_restart(
log::info!("Update installation started successfully");
if let Err(e) = app.emit("update-install-completed", ()) {
log::error!("Failed to emit install completed event: {}", e);
log::error!("Failed to emit install completed event: {e}");
}
if let Err(e) = app.emit("app-restarting", ()) {
log::error!("Failed to emit app restarting event: {}", e);
log::error!("Failed to emit app restarting event: {e}");
}
tokio::time::sleep(Duration::from_millis(1000)).await;
std::process::exit(0);
}
Err(e) => {
log::error!("Update installation failed: {}", e);
log::error!("Update installation failed: {e}");
if let Err(emit_err) = app.emit("update-install-error", &e) {
log::error!("Failed to emit install error event: {}", emit_err);
log::error!("Failed to emit install error event: {emit_err}");
}
Err(e)
}
@@ -396,13 +396,12 @@ async fn install_windows_update(installer_path: &PathBuf) -> Result<(), String>
let mut cmd = Command::new(installer_path);
cmd.arg("/SILENT");
tokio::task::spawn_blocking(move || {
let _ = tokio::task::spawn_blocking(move || {
cmd.spawn()
.map_err(|e| format!("Failed to start Windows installer: {}", e))
.map_err(|e| format!("Failed to start Windows installer: {e}"))
})
.await
.map_err(|e| format!("Task error: {}", e))?
.map_err(|e| e)?;
.map_err(|e| format!("Task error: {e}"))?;
Ok(())
}
@@ -413,13 +412,12 @@ async fn install_macos_update(installer_path: &PathBuf) -> Result<(), String> {
let mut cmd = Command::new("open");
cmd.arg(installer_path);
tokio::task::spawn_blocking(move || {
let _ = tokio::task::spawn_blocking(move || {
cmd.spawn()
.map_err(|e| format!("Failed to start macOS installer: {}", e))
.map_err(|e| format!("Failed to start macOS installer: {e}"))
})
.await
.map_err(|e| format!("Task error: {}", e))?
.map_err(|e| e)?;
.map_err(|e| format!("Task error: {e}"))?;
Ok(())
}
@@ -450,13 +448,12 @@ async fn install_linux_update(installer_path: &PathBuf) -> Result<(), String> {
}
};
tokio::task::spawn_blocking(move || {
let _ = tokio::task::spawn_blocking(move || {
cmd.spawn()
.map_err(|e| format!("Failed to start Linux installer: {}", e))
.map_err(|e| format!("Failed to start Linux installer: {e}"))
})
.await
.map_err(|e| format!("Task error: {}", e))?
.map_err(|e| e)?;
.map_err(|e| format!("Task error: {e}"))?;
Ok(())
}
@@ -471,7 +468,7 @@ pub async fn set_auto_check_enabled(
enabled: bool,
state: State<'_, AppState>,
) -> Result<(), String> {
log::info!("Setting auto-check updates preference to: {}", enabled);
log::info!("Setting auto-check updates preference to: {enabled}");
let mut settings = state.get_settings().unwrap_or_else(|| {
use crate::conf::config::MergedSettings;
@@ -482,7 +479,7 @@ pub async fn set_auto_check_enabled(
state.update_settings(settings.clone());
save_settings(settings, state)
.await
.map_err(|e| format!("Failed to save settings: {}", e))?;
.map_err(|e| format!("Failed to save settings: {e}"))?;
Ok(())
}
@@ -509,15 +506,15 @@ pub async fn perform_background_update_check(app: AppHandle) -> Result<(), Strin
);
if let Err(e) = app.emit("background-update-available", &update_check) {
log::error!("Failed to emit background-update-available event: {}", e);
log::error!("Failed to emit background-update-available event: {e}");
}
} else {
log::debug!("Background check: App is up to date");
log::error!("Background check: App is up to date");
}
Ok(())
}
Err(e) => {
log::debug!("Background update check failed: {}", e);
log::error!("Background update check failed: {e}");
Ok(())
}
}
@@ -528,7 +525,7 @@ pub async fn restart_app(app: AppHandle) {
log::info!("Restarting application...");
if let Err(e) = app.emit("app-restarting", ()) {
log::error!("Failed to emit app-restarting event: {}", e);
log::error!("Failed to emit app-restarting event: {e}");
}
tokio::time::sleep(Duration::from_millis(500)).await;

View File

@@ -7,27 +7,28 @@ use std::str::FromStr;
use tauri::State;
#[tauri::command]
pub async fn get_process_list(state: State<'_, AppState>) -> Result<Vec<ProcessStatus>, String> {
let api_key = get_api_key(state);
pub async fn get_process_list(_state: State<'_, AppState>) -> Result<Vec<ProcessStatus>, String> {
let api_key = get_api_key();
let port = get_server_port();
println!("API Key: {api_key}");
println!("Server Port: {port}");
let client = reqwest::Client::new();
let response = client
.get(format!("http://127.0.0.1:{}/api/v1/processes", port))
.header("Authorization", format!("Bearer {}", api_key))
.get(format!("http://127.0.0.1:{port}/api/v1/processes"))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
let response_text = response
.text()
.await
.map_err(|e| format!("Failed to read response text: {}", e))?;
.map_err(|e| format!("Failed to read response text: {e}"))?;
let process_list = match serde_json::from_str::<ListProcessResponse>(&response_text) {
Ok(process_list) => process_list,
Err(e) => {
return Err(format!(
"Failed to parse response: {}, response: {}",
e, response_text
"Failed to parse response: {e}, response: {response_text}"
));
}
};
@@ -38,19 +39,18 @@ pub async fn get_process_list(state: State<'_, AppState>) -> Result<Vec<ProcessS
}
#[tauri::command]
pub async fn start_process(id: String, state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key(state);
pub async fn start_process(id: String, _state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key();
let port = get_server_port();
let client = reqwest::Client::new();
let response = client
.post(format!(
"http://127.0.0.1:{}/api/v1/processes/{}/start",
port, id
"http://127.0.0.1:{port}/api/v1/processes/{id}/start"
))
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
Ok(true)
} else {
@@ -59,19 +59,18 @@ pub async fn start_process(id: String, state: State<'_, AppState>) -> Result<boo
}
#[tauri::command]
pub async fn stop_process(id: String, state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key(state);
pub async fn stop_process(id: String, _state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key();
let port = get_server_port();
let client = reqwest::Client::new();
let response = client
.post(format!(
"http://127.0.0.1:{}/api/v1/processes/{}/stop",
port, id
"http://127.0.0.1:{port}/api/v1/processes/{id}/stop"
))
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
Ok(true)
} else {
@@ -80,32 +79,30 @@ pub async fn stop_process(id: String, state: State<'_, AppState>) -> Result<bool
}
#[tauri::command]
pub async fn restart_process(id: String, state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key(state);
pub async fn restart_process(id: String, _state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key();
let port = get_server_port();
let client = reqwest::Client::new();
let stop_response = client
.post(format!(
"http://127.0.0.1:{}/api/v1/processes/{}/stop",
port, id
"http://127.0.0.1:{port}/api/v1/processes/{id}/stop"
))
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if stop_response.status().is_success() {
let start_response = client
.post(
url::Url::from_str(&format!(
"http://127.0.0.1:{}/api/v1/processes/{}/start",
port, id
"http://127.0.0.1:{port}/api/v1/processes/{id}/start"
))
.unwrap(),
)
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if start_response.status().is_success() {
Ok(true)
} else {
@@ -126,18 +123,18 @@ pub async fn restart_process(id: String, state: State<'_, AppState>) -> Result<b
pub async fn update_process(
id: String,
update_config: HashMap<String, String>,
state: State<'_, AppState>,
_state: State<'_, AppState>,
) -> Result<bool, String> {
let api_key = get_api_key(state);
let api_key = get_api_key();
let port = get_server_port();
let client = reqwest::Client::new();
let response = client
.put(format!("http://127.0.0.1:{}/api/v1/processes/{}", port, id))
.header("Authorization", format!("Bearer {}", api_key))
.put(format!("http://127.0.0.1:{port}/api/v1/processes/{id}"))
.header("Authorization", format!("Bearer {api_key}"))
.json(&update_config)
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
Ok(true)
} else {

View File

@@ -7,10 +7,10 @@ pub async fn get_admin_password() -> Result<String, String> {
let logs_dir = app_dir.join("logs/process_openlist_core.log");
let logs_content =
std::fs::read_to_string(logs_dir).map_err(|e| format!("Failed to read log file: {}", e))?;
std::fs::read_to_string(logs_dir).map_err(|e| format!("Failed to read log file: {e}"))?;
let re = Regex::new(r"Successfully created the admin user and the initial password is: (\w+)")
.map_err(|e| format!("Failed to create regex: {}", e))?;
.map_err(|e| format!("Failed to create regex: {e}"))?;
let mut last_password = None;
for line in logs_content.lines() {

View File

@@ -10,15 +10,15 @@ use url::Url;
#[tauri::command]
pub async fn create_openlist_core_process(
auto_start: bool,
state: State<'_, AppState>,
_state: State<'_, AppState>,
) -> Result<ProcessConfig, String> {
let binary_path = get_openlist_binary_path()
.map_err(|e| format!("Failed to get OpenList binary path: {}", e))?;
.map_err(|e| format!("Failed to get OpenList binary path: {e}"))?;
let log_file_path =
get_app_logs_dir().map_err(|e| format!("Failed to get app logs directory: {}", e))?;
get_app_logs_dir().map_err(|e| format!("Failed to get app logs directory: {e}"))?;
let log_file_path = log_file_path.join("process_openlist_core.log");
let api_key = get_api_key(state);
let api_key = get_api_key();
let port = get_server_port();
let config = ProcessConfig {
@@ -39,23 +39,22 @@ pub async fn create_openlist_core_process(
};
let client = reqwest::Client::new();
let response = client
.post(format!("http://127.0.0.1:{}/api/v1/processes", port))
.post(format!("http://127.0.0.1:{port}/api/v1/processes"))
.json(&config)
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
let response_text = response
.text()
.await
.map_err(|e| format!("Failed to read response text: {}", e))?;
.map_err(|e| format!("Failed to read response text: {e}"))?;
let process_config = match serde_json::from_str::<CreateProcessResponse>(&response_text) {
Ok(process_config) => process_config,
Err(e) => {
return Err(format!(
"Failed to parse response: {}, response: {}",
e, response_text
"Failed to parse response: {e}, response: {response_text}"
));
}
};
@@ -85,10 +84,10 @@ pub async fn get_openlist_core_status(state: State<'_, AppState>) -> Result<Serv
let health_check_url = format!("{}://localhost:{}", protocol, openlist_config.port);
let url =
Url::parse(&health_check_url).map_err(|e| format!("Invalid health check URL: {}", e))?;
Url::parse(&health_check_url).map_err(|e| format!("Invalid health check URL: {e}"))?;
let port = url.port_or_known_default();
let health_url = format!("{}/ping", health_check_url);
let health_url = format!("{health_check_url}/ping");
let local_pid = None;
match reqwest::get(&health_url).await {

View File

@@ -1,5 +1,5 @@
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use tauri::{AppHandle, State};
use crate::cmd::http_api::{get_process_list, start_process, stop_process};
@@ -11,14 +11,14 @@ fn normalize_path(path: &str) -> String {
{
let normalized = path.replace('/', "\\");
if normalized.len() == 2 && normalized.chars().nth(1) == Some(':') {
format!("{}\\", normalized)
format!("{normalized}\\")
} else if normalized.len() > 2
&& normalized.chars().nth(1) == Some(':')
&& normalized.chars().nth(2) != Some('\\')
{
let drive = &normalized[..2];
let rest = &normalized[2..];
format!("{}\\{}", drive, rest)
format!("{drive}\\{rest}")
} else {
normalized
}
@@ -159,11 +159,11 @@ pub async fn update_tool_version(
version: String,
state: State<'_, AppState>,
) -> Result<String, String> {
log::info!("Updating {} to version {}", tool, version);
log::info!("Updating {tool} to version {version}");
let process_list = get_process_list(state.clone())
.await
.map_err(|e| format!("Failed to get process list: {}", e))?;
.map_err(|e| format!("Failed to get process list: {e}"))?;
let process_name = match tool.as_str() {
"openlist" => "single_openlist_core_process",
@@ -178,16 +178,16 @@ pub async fn update_tool_version(
if was_running {
if let Some(pid) = &process_id {
log::info!("Stopping {} process with ID: {}", tool, pid);
log::info!("Stopping {tool} process with ID: {pid}");
match tool.as_str() {
"openlist" | "rclone" => {
stop_process(pid.clone(), state.clone())
.await
.map_err(|e| format!("Failed to stop process: {}", e))?;
.map_err(|e| format!("Failed to stop process: {e}"))?;
}
_ => return Err("Unsupported tool".to_string()),
}
log::info!("Successfully stopped {} process", tool);
log::info!("Successfully stopped {tool} process");
}
}
@@ -195,33 +195,32 @@ pub async fn update_tool_version(
match result {
Ok(_) => {
log::info!("Successfully downloaded and replaced {} binary", tool);
log::info!("Successfully downloaded and replaced {tool} binary");
if was_running {
if let Some(pid) = &process_id {
log::info!("Starting {} process with ID: {}", tool, pid);
log::info!("Starting {tool} process with ID: {pid}");
match tool.as_str() {
"openlist" | "rclone" => {
start_process(pid.clone(), state.clone())
.await
.map_err(|e| format!("Failed to start {} process: {}", tool, e))?;
.map_err(|e| format!("Failed to start {tool} process: {e}"))?;
}
_ => return Err("Unsupported tool".to_string()),
}
log::info!("Successfully restarted {} process", tool);
log::info!("Successfully restarted {tool} process");
}
}
Ok(format!("Successfully updated {} to {}", tool, version))
Ok(format!("Successfully updated {tool} to {version}"))
}
Err(e) => {
log::error!("Failed to update {} binary: {}", tool, e);
log::error!("Failed to update {tool} binary: {e}");
if was_running {
if let Some(pid) = &process_id {
log::info!(
"Attempting to restart {} with previous binary after update failure",
tool
"Attempting to restart {tool} with previous binary after update failure"
);
match tool.as_str() {
"openlist" | "rclone" => {
@@ -232,7 +231,7 @@ pub async fn update_tool_version(
}
}
Err(format!("Failed to update {} to {}: {}", tool, version, e))
Err(format!("Failed to update {tool} to {version}: {e}"))
}
}
}
@@ -247,29 +246,29 @@ async fn download_and_replace_binary(tool: &str, version: &str) -> Result<(), St
"windows" => "win32",
"macos" => "darwin",
"linux" => "linux",
_ => return Err(format!("Unsupported platform: {}", platform)),
_ => return Err(format!("Unsupported platform: {platform}")),
},
match arch {
"x86_64" => "x64",
"x86" => "ia32",
"aarch64" => "arm64",
"arm" => "arm",
_ => return Err(format!("Unsupported architecture: {}", arch)),
_ => return Err(format!("Unsupported architecture: {arch}")),
}
);
log::info!("Detected platform: {}", platform_arch);
log::info!("Detected platform: {platform_arch}");
let (binary_path, download_info) = match tool {
"openlist" => {
let path = get_openlist_binary_path()
.map_err(|e| format!("Failed to get OpenList binary path: {}", e))?;
.map_err(|e| format!("Failed to get OpenList binary path: {e}"))?;
let info = get_openlist_download_info(&platform_arch, version)?;
(path, info)
}
"rclone" => {
let path = get_rclone_binary_path()
.map_err(|e| format!("Failed to get Rclone binary path: {}", e))?;
.map_err(|e| format!("Failed to get Rclone binary path: {e}"))?;
let info = get_rclone_download_info(&platform_arch, version)?;
(path, info)
}
@@ -278,8 +277,8 @@ async fn download_and_replace_binary(tool: &str, version: &str) -> Result<(), St
log::info!("Downloading {} from: {}", tool, download_info.download_url);
let temp_dir = std::env::temp_dir().join(format!("{}-update-{}", tool, version));
fs::create_dir_all(&temp_dir).map_err(|e| format!("Failed to create temp directory: {}", e))?;
let temp_dir = std::env::temp_dir().join(format!("{tool}-update-{version}"));
fs::create_dir_all(&temp_dir).map_err(|e| format!("Failed to create temp directory: {e}"))?;
let archive_path = temp_dir.join(&download_info.archive_name);
download_file(&download_info.download_url, &archive_path).await?;
@@ -302,7 +301,7 @@ async fn download_and_replace_binary(tool: &str, version: &str) -> Result<(), St
if binary_path.exists() {
fs::copy(&binary_path, &backup_path)
.map_err(|e| format!("Failed to backup current binary: {}", e))?;
.map_err(|e| format!("Failed to backup current binary: {e}"))?;
}
fs::copy(&extracted_binary_path, &binary_path).map_err(|e| {
@@ -310,7 +309,7 @@ async fn download_and_replace_binary(tool: &str, version: &str) -> Result<(), St
let _ = fs::copy(&backup_path, &binary_path);
let _ = fs::remove_file(&backup_path);
}
format!("Failed to replace binary: {}", e)
format!("Failed to replace binary: {e}")
})?;
if backup_path.exists() {
@@ -321,17 +320,17 @@ async fn download_and_replace_binary(tool: &str, version: &str) -> Result<(), St
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&binary_path)
.map_err(|e| format!("Failed to get binary metadata: {}", e))?
.map_err(|e| format!("Failed to get binary metadata: {e}"))?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&binary_path, perms)
.map_err(|e| format!("Failed to set executable permissions: {}", e))?;
.map_err(|e| format!("Failed to set executable permissions: {e}"))?;
}
let _ = fs::remove_file(&extracted_binary_path);
let _ = fs::remove_dir_all(&temp_dir);
log::info!("Successfully replaced {} binary", tool);
log::info!("Successfully replaced {tool} binary");
Ok(())
}
@@ -349,11 +348,10 @@ fn get_openlist_download_info(platform_arch: &str, version: &str) -> Result<Down
let archive_ext = if is_unix { "tar.gz" } else { "zip" };
let exe_ext = if is_windows { ".exe" } else { "" };
let archive_name = format!("openlist-{}.{}", arch_map, archive_ext);
let executable_name = format!("openlist{}", exe_ext);
let archive_name = format!("openlist-{arch_map}.{archive_ext}");
let executable_name = format!("openlist{exe_ext}");
let download_url = format!(
"https://github.com/OpenListTeam/OpenList/releases/download/{}/{}",
version, archive_name
"https://github.com/OpenListTeam/OpenList/releases/download/{version}/{archive_name}"
);
Ok(DownloadInfo {
@@ -368,12 +366,10 @@ fn get_rclone_download_info(platform_arch: &str, version: &str) -> Result<Downlo
let is_windows = platform_arch.starts_with("win32");
let exe_ext = if is_windows { ".exe" } else { "" };
let archive_name = format!("rclone-{}-{}.zip", version, arch_map);
let executable_name = format!("rclone{}", exe_ext);
let download_url = format!(
"https://github.com/rclone/rclone/releases/download/{}/{}",
version, archive_name
);
let archive_name = format!("rclone-{version}-{arch_map}.zip");
let executable_name = format!("rclone{exe_ext}");
let download_url =
format!("https://github.com/rclone/rclone/releases/download/{version}/{archive_name}");
Ok(DownloadInfo {
download_url,
@@ -394,8 +390,7 @@ fn get_openlist_arch_mapping(platform_arch: &str) -> Result<&'static str, String
"linux-arm64" => Ok("linux-arm64"),
"linux-arm" => Ok("linux-arm-7"),
_ => Err(format!(
"Unsupported platform architecture: {}",
platform_arch
"Unsupported platform architecture: {platform_arch}"
)),
}
}
@@ -412,25 +407,24 @@ fn get_rclone_arch_mapping(platform_arch: &str) -> Result<&'static str, String>
"linux-arm64" => Ok("linux-arm64"),
"linux-arm" => Ok("linux-arm-v7"),
_ => Err(format!(
"Unsupported platform architecture: {}",
platform_arch
"Unsupported platform architecture: {platform_arch}"
)),
}
}
async fn download_file(url: &str, path: &PathBuf) -> Result<(), String> {
log::info!("Downloading file from: {}", url);
log::info!("Downloading file from: {url}");
let client = reqwest::Client::builder()
.user_agent("OpenList Desktop/1.0")
.build()
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
.map_err(|e| format!("Failed to create HTTP client: {e}"))?;
let response = client
.get(url)
.send()
.await
.map_err(|e| format!("Failed to download file: {}", e))?;
.map_err(|e| format!("Failed to download file: {e}"))?;
if !response.status().is_success() {
return Err(format!(
@@ -442,21 +436,21 @@ async fn download_file(url: &str, path: &PathBuf) -> Result<(), String> {
let bytes = response
.bytes()
.await
.map_err(|e| format!("Failed to read response bytes: {}", e))?;
.map_err(|e| format!("Failed to read response bytes: {e}"))?;
fs::write(path, &bytes).map_err(|e| format!("Failed to write file: {}", e))?;
fs::write(path, &bytes).map_err(|e| format!("Failed to write file: {e}"))?;
log::info!("Downloaded file to: {:?}", path);
log::info!("Downloaded file to: {path:?}");
Ok(())
}
async fn extract_binary(
archive_path: &PathBuf,
extract_dir: &PathBuf,
extract_dir: &Path,
executable_name: &str,
tool: &str,
) -> Result<PathBuf, String> {
log::info!("Extracting archive: {:?}", archive_path);
log::info!("Extracting archive: {archive_path:?}");
let archive_name = archive_path
.file_name()
@@ -468,44 +462,43 @@ async fn extract_binary(
} else if archive_name.ends_with(".tar.gz") {
extract_tar_gz(archive_path, extract_dir, executable_name, tool)
} else {
Err(format!("Unsupported archive format: {}", archive_name))
Err(format!("Unsupported archive format: {archive_name}"))
}
}
fn extract_zip(
archive_path: &PathBuf,
extract_dir: &PathBuf,
extract_dir: &Path,
executable_name: &str,
tool: &str,
) -> Result<PathBuf, String> {
let file =
fs::File::open(archive_path).map_err(|e| format!("Failed to open zip file: {}", e))?;
let file = fs::File::open(archive_path).map_err(|e| format!("Failed to open zip file: {e}"))?;
let mut archive =
zip::ZipArchive::new(file).map_err(|e| format!("Failed to read zip archive: {}", e))?;
zip::ZipArchive::new(file).map_err(|e| format!("Failed to read zip archive: {e}"))?;
let mut executable_path = None;
for i in 0..archive.len() {
let mut file = archive
.by_index(i)
.map_err(|e| format!("Failed to read zip entry: {}", e))?;
.map_err(|e| format!("Failed to read zip entry: {e}"))?;
let file_name = file.name();
let is_target_executable = if tool == "rclone" {
file_name.ends_with(executable_name) && file_name.contains("rclone")
} else {
file_name == executable_name || file_name.ends_with(&format!("/{}", executable_name))
file_name == executable_name || file_name.ends_with(&format!("/{executable_name}"))
};
if is_target_executable {
let output_path = extract_dir.join(executable_name);
let mut output_file = fs::File::create(&output_path)
.map_err(|e| format!("Failed to create output file: {}", e))?;
.map_err(|e| format!("Failed to create output file: {e}"))?;
std::io::copy(&mut file, &mut output_file)
.map_err(|e| format!("Failed to extract file: {}", e))?;
.map_err(|e| format!("Failed to extract file: {e}"))?;
executable_path = Some(output_path);
break;
@@ -513,12 +506,12 @@ fn extract_zip(
}
executable_path
.ok_or_else(|| format!("Executable '{}' not found in zip archive", executable_name))
.ok_or_else(|| format!("Executable '{executable_name}' not found in zip archive"))
}
fn extract_tar_gz(
archive_path: &PathBuf,
extract_dir: &PathBuf,
extract_dir: &Path,
executable_name: &str,
_tool: &str,
) -> Result<PathBuf, String> {
@@ -526,7 +519,7 @@ fn extract_tar_gz(
use tar::Archive;
let file =
fs::File::open(archive_path).map_err(|e| format!("Failed to open tar.gz file: {}", e))?;
fs::File::open(archive_path).map_err(|e| format!("Failed to open tar.gz file: {e}"))?;
let gz_decoder = GzDecoder::new(file);
let mut archive = Archive::new(gz_decoder);
@@ -535,21 +528,21 @@ fn extract_tar_gz(
for entry in archive
.entries()
.map_err(|e| format!("Failed to read tar entries: {}", e))?
.map_err(|e| format!("Failed to read tar entries: {e}"))?
{
let mut entry = entry.map_err(|e| format!("Failed to read tar entry: {}", e))?;
let mut entry = entry.map_err(|e| format!("Failed to read tar entry: {e}"))?;
let path = entry
.path()
.map_err(|e| format!("Failed to get entry path: {}", e))?;
.map_err(|e| format!("Failed to get entry path: {e}"))?;
if let Some(file_name) = path.file_name() {
if file_name == executable_name {
let output_path = extract_dir.join(executable_name);
let mut output_file = fs::File::create(&output_path)
.map_err(|e| format!("Failed to create output file: {}", e))?;
.map_err(|e| format!("Failed to create output file: {e}"))?;
std::io::copy(&mut entry, &mut output_file)
.map_err(|e| format!("Failed to extract file: {}", e))?;
.map_err(|e| format!("Failed to extract file: {e}"))?;
executable_path = Some(output_path);
break;
@@ -557,10 +550,6 @@ fn extract_tar_gz(
}
}
executable_path.ok_or_else(|| {
format!(
"Executable '{}' not found in tar.gz archive",
executable_name
)
})
executable_path
.ok_or_else(|| format!("Executable '{executable_name}' not found in tar.gz archive"))
}

View File

@@ -34,18 +34,18 @@ pub async fn create_and_start_rclone_backend(
#[tauri::command]
pub async fn create_rclone_backend_process(
state: State<'_, AppState>,
_state: State<'_, AppState>,
) -> Result<ProcessConfig, String> {
let binary_path =
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {}", e))?;
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {e}"))?;
let log_file_path =
get_app_logs_dir().map_err(|e| format!("Failed to get app logs directory: {}", e))?;
get_app_logs_dir().map_err(|e| format!("Failed to get app logs directory: {e}"))?;
let rclone_conf_path = binary_path
.parent()
.map(|p| p.join("rclone.conf"))
.ok_or_else(|| "Failed to determine rclone.conf path".to_string())?;
let log_file_path = log_file_path.join("process_rclone.log");
let api_key = get_api_key(state);
let api_key = get_api_key();
let port = get_server_port();
let config = ProcessConfig {
id: "rclone_backend".into(),
@@ -76,23 +76,22 @@ pub async fn create_rclone_backend_process(
};
let client = reqwest::Client::new();
let response = client
.post(format!("http://127.0.0.1:{}/api/v1/processes", port))
.post(format!("http://127.0.0.1:{port}/api/v1/processes"))
.json(&config)
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
let response_text = response
.text()
.await
.map_err(|e| format!("Failed to read response text: {}", e))?;
.map_err(|e| format!("Failed to read response text: {e}"))?;
let process_config = match serde_json::from_str::<CreateProcessResponse>(&response_text) {
Ok(process_config) => process_config,
Err(e) => {
return Err(format!(
"Failed to parse response: {}, response: {}",
e, response_text
"Failed to parse response: {e}, response: {response_text}"
));
}
};
@@ -114,7 +113,7 @@ async fn is_rclone_running() -> bool {
let client = Client::new();
let response = client
.get(&format!("{}/", RCLONE_API_BASE))
.get(format!("{RCLONE_API_BASE}/"))
.timeout(Duration::from_secs(1))
.send()
.await;

View File

@@ -20,39 +20,38 @@ pub async fn rclone_list_config(
) -> Result<serde_json::Value, String> {
let client = Client::new();
let response = client
.post(format!("{}/config/dump", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/config/dump"))
.header("Authorization", RCLONE_AUTH)
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
let response_text = response
.text()
.await
.map_err(|e| format!("Failed to read response text: {}", e))?;
.map_err(|e| format!("Failed to read response text: {e}"))?;
let json: serde_json::Value = serde_json::from_str(&response_text)
.map_err(|e| format!("Failed to parse JSON: {}", e))?;
.map_err(|e| format!("Failed to parse JSON: {e}"))?;
let remotes = if remote_type.is_empty() {
json.clone()
} else {
if let Some(obj) = json.as_object() {
let mut filtered_map = serde_json::Map::new();
for (remote_name, remote_config) in obj {
if let Some(config_obj) = remote_config.as_object() {
if let Some(remote_type_value) = config_obj.get("type") {
if let Some(type_str) = remote_type_value.as_str() {
if type_str == remote_type {
filtered_map.insert(remote_name.clone(), remote_config.clone());
}
} else if let Some(obj) = json.as_object() {
let mut filtered_map = serde_json::Map::new();
for (remote_name, remote_config) in obj {
if let Some(config_obj) = remote_config.as_object() {
if let Some(remote_type_value) = config_obj.get("type") {
if let Some(type_str) = remote_type_value.as_str() {
if type_str == remote_type {
filtered_map.insert(remote_name.clone(), remote_config.clone());
}
}
}
}
serde_json::Value::Object(filtered_map)
} else {
serde_json::Value::Object(serde_json::Map::new())
}
serde_json::Value::Object(filtered_map)
} else {
serde_json::Value::Object(serde_json::Map::new())
};
Ok(remotes)
} else {
Err(format!(
@@ -67,24 +66,24 @@ pub async fn rclone_list_remotes() -> Result<Vec<String>, String> {
let client = Client::new();
let response = client
.post(&format!("{}/config/listremotes", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/config/listremotes"))
.header("Authorization", RCLONE_AUTH)
.send()
.await
.map_err(|e| format!("Failed to list remotes: {}", e))?;
.map_err(|e| format!("Failed to list remotes: {e}"))?;
if response.status().is_success() {
let remote_list: RcloneRemoteListResponse = response
.json()
.await
.map_err(|e| format!("Failed to parse remote list response: {}", e))?;
.map_err(|e| format!("Failed to parse remote list response: {e}"))?;
Ok(remote_list.remotes)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to list remotes: {}", error_text))
Err(format!("Failed to list remotes: {error_text}"))
}
}
@@ -93,24 +92,24 @@ pub async fn rclone_list_mounts() -> Result<RcloneMountListResponse, String> {
let client = Client::new();
let response = client
.post(&format!("{}/mount/listmounts", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/mount/listmounts"))
.header("Authorization", RCLONE_AUTH)
.send()
.await
.map_err(|e| format!("Failed to list mounts: {}", e))?;
.map_err(|e| format!("Failed to list mounts: {e}"))?;
if response.status().is_success() {
let mount_list: RcloneMountListResponse = response
.json()
.await
.map_err(|e| format!("Failed to parse mount list response: {}", e))?;
.map_err(|e| format!("Failed to parse mount list response: {e}"))?;
Ok(mount_list)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to list mounts: {}", error_text))
Err(format!("Failed to list mounts: {error_text}"))
}
}
@@ -135,13 +134,13 @@ pub async fn rclone_create_remote(
};
let response = client
.post(&format!("{}/config/create", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/config/create"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&create_request)
.send()
.await
.map_err(|e| format!("Failed to create remote config: {}", e))?;
.map_err(|e| format!("Failed to create remote config: {e}"))?;
if response.status().is_success() {
Ok(true)
@@ -150,7 +149,7 @@ pub async fn rclone_create_remote(
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to create remote config: {}", error_text))
Err(format!("Failed to create remote config: {error_text}"))
}
}
@@ -164,13 +163,13 @@ pub async fn rclone_update_remote(
let client = Client::new();
let response = client
.post(&format!("{}/config/update", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/config/update"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&json!({ "name": name, "type": r#type, "parameters": config }))
.send()
.await
.map_err(|e| format!("Failed to update remote config: {}", e))?;
.map_err(|e| format!("Failed to update remote config: {e}"))?;
if response.status().is_success() {
Ok(true)
@@ -179,7 +178,7 @@ pub async fn rclone_update_remote(
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to update remote config: {}", error_text))
Err(format!("Failed to update remote config: {error_text}"))
}
}
@@ -191,13 +190,13 @@ pub async fn rclone_delete_remote(
let client = Client::new();
let response = client
.post(&format!("{}/config/delete", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/config/delete"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&json!({ "name": name }))
.send()
.await
.map_err(|e| format!("Failed to delete remote config: {}", e))?;
.map_err(|e| format!("Failed to delete remote config: {e}"))?;
if response.status().is_success() {
Ok(true)
@@ -206,7 +205,7 @@ pub async fn rclone_delete_remote(
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to delete remote config: {}", error_text))
Err(format!("Failed to delete remote config: {error_text}"))
}
}
@@ -218,13 +217,13 @@ pub async fn rclone_mount_remote(
let client = Client::new();
let response = client
.post(&format!("{}/mount/mount", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/mount/mount"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&mount_request)
.send()
.await
.map_err(|e| format!("Failed to mount remote: {}", e))?;
.map_err(|e| format!("Failed to mount remote: {e}"))?;
if response.status().is_success() {
Ok(true)
@@ -233,7 +232,7 @@ pub async fn rclone_mount_remote(
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to mount remote: {}", error_text))
Err(format!("Failed to mount remote: {error_text}"))
}
}
@@ -245,13 +244,13 @@ pub async fn rclone_unmount_remote(
let client = Client::new();
let response = client
.post(&format!("{}/mount/unmount", RCLONE_API_BASE))
.post(format!("{RCLONE_API_BASE}/mount/unmount"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&json!({ "mountPoint": mount_point }))
.send()
.await
.map_err(|e| format!("Failed to unmount remote: {}", e))?;
.map_err(|e| format!("Failed to unmount remote: {e}"))?;
if response.status().is_success() {
Ok(true)
@@ -260,26 +259,26 @@ pub async fn rclone_unmount_remote(
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to unmount remote: {}", error_text))
Err(format!("Failed to unmount remote: {error_text}"))
}
}
#[tauri::command]
pub async fn create_rclone_mount_remote_process(
config: ProcessConfig,
state: State<'_, AppState>,
_state: State<'_, AppState>,
) -> Result<ProcessConfig, String> {
let binary_path =
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {}", e))?;
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {e}"))?;
let log_file_path =
get_app_logs_dir().map_err(|e| format!("Failed to get app logs directory: {}", e))?;
get_app_logs_dir().map_err(|e| format!("Failed to get app logs directory: {e}"))?;
let log_file_path = log_file_path.join("process_rclone.log");
let rclone_conf_path = binary_path
.parent()
.map(|p| p.join("rclone.conf"))
.ok_or_else(|| "Failed to determine rclone.conf path".to_string())?;
let api_key = get_api_key(state);
let api_key = get_api_key();
let port = get_server_port();
let mut args: Vec<String> = vec![
"mount".into(),
@@ -306,23 +305,22 @@ pub async fn create_rclone_mount_remote_process(
};
let client = reqwest::Client::new();
let response = client
.post(format!("http://127.0.0.1:{}/api/v1/processes", port))
.post(format!("http://127.0.0.1:{port}/api/v1/processes"))
.json(&config)
.header("Authorization", format!("Bearer {}", api_key))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
let response_text = response
.text()
.await
.map_err(|e| format!("Failed to read response text: {}", e))?;
.map_err(|e| format!("Failed to read response text: {e}"))?;
let process_config = match serde_json::from_str::<CreateProcessResponse>(&response_text) {
Ok(process_config) => process_config,
Err(e) => {
return Err(format!(
"Failed to parse response: {}, response: {}",
e, response_text
"Failed to parse response: {e}, response: {response_text}"
));
}
};
@@ -347,7 +345,7 @@ pub async fn check_mount_status(
#[cfg(target_os = "windows")]
{
if mount_point.len() == 2 && mount_point.ends_with(':') {
let drive_path = format!("{}\\", mount_point);
let drive_path = format!("{mount_point}\\");
match fs::read_dir(&drive_path) {
Ok(_) => return Ok(true),
Err(_) => return Ok(false),
@@ -387,12 +385,10 @@ pub async fn get_mount_info_list(
Ok(is_mounted) => {
if process.is_running {
if is_mounted { "mounted" } else { "mounting" }
} else if is_mounted {
"unmounting"
} else {
if is_mounted {
"unmounting"
} else {
"unmounted"
}
"unmounted"
}
}
Err(_) => "error",

View File

@@ -24,16 +24,16 @@ pub async fn uninstall_service() -> Result<bool, String> {
}
#[tauri::command]
pub async fn stop_service(state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key(state);
pub async fn stop_service(_state: State<'_, AppState>) -> Result<bool, String> {
let api_key = get_api_key();
let port = get_server_port();
let client = reqwest::Client::new();
let response = client
.post(format!("http://127.0.0.1:{}/api/v1/service/stop", port))
.header("Authorization", format!("Bearer {}", api_key))
.post(format!("http://127.0.0.1:{port}/api/v1/service/stop"))
.header("Authorization", format!("Bearer {api_key}"))
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
.map_err(|e| format!("Failed to send request: {e}"))?;
if response.status().is_success() {
Ok(true)
} else {

View File

@@ -26,16 +26,16 @@ impl MergedSettings {
}
}
fn get_data_config_path() -> Result<PathBuf, String> {
pub fn get_data_config_path() -> Result<PathBuf, String> {
let app_dir = std::env::current_exe()
.map_err(|e| format!("Failed to get current exe path: {}", e))?
.map_err(|e| format!("Failed to get current exe path: {e}"))?
.parent()
.ok_or("Failed to get parent directory")?
.to_path_buf();
Ok(app_dir.join("data").join("config.json"))
}
fn read_data_config() -> Result<serde_json::Value, String> {
pub fn read_data_config() -> Result<serde_json::Value, String> {
let path = Self::get_data_config_path()?;
if !path.exists() {
return Err("data/config.json does not exist".to_string());
@@ -78,12 +78,10 @@ impl MergedSettings {
serde_json::from_str(&config).map_err(|e| e.to_string())?
};
if let Ok(data_port) = Self::get_port_from_data_config() {
if let Some(port) = data_port {
if merged_settings.openlist.port != port {
merged_settings.openlist.port = port;
merged_settings.save()?;
}
if let Ok(Some(port)) = Self::get_port_from_data_config() {
if merged_settings.openlist.port != port {
merged_settings.openlist.port = port;
merged_settings.save()?;
}
}

View File

@@ -31,8 +31,7 @@ pub async fn install_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true)
} else {
Err(Box::from(format!(
"Failed to install service, exit status: {}",
status
"Failed to install service, exit status: {status}"
)))
}
}
@@ -47,7 +46,7 @@ pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
let uninstall_path = app_dir.join("uninstall-openlist-service.exe");
if !uninstall_path.exists() {
error!("Uninstaller not found: {:?}", uninstall_path);
error!("Uninstaller not found: {uninstall_path:?}");
return Err(Box::from(format!(
"uninstaller not found: {uninstall_path:?}"
)));
@@ -66,8 +65,7 @@ pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true)
} else {
Err(Box::from(format!(
"Failed to uninstall service, exit status: {}",
status
"Failed to uninstall service, exit status: {status}"
)))
}
}
@@ -102,8 +100,7 @@ pub async fn install_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true)
} else {
Err(Box::from(format!(
"Failed to install service, exit status: {}",
status
"Failed to install service, exit status: {status}"
)))
}
}
@@ -116,10 +113,9 @@ pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
let uninstall_path = app_dir.join("uninstall-openlist-service");
if !uninstall_path.exists() {
error!("Uninstaller not found: {:?}", uninstall_path);
error!("Uninstaller not found: {uninstall_path:?}");
return Err(Box::from(format!(
"Uninstaller not found: {:?}",
uninstall_path
"Uninstaller not found: {uninstall_path:?}"
)));
}
@@ -139,8 +135,7 @@ pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true)
} else {
Err(Box::from(format!(
"Failed to uninstall service, exit status: {}",
status
"Failed to uninstall service, exit status: {status}"
)))
}
}
@@ -172,7 +167,7 @@ fn start_service_with_elevation(service_name: &str) -> Result<bool, Box<dyn std:
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let powershell_cmd = format!("Start-Service -Name '{}'", service_name);
let powershell_cmd = format!("Start-Service -Name '{service_name}'");
let status = match level {
PrivilegeLevel::NotPrivileged => {
@@ -185,7 +180,7 @@ fn start_service_with_elevation(service_name: &str) -> Result<bool, Box<dyn std:
_ => {
log::info!("Already have admin privileges, running directly");
StdCommand::new("powershell.exe")
.args(&["-Command", &powershell_cmd])
.args(["-Command", &powershell_cmd])
.creation_flags(0x08000000)
.status()?
}
@@ -195,10 +190,7 @@ fn start_service_with_elevation(service_name: &str) -> Result<bool, Box<dyn std:
log::info!("Service started successfully via PowerShell");
Ok(true)
} else {
log::error!(
"Failed to start service via PowerShell, exit code: {}",
status
);
log::error!("Failed to start service via PowerShell, exit code: {status}");
Ok(false)
}
}
@@ -219,7 +211,7 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
let service = match manager.open_service(service_name, ServiceAccess::QUERY_STATUS) {
Ok(svc) => svc,
Err(e) => {
log::error!("Failed to open service '{}': {:?}", service_name, e);
log::error!("Failed to open service '{service_name}': {e:?}");
return Ok(false);
}
};
@@ -247,13 +239,13 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true) => Ok(true),
Ok(false) => Ok(false),
Err(e) => {
log::error!("Error during service elevation: {:?}", e);
log::error!("Error during service elevation: {e:?}");
Ok(false)
}
}
}
Err(e) => {
log::error!("Failed to query service status: {:?}", e);
log::error!("Failed to query service status: {e:?}");
match start_service_with_elevation(service_name) {
Ok(true) => Ok(true),
Ok(false) => {
@@ -261,7 +253,7 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(false)
}
Err(elev_err) => {
log::error!("Error during service elevation: {:?}", elev_err);
log::error!("Error during service elevation: {elev_err:?}");
Ok(false)
}
}
@@ -273,7 +265,7 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
const SERVICE_NAME: &str = "openlist-desktop-service";
log::info!("Checking Linux service status for: {}", SERVICE_NAME);
log::info!("Checking Linux service status for: {SERVICE_NAME}");
let init_system = detect_linux_init_system();
@@ -281,7 +273,7 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
"systemd" => start_systemd_service_with_check(SERVICE_NAME).await,
"openrc" => start_openrc_service_with_check(SERVICE_NAME).await,
_ => {
log::warn!("Unknown init system: {}, assuming systemd", init_system);
log::warn!("Unknown init system: {init_system}, assuming systemd");
start_systemd_service_with_check(SERVICE_NAME).await
}
}
@@ -291,10 +283,10 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
async fn start_systemd_service_with_check(
service_name: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
log::info!("Checking systemd service status for: {}", service_name);
log::info!("Checking systemd service status for: {service_name}");
let status_output = StdCommand::new("systemctl")
.args(&["is-active", service_name])
.args(["is-active", service_name])
.output();
match status_output {
@@ -302,21 +294,21 @@ async fn start_systemd_service_with_check(
let status = String::from_utf8_lossy(&output.stdout)
.trim()
.to_lowercase();
log::info!("Service {} status: {}", service_name, status);
log::info!("Service {service_name} status: {status}");
match status.as_str() {
"active" | "activating" => {
log::info!("Service is active and running");
return Ok(true);
Ok(true)
}
"inactive" | "failed" => {
log::info!("Service is {}, attempting to start", status);
log::info!("Service is {status}, attempting to start");
return start_systemd_service(service_name).await;
}
"unknown" => {
log::warn!("Service status unknown, checking if service exists");
let exists_output = StdCommand::new("systemctl")
.args(&["list-unit-files", &format!("{}.service", service_name)])
.args(["list-unit-files", &format!("{service_name}.service")])
.output();
match exists_output {
@@ -326,24 +318,24 @@ async fn start_systemd_service_with_check(
log::info!("Service exists but not active, attempting to start");
return start_systemd_service(service_name).await;
} else {
log::error!("Service {} not found", service_name);
return Ok(false);
log::error!("Service {service_name} not found");
Ok(false)
}
}
_ => {
log::error!("Failed to check if service exists");
return Ok(false);
Ok(false)
}
}
}
_ => {
log::warn!("Unknown service status: {}, attempting to start", status);
log::warn!("Unknown service status: {status}, attempting to start");
return start_systemd_service(service_name).await;
}
}
}
Err(e) => {
log::error!("Failed to check systemd service status: {}", e);
log::error!("Failed to check systemd service status: {e}");
return start_systemd_service(service_name).await;
}
}
@@ -353,10 +345,10 @@ async fn start_systemd_service_with_check(
async fn start_openrc_service_with_check(
service_name: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
log::info!("Checking OpenRC service status for: {}", service_name);
log::info!("Checking OpenRC service status for: {service_name}");
let status_output = StdCommand::new("rc-service")
.args(&[service_name, "status"])
.args([service_name, "status"])
.output();
match status_output {
@@ -364,16 +356,16 @@ async fn start_openrc_service_with_check(
let status_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
let stderr_str = String::from_utf8_lossy(&output.stderr).to_lowercase();
log::info!("OpenRC service status output: {}", status_str);
log::info!("OpenRC service status output: {status_str}");
if status_str.contains("started") || status_str.contains("running") {
log::info!("Service is running");
return Ok(true);
Ok(true)
} else if status_str.contains("stopped") || status_str.contains("inactive") {
log::info!("Service is stopped, attempting to start");
return start_openrc_service(service_name).await;
} else if stderr_str.contains("does not exist") {
log::error!("Service {} does not exist", service_name);
log::error!("Service {service_name} does not exist");
return Ok(false);
} else {
log::warn!("Unknown service status, attempting to start");
@@ -381,7 +373,7 @@ async fn start_openrc_service_with_check(
}
}
Err(e) => {
log::error!("Failed to check OpenRC service status: {}", e);
log::error!("Failed to check OpenRC service status: {e}");
return start_openrc_service(service_name).await;
}
}
@@ -406,8 +398,7 @@ pub async fn install_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true)
} else {
Err(Box::from(format!(
"Failed to install service, exit status: {}",
status
"Failed to install service, exit status: {status}"
)))
}
}
@@ -418,10 +409,9 @@ pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
let uninstall_path = app_dir.join("uninstall-openlist-service");
if !uninstall_path.exists() {
error!("Uninstaller not found: {:?}", uninstall_path);
error!("Uninstaller not found: {uninstall_path:?}");
return Err(Box::from(format!(
"Uninstaller not found: {:?}",
uninstall_path
"Uninstaller not found: {uninstall_path:?}"
)));
}
let status = StdCommand::new(&uninstall_path).status()?;
@@ -430,8 +420,7 @@ pub async fn uninstall_service() -> Result<bool, Box<dyn std::error::Error>> {
Ok(true)
} else {
Err(Box::from(format!(
"Failed to uninstall service, exit status: {}",
status
"Failed to uninstall service, exit status: {status}"
)))
}
}
@@ -452,26 +441,24 @@ pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>
let service = match manager.open_service(service_name, ServiceAccess::QUERY_STATUS) {
Ok(svc) => svc,
Err(e) => {
log::error!("Failed to open service '{}': {:?}", service_name, e);
log::error!("Failed to open service '{service_name}': {e:?}");
return Ok("not-installed".to_string());
}
};
match service.query_status() {
Ok(status) => match status.current_state {
ServiceState::Running | ServiceState::StartPending => {
return Ok("running".to_string());
}
ServiceState::Running | ServiceState::StartPending => Ok("running".to_string()),
ServiceState::StopPending => {
std::thread::sleep(std::time::Duration::from_millis(1000));
return Ok("stopped".to_string());
Ok("stopped".to_string())
}
_ => {
log::info!("Service is in state: {:?}.", status.current_state);
return Ok("stopped".to_string());
Ok("stopped".to_string())
}
},
Err(e) => {
log::error!("Failed to query service status: {:?}", e);
log::error!("Failed to query service status: {e:?}");
match start_service_with_elevation(service_name) {
Ok(true) => Ok("running".to_string()),
Ok(false) => {
@@ -479,7 +466,7 @@ pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>
Ok("stopped".to_string())
}
Err(elev_err) => {
log::error!("Error during service elevation: {:?}", elev_err);
log::error!("Error during service elevation: {elev_err:?}");
Ok("error".to_string())
}
}
@@ -491,7 +478,7 @@ pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>
pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>> {
const SERVICE_NAME: &str = "openlist-desktop-service";
log::info!("Checking Linux service status for: {}", SERVICE_NAME);
log::info!("Checking Linux service status for: {SERVICE_NAME}");
let init_system = detect_linux_init_system();
@@ -499,7 +486,7 @@ pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>
"systemd" => check_systemd_service_status(SERVICE_NAME).await,
"openrc" => check_openrc_service_status(SERVICE_NAME).await,
_ => {
log::warn!("Unknown init system: {}, assuming systemd", init_system);
log::warn!("Unknown init system: {init_system}, assuming systemd");
check_systemd_service_status(SERVICE_NAME).await
}
}
@@ -534,10 +521,10 @@ fn detect_linux_init_system() -> String {
async fn check_systemd_service_status(
service_name: &str,
) -> Result<String, Box<dyn std::error::Error>> {
log::info!("Checking systemd service status for: {}", service_name);
log::info!("Checking systemd service status for: {service_name}");
let status_output = StdCommand::new("systemctl")
.args(&["is-active", service_name])
.args(["is-active", service_name])
.output();
match status_output {
@@ -545,21 +532,21 @@ async fn check_systemd_service_status(
let status = String::from_utf8_lossy(&output.stdout)
.trim()
.to_lowercase();
log::info!("Service {} status: {}", service_name, status);
log::info!("Service {service_name} status: {status}");
match status.as_str() {
"active" | "activating" => {
log::info!("Service is active and running");
return Ok("running".to_string());
Ok("running".to_string())
}
"inactive" | "failed" => {
log::info!("Service is {}", status);
return Ok("stopped".to_string());
log::info!("Service is {status}");
Ok("stopped".to_string())
}
"unknown" => {
log::warn!("Service status unknown, checking if service exists");
let exists_output = StdCommand::new("systemctl")
.args(&["list-unit-files", &format!("{}.service", service_name)])
.args(["list-unit-files", &format!("{service_name}.service")])
.output();
match exists_output {
@@ -567,27 +554,27 @@ async fn check_systemd_service_status(
let output_str = String::from_utf8_lossy(&output.stdout);
if output_str.contains(service_name) {
log::info!("Service exists and not active");
return Ok("stopped".to_string());
Ok("stopped".to_string())
} else {
log::error!("Service {} not found", service_name);
return Ok("not-installed".to_string());
log::error!("Service {service_name} not found");
Ok("not-installed".to_string())
}
}
_ => {
log::error!("Failed to check if service exists");
return Ok("error".to_string());
Ok("error".to_string())
}
}
}
_ => {
log::warn!("Unknown service status: {}", status);
return Ok("error".to_string());
log::warn!("Unknown service status: {status}");
Ok("error".to_string())
}
}
}
Err(e) => {
log::error!("Failed to check systemd service status: {}", e);
return Ok("error".to_string());
log::error!("Failed to check systemd service status: {e}");
Ok("error".to_string())
}
}
}
@@ -596,18 +583,18 @@ async fn check_systemd_service_status(
async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
use users::get_effective_uid;
log::info!("Attempting to start systemd service: {}", service_name);
log::info!("Attempting to start systemd service: {service_name}");
let status = match get_effective_uid() {
0 => StdCommand::new("systemctl")
.args(&["start", service_name])
.args(["start", service_name])
.status()?,
_ => {
let elevator = linux_elevator();
log::info!("Using {} for elevation", elevator);
log::info!("Using {elevator} for elevation");
StdCommand::new(&elevator)
.args(&["systemctl", "start", service_name])
.args(["systemctl", "start", service_name])
.status()?
}
};
@@ -617,7 +604,7 @@ async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::
std::thread::sleep(std::time::Duration::from_millis(1000));
let verify_output = StdCommand::new("systemctl")
.args(&["is-active", service_name])
.args(["is-active", service_name])
.output()?;
let verify_status_str = String::from_utf8_lossy(&verify_output.stdout);
@@ -628,14 +615,13 @@ async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::
log::info!("Service verified as running");
} else {
log::warn!(
"Service start command succeeded but service is not active: {}",
verify_status
"Service start command succeeded but service is not active: {verify_status}"
);
}
Ok(is_running)
} else {
log::error!("Failed to start service, exit code: {}", status);
log::error!("Failed to start service, exit code: {status}");
Ok(false)
}
}
@@ -644,10 +630,10 @@ async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::
async fn check_openrc_service_status(
service_name: &str,
) -> Result<String, Box<dyn std::error::Error>> {
log::info!("Checking OpenRC service status for: {}", service_name);
log::info!("Checking OpenRC service status for: {service_name}");
let status_output = StdCommand::new("rc-service")
.args(&[service_name, "status"])
.args([service_name, "status"])
.output();
match status_output {
@@ -655,25 +641,25 @@ async fn check_openrc_service_status(
let status_str = String::from_utf8_lossy(&output.stdout).to_lowercase();
let stderr_str = String::from_utf8_lossy(&output.stderr).to_lowercase();
log::info!("OpenRC service status output: {}", status_str);
log::info!("OpenRC service status output: {status_str}");
if status_str.contains("started") || status_str.contains("running") {
log::info!("Service is running");
return Ok("running".to_string());
Ok("running".to_string())
} else if status_str.contains("stopped") || status_str.contains("inactive") {
log::info!("Service is stopped");
return Ok("stopped".to_string());
Ok("stopped".to_string())
} else if stderr_str.contains("does not exist") {
log::error!("Service {} does not exist", service_name);
return Ok("not-installed".to_string());
log::error!("Service {service_name} does not exist");
Ok("not-installed".to_string())
} else {
log::warn!("Unknown service status, attempting to start");
return Ok("error".to_string());
Ok("error".to_string())
}
}
Err(e) => {
log::error!("Failed to check OpenRC service status: {}", e);
return Ok("error".to_string());
log::error!("Failed to check OpenRC service status: {e}");
Ok("error".to_string())
}
}
}
@@ -682,17 +668,17 @@ async fn check_openrc_service_status(
async fn start_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::error::Error>> {
use users::get_effective_uid;
log::info!("Attempting to start OpenRC service: {}", service_name);
log::info!("Attempting to start OpenRC service: {service_name}");
let status = match get_effective_uid() {
0 => StdCommand::new("rc-service")
.args(&[service_name, "start"])
.args([service_name, "start"])
.status()?,
_ => {
let elevator = linux_elevator();
log::info!("Using {} for elevation", elevator);
log::info!("Using {elevator} for elevation");
StdCommand::new(&elevator)
.args(&["rc-service", service_name, "start"])
.args(["rc-service", service_name, "start"])
.status()?
}
};
@@ -702,7 +688,7 @@ async fn start_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::e
std::thread::sleep(std::time::Duration::from_millis(1000));
let verify_output = StdCommand::new("rc-service")
.args(&[service_name, "status"])
.args([service_name, "status"])
.output()?;
let verify_status = String::from_utf8_lossy(&verify_output.stdout).to_lowercase();
@@ -712,14 +698,13 @@ async fn start_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::e
log::info!("Service verified as running");
} else {
log::warn!(
"Service start command succeeded but service is not running: {}",
verify_status
"Service start command succeeded but service is not running: {verify_status}"
);
}
Ok(is_running)
} else {
log::error!("Failed to start OpenRC service, exit code: {}", status);
log::error!("Failed to start OpenRC service, exit code: {status}");
Ok(false)
}
}
@@ -728,23 +713,23 @@ async fn start_openrc_service(service_name: &str) -> Result<bool, Box<dyn std::e
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service";
log::info!("Checking macOS service status for: {}", SERVICE_IDENTIFIER);
log::info!("Checking macOS service status for: {SERVICE_IDENTIFIER}");
let status_output = StdCommand::new("launchctl")
.args(&["list", SERVICE_IDENTIFIER])
.args(["list", SERVICE_IDENTIFIER])
.output();
match status_output {
Ok(output) => {
if output.status.success() {
let output_str = String::from_utf8_lossy(&output.stdout);
log::info!("launchctl list output: {}", output_str);
log::info!("launchctl list output: {output_str}");
if let Some(pid_value) = extract_plist_value(&output_str, "PID") {
log::info!("Extracted PID value: {}", pid_value);
log::info!("Extracted PID value: {pid_value}");
if let Ok(pid) = pid_value.parse::<i32>() {
if pid > 0 {
log::info!("Service is running with PID: {}", pid);
log::info!("Service is running with PID: {pid}");
return Ok(true);
}
}
@@ -759,10 +744,9 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
return start_macos_service(SERVICE_IDENTIFIER).await;
} else {
log::warn!(
"Service has non-zero exit status: {}, attempting to restart",
status
"Service has non-zero exit status: {status}, attempting to restart"
);
return restart_macos_service(SERVICE_IDENTIFIER).await;
return start_macos_service(SERVICE_IDENTIFIER).await;
}
}
}
@@ -772,8 +756,8 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
} else {
let stderr_str = String::from_utf8_lossy(&output.stderr);
if stderr_str.contains("Could not find service") {
log::error!("Service {} is not loaded", SERVICE_IDENTIFIER);
return Ok(false);
log::error!("Service {SERVICE_IDENTIFIER} is not loaded");
Ok(false)
} else {
log::warn!("launchctl list failed, attempting to start service anyway");
return start_macos_service(SERVICE_IDENTIFIER).await;
@@ -781,7 +765,7 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
}
}
Err(e) => {
log::error!("Failed to check macOS service status: {}", e);
log::error!("Failed to check macOS service status: {e}");
return start_macos_service(SERVICE_IDENTIFIER).await;
}
}
@@ -791,23 +775,23 @@ pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>> {
const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service";
log::info!("Checking macOS service status for: {}", SERVICE_IDENTIFIER);
log::info!("Checking macOS service status for: {SERVICE_IDENTIFIER}");
let status_output = StdCommand::new("launchctl")
.args(&["list", SERVICE_IDENTIFIER])
.args(["list", SERVICE_IDENTIFIER])
.output();
match status_output {
Ok(output) => {
if output.status.success() {
let output_str = String::from_utf8_lossy(&output.stdout);
log::info!("launchctl list output: {}", output_str);
log::info!("launchctl list output: {output_str}");
if let Some(pid_value) = extract_plist_value(&output_str, "PID") {
log::info!("Extracted PID value: {}", pid_value);
log::info!("Extracted PID value: {pid_value}");
if let Ok(pid) = pid_value.parse::<i32>() {
if pid > 0 {
log::info!("Service is running with PID: {}", pid);
log::info!("Service is running with PID: {pid}");
return Ok("running".to_string());
}
}
@@ -819,38 +803,38 @@ pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>
log::info!("Service is loaded but not running (clean exit)");
return Ok("stopped".to_string());
} else {
log::warn!("Service has non-zero exit status: {}", status);
log::warn!("Service has non-zero exit status: {status}");
return Ok("stopped".to_string());
}
}
}
log::info!("Service appears to be loaded but status unclear");
return Ok("error".to_string());
Ok("error".to_string())
} else {
let stderr_str = String::from_utf8_lossy(&output.stderr);
if stderr_str.contains("Could not find service") {
log::error!("Service {} is not loaded", SERVICE_IDENTIFIER);
return Ok("not-installed".to_string());
log::error!("Service {SERVICE_IDENTIFIER} is not loaded");
Ok("not-installed".to_string())
} else {
log::warn!("launchctl list failed");
return Ok("error".to_string());
Ok("error".to_string())
}
}
}
Err(e) => {
log::error!("Failed to check macOS service status: {}", e);
return Ok("error".to_string());
log::error!("Failed to check macOS service status: {e}");
Ok("error".to_string())
}
}
}
#[cfg(target_os = "macos")]
async fn start_macos_service(service_identifier: &str) -> Result<bool, Box<dyn std::error::Error>> {
log::info!("Attempting to start macOS service: {}", service_identifier);
log::info!("Attempting to start macOS service: {service_identifier}");
let status = StdCommand::new("launchctl")
.args(&["start", service_identifier])
.args(["start", service_identifier])
.status()?;
if status.success() {
@@ -858,20 +842,20 @@ async fn start_macos_service(service_identifier: &str) -> Result<bool, Box<dyn s
std::thread::sleep(std::time::Duration::from_millis(2000));
let verify_output = StdCommand::new("launchctl")
.args(&["list", service_identifier])
.args(["list", service_identifier])
.output()?;
if verify_output.status.success() {
let output_str = String::from_utf8_lossy(&verify_output.stdout);
log::info!("Verification output: {}", output_str);
log::info!("Verification output: {output_str}");
if let Some(pid_value) = extract_plist_value(&output_str, "PID") {
if let Ok(pid) = pid_value.parse::<i32>() {
if pid > 0 {
log::info!("Service verified as running with PID: {}", pid);
log::info!("Service verified as running with PID: {pid}");
return Ok(true);
} else {
log::warn!("Service has invalid PID: {}", pid);
log::warn!("Service has invalid PID: {pid}");
return Ok(false);
}
}
@@ -886,14 +870,14 @@ async fn start_macos_service(service_identifier: &str) -> Result<bool, Box<dyn s
log::warn!("Could not verify service status after start");
Ok(false)
} else {
log::error!("Failed to start macOS service, exit code: {}", status);
log::error!("Failed to start macOS service, exit code: {status}");
Ok(false)
}
}
#[cfg(target_os = "macos")]
fn extract_plist_value(plist_output: &str, key: &str) -> Option<String> {
let pattern = format!("\"{}\"", key);
let pattern = format!("\"{key}\"");
for line in plist_output.lines() {
let trimmed = line.trim();
@@ -902,8 +886,8 @@ fn extract_plist_value(plist_output: &str, key: &str) -> Option<String> {
let value_part = &trimmed[equals_pos + 1..];
let value_trimmed = value_part.trim();
let value_clean = if value_trimmed.ends_with(';') {
&value_trimmed[..value_trimmed.len() - 1]
let value_clean = if let Some(stripped) = value_trimmed.strip_suffix(';') {
stripped
} else {
value_trimmed
};

View File

@@ -8,7 +8,7 @@ mod tray;
mod utils;
use cmd::binary::get_binary_version;
use cmd::config::{load_settings, reset_settings, save_settings};
use cmd::config::{load_settings, reset_settings, save_settings, save_settings_with_update_port};
use cmd::custom_updater::{
check_for_updates, download_update, get_current_version, install_update_and_restart,
is_auto_check_enabled, restart_app, set_auto_check_enabled,
@@ -45,7 +45,7 @@ async fn update_tray_menu(
service_running: bool,
) -> Result<(), String> {
tray::update_tray_menu(&app_handle, service_running)
.map_err(|e| format!("Failed to update tray menu: {}", e))
.map_err(|e| format!("Failed to update tray menu: {e}"))
}
#[tauri::command]
@@ -54,7 +54,7 @@ async fn update_tray_menu_delayed(
service_running: bool,
) -> Result<(), String> {
tray::update_tray_menu_delayed(&app_handle, service_running)
.map_err(|e| format!("Failed to update tray menu (delayed): {}", e))
.map_err(|e| format!("Failed to update tray menu (delayed): {e}"))
}
#[tauri::command]
@@ -63,7 +63,7 @@ async fn force_update_tray_menu(
service_running: bool,
) -> Result<(), String> {
tray::force_update_tray_menu(&app_handle, service_running)
.map_err(|e| format!("Failed to force update tray menu: {}", e))
.map_err(|e| format!("Failed to force update tray menu: {e}"))
}
fn setup_background_update_checker(app_handle: &tauri::AppHandle) {
@@ -79,7 +79,7 @@ fn setup_background_update_checker(app_handle: &tauri::AppHandle) {
cmd::custom_updater::perform_background_update_check(app_handle_initial.clone())
.await
{
log::debug!("Initial background update check failed: {}", e);
log::debug!("Initial background update check failed: {e}");
}
}
_ => {
@@ -94,10 +94,10 @@ pub fn run() {
let app_state = AppState::new();
log::info!("Starting {}...", utils::path::APP_ID);
unsafe {
#[cfg(target_os = "linux")]
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
};
#[cfg(target_os = "linux")]
{
unsafe { std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1") };
}
tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
@@ -142,6 +142,7 @@ pub fn run() {
open_folder,
open_url,
save_settings,
save_settings_with_update_port,
load_settings,
reset_settings,
get_logs,
@@ -174,20 +175,19 @@ pub fn run() {
utils::init_log::init_log()?;
utils::path::get_app_config_dir()?;
let app_state = app.state::<AppState>();
if let Err(e) = app_state.init(&app_handle) {
log::error!("Failed to initialize app state: {}", e);
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
format!("App state initialization failed: {}", e),
)));
if let Err(e) = app_state.init(app_handle) {
log::error!("Failed to initialize app state: {e}");
return Err(Box::new(std::io::Error::other(format!(
"App state initialization failed: {e}"
))));
}
if let Err(e) = tray::create_tray(&app_handle) {
log::error!("Failed to create system tray: {}", e);
if let Err(e) = tray::create_tray(app_handle) {
log::error!("Failed to create system tray: {e}");
} else {
log::info!("System tray created successfully");
}
setup_background_update_checker(&app_handle);
setup_background_update_checker(app_handle);
if let Some(window) = app.get_webview_window("main") {
let app_handle_clone = app_handle.clone();

View File

@@ -31,7 +31,7 @@ impl AppState {
Ok(())
}
Err(e) => {
log::warn!("Failed to load settings, using defaults: {}", e);
log::warn!("Failed to load settings, using defaults: {e}");
let default_settings = MergedSettings::default();
let mut app_settings = self.app_settings.write();
*app_settings = Some(default_settings);

View File

@@ -79,7 +79,7 @@ pub fn create_tray(app_handle: &AppHandle) -> tauri::Result<()> {
log::debug!("Mouse left tray icon area");
}
_ => {
log::debug!("Other tray event: {:?}", event);
log::debug!("Other tray event: {event:?}");
}
}
})
@@ -200,10 +200,7 @@ pub fn update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri:
)?;
tray.set_menu(Some(menu))?;
log::debug!(
"Tray menu updated with service_running: {}",
service_running
);
log::debug!("Tray menu updated with service_running: {service_running}");
}
Ok(())
}
@@ -213,23 +210,24 @@ pub fn update_tray_menu_delayed(
service_running: bool,
) -> tauri::Result<()> {
let app_handle_clone = app_handle.clone();
println!("Scheduling delayed tray menu update...");
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(3000));
if let Err(e) = update_tray_menu(&app_handle_clone, service_running) {
log::error!("Failed to update tray menu (delayed): {}", e);
log::error!("Failed to update tray menu (delayed): {e}");
}
});
Ok(())
}
fn handle_core_action(app_handle: &AppHandle, action: &str) {
log::info!("Handling core action from tray: {}", action);
log::info!("Handling core action from tray: {action}");
if let Err(e) = app_handle.emit("tray-core-action", action) {
log::error!("Failed to emit tray core action event: {}", e);
log::error!("Failed to emit tray core action event: {e}");
}
log::debug!("Core action '{}' dispatched to frontend", action);
log::debug!("Core action '{action}' dispatched to frontend");
}
pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri::Result<()> {
@@ -288,10 +286,7 @@ pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) ->
*last_update = Some(Instant::now());
}
log::debug!(
"Tray menu force updated with service_running: {}",
service_running
);
log::debug!("Tray menu force updated with service_running: {service_running}");
}
Ok(())
}

View File

@@ -1,9 +1,6 @@
use crate::object::structs::AppState;
use std::{collections::HashMap, env};
use serde::{Deserialize, Serialize};
use tauri::State;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ProcessConfig {
@@ -56,21 +53,11 @@ pub fn get_server_port() -> u16 {
env::var("PROCESS_MANAGER_PORT")
.ok()
.and_then(|port_str| port_str.parse().ok())
.unwrap_or_else(|| DEFAULT_HTTP_SERVER_PORT)
.unwrap_or(DEFAULT_HTTP_SERVER_PORT)
}
pub fn get_api_key(state: State<'_, AppState>) -> String {
let app_settings = state
.app_settings
.read()
.clone()
.ok_or_else(|| "Failed to read app settings".to_string())
.unwrap();
let openlist_config = app_settings.openlist;
if openlist_config.api_token != "" {
return openlist_config.api_token.clone();
}
let api_key =
env::var("PROCESS_MANAGER_API_KEY").unwrap_or_else(|_| DEFAULT_API_KEY.to_string());
api_key
pub fn get_api_key() -> String {
env::var("PROCESS_MANAGER_API_KEY")
.ok()
.unwrap_or_else(|| DEFAULT_API_KEY.to_string())
}

View File

@@ -5,15 +5,12 @@ pub static APP_ID: &str = "io.github.openlistteam.openlist.desktop";
fn get_app_dir() -> Result<PathBuf, String> {
let app_dir = env::current_exe()
.map_err(|e| format!("Failed to get current exe path: {}", e))?
.map_err(|e| format!("Failed to get current exe path: {e}"))?
.parent()
.ok_or("Failed to get parent directory")?
.to_path_buf();
if !app_dir.exists() {
return Err(format!(
"Application directory does not exist: {:?}",
app_dir
));
return Err(format!("Application directory does not exist: {app_dir:?}"));
}
Ok(app_dir)
@@ -31,8 +28,7 @@ pub fn get_openlist_binary_path() -> Result<PathBuf, String> {
if !binary_path.exists() {
return Err(format!(
"OpenList service binary not found at: {:?}",
binary_path
"OpenList service binary not found at: {binary_path:?}"
));
}
@@ -51,8 +47,7 @@ pub fn get_rclone_binary_path() -> Result<PathBuf, String> {
if !binary_path.exists() {
return Err(format!(
"Rclone service binary not found at: {:?}",
binary_path
"Rclone service binary not found at: {binary_path:?}"
));
}

View File

@@ -148,6 +148,10 @@ export class TauriAPI {
return await invoke('save_settings', { settings })
}
static async saveSettingsWithUpdatePort(settings: MergedSettings): Promise<boolean> {
return await invoke('save_settings_with_update_port', { settings })
}
static async resetSettings(): Promise<MergedSettings | null> {
return await invoke('reset_settings')
}

View File

@@ -157,8 +157,8 @@ const gridColor = computed(() => {
return document.documentElement.classList.contains('dark') ? '#374151' : '#e5e7eb'
})
const checkServiceHealth = async () => {
await store.refreshServiceStatus()
const checkCoreHealth = async () => {
await store.refreshOpenListCoreStatus()
if (!isCoreRunning.value) {
dataPoints.value.push({
timestamp: Date.now(),
@@ -172,7 +172,7 @@ const checkServiceHealth = async () => {
const startTime = Date.now()
try {
await store.refreshServiceStatus()
await store.refreshOpenListCoreStatus()
const endTime = Date.now()
const responseTimeMs = endTime - startTime
@@ -228,7 +228,7 @@ onMounted(async () => {
startTime.value = Date.now()
}
monitoringInterval.value = window.setInterval(checkServiceHealth, (store.settings.app.monitor_interval || 5) * 1000)
monitoringInterval.value = window.setInterval(checkCoreHealth, (store.settings.app.monitor_interval || 5) * 1000)
window.addEventListener('resize', updateChartSize)
})

View File

@@ -20,7 +20,12 @@
<span>{{ t('dashboard.quickActions.restart') }}</span>
</button>
<button @click="openWebUI" :disabled="!isCoreRunning" class="action-btn web-btn">
<button
@click="openWebUI"
:disabled="!isCoreRunning"
class="action-btn web-btn"
:title="store.openListCoreUrl"
>
<ExternalLink :size="18" />
<span>{{ t('dashboard.quickActions.openWeb') }}</span>
</button>

View File

@@ -14,7 +14,7 @@ export const useCoreActions = () => {
const stopOpenListCore = async () => {
try {
await store.startOpenListCore()
await store.stopOpenListCore()
} catch (error) {
console.error('Failed to stop service:', error)
throw error

View File

@@ -18,8 +18,6 @@ export const useTray = () => {
}
const handleTrayServiceAction = async (action: string) => {
console.log('Tray core action:', action)
try {
switch (action) {
case 'start':

View File

@@ -304,7 +304,7 @@ export const useAppStore = defineStore('app', () => {
const isCoreRunning = computed(() => openlistCoreStatus.value.running)
const openListCoreUrl = computed(() => {
const protocol = settings.value.openlist.ssl_enabled ? 'https' : 'http'
return `${protocol}://localhost:${openlistCoreStatus.value.port}`
return `${protocol}://localhost:${settings.value.openlist.port}`
})
async function loadSettings() {
@@ -333,6 +333,17 @@ export const useAppStore = defineStore('app', () => {
}
}
async function saveSettingsWithUpdatePort(): Promise<boolean> {
try {
await TauriAPI.saveSettingsWithUpdatePort(settings.value)
return true
} catch (err) {
error.value = 'Failed to save settings'
console.error('Failed to save settings:', err)
return false
}
}
async function resetSettings() {
try {
loading.value = true
@@ -392,7 +403,7 @@ export const useAppStore = defineStore('app', () => {
}
openlistProcessId.value = processId
await refreshServiceStatus()
await refreshOpenListCoreStatus()
await TauriAPI.updateTrayMenu(openlistCoreStatus.value.running)
} catch (err: any) {
@@ -446,7 +457,7 @@ export const useAppStore = defineStore('app', () => {
error.value = errorMessage
console.error('Failed to stop service:', err)
try {
await refreshServiceStatus()
await refreshOpenListCoreStatus()
} catch (refreshErr) {
console.error('Failed to refresh service status after stop failure:', refreshErr)
}
@@ -485,19 +496,19 @@ export const useAppStore = defineStore('app', () => {
}
const result = await TauriAPI.restartProcess(id)
if (!result) {
throw new Error('Failed to restart OpenList Core service - service returned false')
throw new Error('Failed to restart OpenList Core - service returned false')
}
await refreshServiceStatus()
await refreshOpenListCoreStatus()
await TauriAPI.updateTrayMenu(openlistCoreStatus.value.running)
} catch (err: any) {
const errorMessage = `Failed to restart service: ${formatError(err)}`
const errorMessage = `Failed to restart core: ${formatError(err)}`
error.value = errorMessage
console.error('Failed to restart service:', err)
console.error('Failed to restart core:', err)
try {
await refreshServiceStatus()
await refreshOpenListCoreStatus()
await safeUpdateTrayMenu(openlistCoreStatus.value.running)
} catch (refreshErr) {
console.error('Failed to refresh service status after restart failure:', refreshErr)
console.error('Failed to refresh core status after restart failure:', refreshErr)
}
throw err
} finally {
@@ -505,7 +516,7 @@ export const useAppStore = defineStore('app', () => {
}
}
async function refreshServiceStatus() {
async function refreshOpenListCoreStatus() {
try {
const status = await TauriAPI.getOpenListCoreStatus()
const statusChanged = openlistCoreStatus.value.running !== status.running
@@ -605,13 +616,13 @@ export const useAppStore = defineStore('app', () => {
}
}
async function autoStartServiceIfEnabled() {
async function autoStartCoreIfEnabled() {
try {
if (settings.value.openlist.auto_launch) {
await startOpenListCore()
}
} catch (err) {
console.warn('Failed to auto-start service:', err)
console.warn('Failed to auto-start core:', err)
}
}
@@ -654,9 +665,10 @@ export const useAppStore = defineStore('app', () => {
try {
initTutorial()
await loadSettings()
await refreshServiceStatus()
await refreshOpenListCoreStatus()
await TauriAPI.updateTrayMenuDelayed(openlistCoreStatus.value.running)
await loadLogs()
await autoStartServiceIfEnabled()
await autoStartCoreIfEnabled()
await loadRemoteConfigs()
await loadMountInfos()
} catch (err) {
@@ -761,13 +773,14 @@ export const useAppStore = defineStore('app', () => {
loadSettings,
saveSettings,
saveSettingsWithUpdatePort,
resetSettings,
startOpenListCore,
stopOpenListCore,
restartOpenListCore,
enableAutoLaunch,
refreshServiceStatus,
refreshOpenListCoreStatus,
loadLogs,
clearLogs,
listFiles,

View File

@@ -19,10 +19,7 @@ const autoStartApp = ref(false)
const openlistCoreSettings = reactive({ ...store.settings.openlist })
const rcloneSettings = reactive({ ...store.settings.rclone })
const appSettings = reactive({ ...store.settings.app })
const isOpenListPortChanged = computed(() => {
return openlistCoreSettings.port !== store.settings.openlist.port
})
let originalOpenlistPort = openlistCoreSettings.port || 5244
watch(autoStartApp, async newValue => {
if (newValue) {
@@ -72,6 +69,7 @@ onMounted(async () => {
if (!appSettings.monitor_interval) appSettings.monitor_interval = 5
if (appSettings.auto_update_enabled === undefined) appSettings.auto_update_enabled = true
originalOpenlistPort = openlistCoreSettings.port || 5244
})
const hasUnsavedChanges = computed(() => {
@@ -108,8 +106,11 @@ const handleSave = async () => {
store.settings.openlist = { ...openlistCoreSettings }
store.settings.rclone = { ...rcloneSettings }
store.settings.app = { ...appSettings }
await store.saveSettings()
if (originalOpenlistPort !== openlistCoreSettings.port) {
await store.saveSettingsWithUpdatePort()
} else {
await store.saveSettings()
}
message.value = t('settings.saved')
messageType.value = 'success'
} catch (error) {

154
yarn.lock
View File

@@ -1353,6 +1353,13 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.2:
dependencies:
type-fest "^0.21.3"
ansi-escapes@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7"
integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==
dependencies:
environment "^1.0.0"
ansi-regex@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
@@ -1387,6 +1394,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
ansi-styles@^6.0.0, ansi-styles@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -1563,6 +1575,14 @@ cli-spinners@^2.5.0, cli-spinners@^2.9.2:
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
cli-truncate@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a"
integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==
dependencies:
slice-ansi "^5.0.0"
string-width "^7.0.0"
cli-width@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
@@ -1616,6 +1636,16 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colorette@^2.0.20:
version "2.0.20"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
commander@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.0.tgz#f244fc74a92343514e56229f16ef5c5e22ced5e9"
integrity sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==
commitizen@^4.0.3, commitizen@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.3.1.tgz#f0e0e4b7ae3fafc92e444bbb78f2ded5a1d4311a"
@@ -1743,7 +1773,14 @@ cosmiconfig@^9.0.0:
js-yaml "^4.1.0"
parse-json "^5.2.0"
cross-spawn@^7.0.6:
cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^7.0.1, cross-spawn@^7.0.6:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -1803,7 +1840,7 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
@@ -1887,6 +1924,11 @@ env-paths@^2.2.1:
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
environment@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1"
integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -2097,6 +2139,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
eventemitter3@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@@ -2611,6 +2658,18 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-fullwidth-code-point@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88"
integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==
is-fullwidth-code-point@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704"
integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==
dependencies:
get-east-asian-width "^1.0.0"
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -2761,11 +2820,44 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
lilconfig@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
lint-staged@^16.1.2:
version "16.1.2"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-16.1.2.tgz#8cb84daa844f39c7a9790dd2c0caa327125ef059"
integrity sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==
dependencies:
chalk "^5.4.1"
commander "^14.0.0"
debug "^4.4.1"
lilconfig "^3.1.3"
listr2 "^8.3.3"
micromatch "^4.0.8"
nano-spawn "^1.0.2"
pidtree "^0.6.0"
string-argv "^0.3.2"
yaml "^2.8.0"
listr2@^8.3.3:
version "8.3.3"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.3.3.tgz#815fc8f738260ff220981bf9e866b3e11e8121bf"
integrity sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==
dependencies:
cli-truncate "^4.0.0"
colorette "^2.0.20"
eventemitter3 "^5.0.1"
log-update "^6.1.0"
rfdc "^1.4.1"
wrap-ansi "^9.0.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
@@ -2851,6 +2943,17 @@ log-symbols@^6.0.0:
chalk "^5.3.0"
is-unicode-supported "^1.3.0"
log-update@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4"
integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==
dependencies:
ansi-escapes "^7.0.0"
cli-cursor "^5.0.0"
slice-ansi "^7.1.0"
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
longest@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8"
@@ -2999,6 +3102,11 @@ mute-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b"
integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==
nano-spawn@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.2.tgz#9853795681f0e96ef6f39104c2e4347b6ba79bf6"
integrity sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==
nanoid@^3.3.11:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
@@ -3235,6 +3343,11 @@ picomatch@^4.0.2:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
pidtree@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c"
integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==
pinia@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/pinia/-/pinia-3.0.3.tgz#f412019bdeb2f45e85927b432803190343e12d89"
@@ -3463,6 +3576,22 @@ signal-exit@^4.1.0:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
slice-ansi@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==
dependencies:
ansi-styles "^6.0.0"
is-fullwidth-code-point "^4.0.0"
slice-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9"
integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==
dependencies:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
source-map-js@^1.0.2, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -3514,6 +3643,11 @@ stdin-discarder@^0.2.2:
resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be"
integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==
string-argv@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
string-width@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -3531,7 +3665,7 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^7.2.0:
string-width@^7.0.0, string-width@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc"
integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==
@@ -3896,6 +4030,15 @@ wrap-ansi@^7.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e"
integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==
dependencies:
ansi-styles "^6.2.1"
string-width "^7.0.0"
strip-ansi "^7.1.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -3911,6 +4054,11 @@ yallist@^5.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
yaml@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6"
integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"