mirror of
https://github.com/TeamAmaze/AmazeFileManager.git
synced 2026-10-17 05:08:00 +08:00
Merge pull request #4431 from TranceLove/feature/rust-file-operations
This commit is contained in:
@@ -27,6 +27,39 @@ jobs:
|
||||
ndk-version: r28c
|
||||
link-to-sdk: true
|
||||
local-cache: true
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
file_operations/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Setup Rust for Android
|
||||
run: |
|
||||
cd file_operations
|
||||
chmod +x setup_rust_android.sh
|
||||
./setup_rust_android.sh
|
||||
- name: Verify Rust setup
|
||||
run: |
|
||||
cd file_operations
|
||||
echo "🔍 Verifying Rust Android targets..."
|
||||
rustup target list --installed | grep android || echo "No Android targets found"
|
||||
echo "🔍 Verifying cargo configuration..."
|
||||
if [ -f .cargo/config.toml ]; then
|
||||
echo "✅ .cargo/config.toml exists"
|
||||
else
|
||||
echo "❌ .cargo/config.toml missing"
|
||||
exit 1
|
||||
fi
|
||||
- name: Check formatting using spotless
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
@@ -42,6 +75,39 @@ jobs:
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
file_operations/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Setup Rust for Android
|
||||
run: |
|
||||
cd file_operations
|
||||
chmod +x setup_rust_android.sh
|
||||
./setup_rust_android.sh
|
||||
- name: Verify Rust setup
|
||||
run: |
|
||||
cd file_operations
|
||||
echo "🔍 Verifying Rust Android targets..."
|
||||
rustup target list --installed | grep android || echo "No Android targets found"
|
||||
echo "🔍 Verifying cargo configuration..."
|
||||
if [ -f .cargo/config.toml ]; then
|
||||
echo "✅ .cargo/config.toml exists"
|
||||
else
|
||||
echo "❌ .cargo/config.toml missing"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
|
||||
@@ -46,6 +46,39 @@ jobs:
|
||||
ndk-version: r28c
|
||||
link-to-sdk: true
|
||||
local-cache: true
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
file_operations/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Setup Rust for Android
|
||||
run: |
|
||||
cd file_operations
|
||||
chmod +x setup_rust_android.sh
|
||||
./setup_rust_android.sh
|
||||
- name: Verify Rust setup
|
||||
run: |
|
||||
cd file_operations
|
||||
echo "🔍 Verifying Rust Android targets..."
|
||||
rustup target list --installed | grep android || echo "No Android targets found"
|
||||
echo "🔍 Verifying cargo configuration..."
|
||||
if [ -f .cargo/config.toml ]; then
|
||||
echo "✅ .cargo/config.toml exists"
|
||||
else
|
||||
echo "❌ .cargo/config.toml missing"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
|
||||
@@ -20,6 +20,39 @@ jobs:
|
||||
ndk-version: r28c
|
||||
link-to-sdk: true
|
||||
local-cache: true
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
file_operations/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Setup Rust for Android
|
||||
run: |
|
||||
cd file_operations
|
||||
chmod +x setup_rust_android.sh
|
||||
./setup_rust_android.sh
|
||||
- name: Verify Rust setup
|
||||
run: |
|
||||
cd file_operations
|
||||
echo "🔍 Verifying Rust Android targets..."
|
||||
rustup target list --installed | grep android || echo "No Android targets found"
|
||||
echo "🔍 Verifying cargo configuration..."
|
||||
if [ -f .cargo/config.toml ]; then
|
||||
echo "✅ .cargo/config.toml exists"
|
||||
else
|
||||
echo "❌ .cargo/config.toml missing"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
|
||||
Regular → Executable
+36
@@ -23,6 +23,7 @@ jobs:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
- name: Set up NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@afb4c9964b521afb97c864b7d40b11e6911bd410
|
||||
with:
|
||||
ndk-version: r28c
|
||||
@@ -32,6 +33,8 @@ jobs:
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
arguments: spotlessCheck --stacktrace
|
||||
env:
|
||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||
|
||||
build:
|
||||
name: Build debug and run Jacoco tests
|
||||
@@ -43,6 +46,39 @@ jobs:
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
file_operations/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Setup Rust for Android
|
||||
run: |
|
||||
cd file_operations
|
||||
chmod +x setup_rust_android.sh
|
||||
./setup_rust_android.sh
|
||||
- name: Verify Rust setup
|
||||
run: |
|
||||
cd file_operations
|
||||
echo "🔍 Verifying Rust Android targets..."
|
||||
rustup target list --installed | grep android || echo "No Android targets found"
|
||||
echo "🔍 Verifying cargo configuration..."
|
||||
if [ -f .cargo/config.toml ]; then
|
||||
echo "✅ .cargo/config.toml exists"
|
||||
else
|
||||
echo "❌ .cargo/config.toml missing"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
|
||||
@@ -45,6 +45,39 @@ jobs:
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
file_operations/target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Setup Rust for Android
|
||||
run: |
|
||||
cd file_operations
|
||||
chmod +x setup_rust_android.sh
|
||||
./setup_rust_android.sh
|
||||
- name: Verify Rust setup
|
||||
run: |
|
||||
cd file_operations
|
||||
echo "🔍 Verifying Rust Android targets..."
|
||||
rustup target list --installed | grep android || echo "No Android targets found"
|
||||
echo "🔍 Verifying cargo configuration..."
|
||||
if [ -f .cargo/config.toml ]; then
|
||||
echo "✅ .cargo/config.toml exists"
|
||||
else
|
||||
echo "❌ .cargo/config.toml missing"
|
||||
exit 1
|
||||
fi
|
||||
- name: Build with Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
|
||||
@@ -38,6 +38,7 @@ If we feel your PR is a significant help to us, we'll award you a bounty with an
|
||||
|
||||
- Please follow [Android/JAVA code style](https://source.android.com/docs/setup/contribute/code-style) for writing any code. Please see [this](https://github.com/TeamAmaze/AmazeFileManager/issues/986) issue too.
|
||||
- Also, follow [Android Material Design guidelines](https://m3.material.io/get-started) in case you make changes to any UI element.
|
||||
- The `file_operations` module contains native code in Rust. Please see [file_operations/src/main/rust/README.md](file_operations/src/main/rust/README.md) for more details on how to work with it.
|
||||
|
||||
## PS: please make sure to
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ buildscript {
|
||||
classpath libs.kotlin.gradle.plugin
|
||||
classpath libs.easylauncher
|
||||
classpath libs.jacoco.android.plugin
|
||||
classpath libs.rust.android.gradle
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Cargo configuration for Android cross-compilation
|
||||
# This file configures the linkers for different Android architectures
|
||||
# Generated from config.toml.template by setup_rust_android.sh
|
||||
|
||||
[target.aarch64-linux-android]
|
||||
linker = "{{NDK_PATH}}/aarch64-linux-android{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
|
||||
|
||||
[target.armv7-linux-androideabi]
|
||||
linker = "{{NDK_PATH}}/armv7a-linux-androideabi{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
|
||||
|
||||
[target.i686-linux-android]
|
||||
linker = "{{NDK_PATH}}/i686-linux-android{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
|
||||
|
||||
[target.x86_64-linux-android]
|
||||
linker = "{{NDK_PATH}}/x86_64-linux-android{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
|
||||
|
||||
# Note: This file is auto-generated. Do not edit manually.
|
||||
# To regenerate, run: ./setup_rust_android.sh
|
||||
#
|
||||
# Configuration details:
|
||||
# - API level: {{API_LEVEL}} (matching minSdkVersion from build.gradle)
|
||||
# - Platform: {{PLATFORM}}
|
||||
# - NDK version: {{NDK_VERSION}}
|
||||
# - Android SDK: {{ANDROID_SDK_PATH}}
|
||||
@@ -1,5 +1,11 @@
|
||||
/build
|
||||
# Rust build artifacts
|
||||
target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
# Auto-generated configuration (generated from template)
|
||||
.cargo/config.toml
|
||||
|
||||
# Android build artifacts
|
||||
.cxx/
|
||||
build/
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "rootoperations"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "rootoperations"
|
||||
crate-type = ["cdylib"]
|
||||
path = "src/main/rust/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
jni = "0.21"
|
||||
libc = "0.2"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.11"
|
||||
log = "0.4"
|
||||
@@ -1,10 +1,12 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'com.google.devtools.ksp'
|
||||
apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
|
||||
|
||||
android {
|
||||
namespace "com.amaze.filemanager.fileoperations"
|
||||
compileSdk libs.versions.compileSdk.get().toInteger()
|
||||
ndkVersion libs.versions.ndk.get()
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion libs.versions.minSdk.get().toInteger()
|
||||
@@ -14,12 +16,6 @@ android {
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cFlags '-std=c11'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -46,21 +42,40 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path file('src/main/c/CMakeLists.txt')
|
||||
}
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
}
|
||||
|
||||
// Rust configuration
|
||||
cargo {
|
||||
module = "./"
|
||||
libname = "rootoperations"
|
||||
targets = ["arm", "arm64", "x86", "x86_64"]
|
||||
profile = "release"
|
||||
|
||||
// Optional: Use cargo and rustc from the path
|
||||
rustcCommand = "rustc"
|
||||
cargoCommand = "cargo"
|
||||
pythonCommand = "python3"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
// Ensure Rust libraries are built before Android tasks
|
||||
afterEvaluate {
|
||||
android.libraryVariants.all { variant ->
|
||||
variant.outputs.all { output ->
|
||||
def buildTask = tasks.findByName("cargoBuild")
|
||||
if (buildTask) {
|
||||
tasks.getByName("merge${variant.name.capitalize()}JniLibFolders").dependsOn buildTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
modules {
|
||||
module("org.bouncycastle:bcprov-jdk15on") {
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
# C++ to Rust Migration Guide
|
||||
|
||||
This document outlines the migration from C++ to Rust in the `file_operations` module using Mozilla's Rust Android Gradle plugin.
|
||||
|
||||
## Overview
|
||||
|
||||
The migration replaces the C++ implementation in `src/main/c/rootoperations.c` with a Rust implementation while maintaining the same JNI interface for seamless integration with the existing Kotlin code.
|
||||
|
||||
## What Was Migrated
|
||||
|
||||
### Original C++ Code
|
||||
- **File**: `src/main/c/rootoperations.c`
|
||||
- **Function**: `Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory`
|
||||
- **Purpose**: Checks if a given path is a directory using the `stat()` system call
|
||||
- **Build System**: CMake
|
||||
|
||||
### New Rust Code
|
||||
- **File**: `src/lib.rs`
|
||||
- **Function**: Same JNI function signature
|
||||
- **Purpose**: Identical functionality using Rust's `std::fs::metadata()`
|
||||
- **Build System**: Cargo with Mozilla's Rust Android Gradle plugin
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Rust Installation**: Install Rust from https://rustup.rs/
|
||||
2. **Android NDK**: Install via Android Studio → Tools → SDK Manager → SDK Tools → NDK
|
||||
3. **Environment Variables**: Set `ANDROID_HOME` or `ANDROID_NDK_ROOT`
|
||||
4. **Rust-compatible IDE**: Before Android Studio gets first-class Rust support, consider using [Visual Studio Code](https://code.visualstudio.com) with Rust extensions for coding.
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Automated Setup (Recommended)
|
||||
|
||||
The project includes an automated setup script that detects your system configuration and generates the correct configuration files:
|
||||
|
||||
```bash
|
||||
cd file_operations
|
||||
./setup_rust_android.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
- **Auto-detect your operating system** (macOS, Linux, Windows)
|
||||
- **Find your Android SDK** path (from `ANDROID_HOME`, `ANDROID_SDK_ROOT`, or common locations)
|
||||
- **Detect the latest NDK version** installed
|
||||
- **Extract API level** from your `build.gradle`
|
||||
- **Install necessary Rust targets** for Android
|
||||
- **Generate `.cargo/config.toml`** with correct linker paths
|
||||
|
||||
### 2. Manual Setup (If Needed)
|
||||
|
||||
If the automated setup doesn't work, you can manually configure:
|
||||
|
||||
1. **Install Android Rust targets:**
|
||||
```bash
|
||||
rustup target add aarch64-linux-android
|
||||
rustup target add armv7-linux-androideabi
|
||||
rustup target add i686-linux-android
|
||||
rustup target add x86_64-linux-android
|
||||
```
|
||||
|
||||
2. **Create `.cargo/config.toml`** based on your system:
|
||||
- Copy `.cargo/config.toml.template` to `.cargo/config.toml`
|
||||
- Replace placeholders with your actual paths:
|
||||
- `{{NDK_PATH}}`: Path to your NDK toolchain binaries
|
||||
- `{{API_LEVEL}}`: Your minimum API level (e.g., 21)
|
||||
- `{{EXE_SUFFIX}}`: `.exe` for Windows, empty for Unix systems
|
||||
|
||||
### 3. System Requirements
|
||||
|
||||
**Android SDK/NDK:**
|
||||
- Android SDK installed (via Android Studio or command-line tools)
|
||||
- NDK installed (via SDK Manager)
|
||||
- Set `ANDROID_HOME` environment variable
|
||||
|
||||
**Supported Platforms:**
|
||||
- macOS (`darwin-x86_64`)
|
||||
- Linux (`linux-x86_64`)
|
||||
- Windows (`windows-x86_64`)
|
||||
|
||||
### 4. Build Configuration
|
||||
|
||||
The `build.gradle` file now includes:
|
||||
- Mozilla's Rust Android Gradle plugin
|
||||
- Cargo configuration for cross-compilation
|
||||
- Automatic building of Rust code during Android build
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
file_operations/
|
||||
├── src/
|
||||
│ └── main/
|
||||
│ └── rust/
|
||||
│ ├── lib.rs # Main library file (module organizer)
|
||||
│ └── rootoperations.rs # Root operations functions
|
||||
├── .cargo/
|
||||
│ ├── config.toml # Auto-generated Cargo configuration for Android
|
||||
│ └── config.toml.template # Template for generating config.toml
|
||||
├── Cargo.toml # Rust project configuration
|
||||
├── setup_rust_android.sh # Automated setup script
|
||||
├── build.gradle # Updated with Rust plugin
|
||||
└── RUST_MIGRATION.md # This file
|
||||
```
|
||||
|
||||
## Key Changes
|
||||
|
||||
### 1. Build System
|
||||
- **Before**: CMake with `externalNativeBuild`
|
||||
- **After**: Cargo with Mozilla's Rust Android Gradle plugin
|
||||
|
||||
### 2. Dependencies
|
||||
- **Added**: `jni`, `libc`, `android_logger`, `log` crates
|
||||
- **Removed**: CMake build configuration
|
||||
|
||||
### 3. Function Implementation
|
||||
- **Before**: C function using `stat()` system call
|
||||
- **After**: Rust function using `std::fs::metadata()`
|
||||
|
||||
### 4. Error Handling
|
||||
- **Before**: errno-based error handling
|
||||
- **After**: Rust's `Result` type with proper error handling
|
||||
|
||||
### 5. Modular Structure
|
||||
- **Before**: Single C file with all functions
|
||||
- **After**: Organized module structure:
|
||||
- `lib.rs`: Main library file that organizes and re-exports modules
|
||||
- `rootoperations.rs`: Root-specific operations (migrated from C)
|
||||
|
||||
## Testing
|
||||
|
||||
The Rust implementation includes unit tests:
|
||||
```bash
|
||||
cd file_operations
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
The Rust code is automatically built when you build the Android project:
|
||||
```bash
|
||||
./gradlew :file_operations:build
|
||||
```
|
||||
|
||||
## Advantages of the Migration
|
||||
|
||||
1. **Memory Safety**: Rust prevents memory leaks and buffer overflows
|
||||
2. **Better Error Handling**: Rust's type system catches errors at compile time
|
||||
3. **Cross-Platform**: Easier to port to other platforms
|
||||
4. **Modern Tooling**: Cargo package manager and ecosystem
|
||||
5. **Performance**: Comparable performance to C++ with better safety
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Configuration Issues
|
||||
First, try re-running the automated setup script:
|
||||
```bash
|
||||
cd file_operations
|
||||
./setup_rust_android.sh
|
||||
```
|
||||
|
||||
This will re-detect your system configuration and regenerate `.cargo/config.toml`.
|
||||
|
||||
### NDK Path Issues
|
||||
If the automated setup fails to find your NDK, ensure your Android SDK is properly configured:
|
||||
```bash
|
||||
export ANDROID_HOME=/path/to/android/sdk
|
||||
# The script will automatically find the NDK in $ANDROID_HOME/ndk/[version]
|
||||
```
|
||||
|
||||
### Linker Not Found
|
||||
If you see linker errors, verify your NDK installation:
|
||||
1. Check that NDK is installed via Android Studio → SDK Manager → SDK Tools → NDK
|
||||
2. The setup script will automatically use the latest NDK version found
|
||||
3. If needed, manually verify the generated `.cargo/config.toml` paths exist
|
||||
|
||||
### Build Errors
|
||||
Clean and rebuild:
|
||||
```bash
|
||||
cd file_operations
|
||||
cargo clean
|
||||
cd ..
|
||||
./gradlew clean
|
||||
./gradlew :file_operations:build
|
||||
```
|
||||
|
||||
### Cross-Platform Issues
|
||||
The setup script supports:
|
||||
- **macOS**: Automatically detects `darwin-x86_64` platform
|
||||
- **Linux**: Automatically detects `linux-x86_64` platform
|
||||
- **Windows**: Automatically detects `windows-x86_64` platform and adds `.exe` suffix
|
||||
|
||||
If you encounter platform-specific issues, check the generated linker paths in `.cargo/config.toml`.
|
||||
|
||||
## Extending the Codebase
|
||||
|
||||
The modular structure makes it easy to add new functionality:
|
||||
|
||||
### Adding a New Module
|
||||
|
||||
1. **Create a new `.rs` file** in `src/main/rust/` (e.g., `network_ops.rs`)
|
||||
2. **Add the module declaration** in `lib.rs`:
|
||||
```rust
|
||||
pub mod network_ops;
|
||||
```
|
||||
3. **Re-export functions** in `lib.rs` for easy access:
|
||||
```rust
|
||||
pub use network_ops::{
|
||||
download_file,
|
||||
upload_file,
|
||||
};
|
||||
```
|
||||
|
||||
### Adding JNI Functions
|
||||
|
||||
To add new JNI functions that can be called from Kotlin:
|
||||
|
||||
1. **Add the function** in the appropriate module:
|
||||
```rust
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_newFunction(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
param: JString,
|
||||
) -> jboolean {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add the corresponding method** in `NativeOperations.kt`:
|
||||
```kotlin
|
||||
@JvmStatic
|
||||
external fun newFunction(param: String?): Boolean
|
||||
```
|
||||
|
||||
### Example: Adding Compression Operations
|
||||
|
||||
```rust
|
||||
// In src/main/rust/compression_ops.rs
|
||||
pub mod compression_ops {
|
||||
use jni::objects::{JClass, JString};
|
||||
use jni::JNIEnv;
|
||||
use jni::sys::jboolean;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_compressFile(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
source: JString,
|
||||
destination: JString,
|
||||
) -> jboolean {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Add More Functions**: Migrate additional C++ functions to Rust
|
||||
2. **Async Operations**: Use Rust's async/await for non-blocking operations
|
||||
3. **Better Logging**: Enhanced logging with structured logs
|
||||
4. **Performance Monitoring**: Add metrics collection
|
||||
5. **Cross-Platform Support**: Extend to other platforms (iOS, Windows, etc.)
|
||||
6. **Error Types**: Create custom error types for better error handling
|
||||
7. **Benchmarking**: Add performance benchmarks for critical operations
|
||||
|
||||
## Resources
|
||||
|
||||
- [Mozilla Rust Android Gradle Plugin](https://github.com/mozilla/rust-android-gradle)
|
||||
- [JNI for Rust](https://docs.rs/jni/latest/jni/)
|
||||
- [Android NDK Documentation](https://developer.android.com/ndk)
|
||||
- [Rust Cross-Compilation](https://rust-lang.github.io/rustup/cross-compilation.html)
|
||||
|
||||
## Support
|
||||
|
||||
For questions or issues with the migration, please refer to:
|
||||
- [Mozilla Rust Android Gradle Issues](https://github.com/mozilla/rust-android-gradle/issues)
|
||||
- [Rust Android Development](https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html)
|
||||
Executable
+182
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Setup script for Rust Android development
|
||||
# This script:
|
||||
# 1. Installs necessary Rust targets for Android
|
||||
# 2. Auto-detects system configuration
|
||||
# 3. Generates .cargo/config.toml with correct linker paths
|
||||
|
||||
set -e
|
||||
|
||||
echo "🦀 Setting up Rust for Android development..."
|
||||
|
||||
# Function to detect operating system
|
||||
detect_os() {
|
||||
case "$(uname -s)" in
|
||||
Darwin*) echo "darwin-x86_64" ;;
|
||||
Linux*) echo "linux-x86_64" ;;
|
||||
CYGWIN*|MINGW*|MSYS*) echo "windows-x86_64" ;;
|
||||
*) echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to find Android SDK path
|
||||
find_android_sdk() {
|
||||
if [ -n "$ANDROID_HOME" ]; then
|
||||
echo "$ANDROID_HOME"
|
||||
elif [ -n "$ANDROID_SDK_ROOT" ]; then
|
||||
echo "$ANDROID_SDK_ROOT"
|
||||
elif [ -d "$HOME/Android/Sdk" ]; then
|
||||
echo "$HOME/Android/Sdk"
|
||||
elif [ -d "$HOME/Library/Android/sdk" ]; then
|
||||
echo "$HOME/Library/Android/sdk"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to find the latest NDK version
|
||||
find_ndk_version() {
|
||||
local sdk_path="$1"
|
||||
local ndk_dir="$sdk_path/ndk"
|
||||
|
||||
if [ -d "$ndk_dir" ]; then
|
||||
# Find the latest version (highest version number)
|
||||
ls -1 "$ndk_dir" | sort -V | tail -n 1
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to extract API level from build.gradle and version catalog
|
||||
extract_api_level() {
|
||||
local build_gradle="build.gradle"
|
||||
local version_catalog="../gradle/libs.versions.toml"
|
||||
|
||||
# First, check if build.gradle uses version catalog
|
||||
if [ -f "$build_gradle" ] && grep -q "libs.versions.minSdk" "$build_gradle"; then
|
||||
# Extract from version catalog
|
||||
if [ -f "$version_catalog" ]; then
|
||||
grep "^minSdk" "$version_catalog" | cut -d '"' -f 2
|
||||
else
|
||||
echo "21" # Default fallback
|
||||
fi
|
||||
elif [ -f "$build_gradle" ]; then
|
||||
# Look for direct minSdkVersion in build.gradle
|
||||
grep -E "minSdkVersion|minSdk" "$build_gradle" | head -n 1 | sed 's/.*[[:space:]]\([0-9]\+\).*/\1/'
|
||||
else
|
||||
echo "21" # Default fallback
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect system configuration
|
||||
OS_PLATFORM=$(detect_os)
|
||||
echo "📱 Detected OS: $OS_PLATFORM"
|
||||
|
||||
if [ "$OS_PLATFORM" = "unknown" ]; then
|
||||
echo "❌ Unsupported operating system. Please configure manually."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find Android SDK
|
||||
ANDROID_SDK_PATH=$(find_android_sdk)
|
||||
if [ -z "$ANDROID_SDK_PATH" ]; then
|
||||
echo "❌ Android SDK not found. Please set ANDROID_HOME or ANDROID_SDK_ROOT environment variable."
|
||||
echo " Or install Android SDK to ~/Android/Sdk (Linux) or ~/Library/Android/sdk (macOS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📁 Found Android SDK: $ANDROID_SDK_PATH"
|
||||
|
||||
# Find NDK version
|
||||
NDK_VERSION=$(find_ndk_version "$ANDROID_SDK_PATH")
|
||||
if [ -z "$NDK_VERSION" ]; then
|
||||
echo "❌ NDK not found in $ANDROID_SDK_PATH/ndk"
|
||||
echo " Please install NDK through Android Studio SDK Manager"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔨 Found NDK version: $NDK_VERSION"
|
||||
|
||||
# Extract API level
|
||||
API_LEVEL=$(extract_api_level)
|
||||
echo "📋 Using API level: $API_LEVEL"
|
||||
|
||||
# Set executable suffix for Windows
|
||||
EXE_SUFFIX=""
|
||||
if [ "$OS_PLATFORM" = "windows-x86_64" ]; then
|
||||
EXE_SUFFIX=".exe"
|
||||
fi
|
||||
|
||||
# Construct NDK path
|
||||
NDK_PATH="$ANDROID_SDK_PATH/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/$OS_PLATFORM/bin"
|
||||
|
||||
# Verify NDK path exists
|
||||
if [ ! -d "$NDK_PATH" ]; then
|
||||
echo "❌ NDK toolchain not found at: $NDK_PATH"
|
||||
echo " Please check your NDK installation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🛠️ NDK toolchain path: $NDK_PATH"
|
||||
|
||||
# Install Rust targets for Android
|
||||
echo "📦 Installing Rust targets for Android..."
|
||||
rustup target add aarch64-linux-android
|
||||
rustup target add armv7-linux-androideabi
|
||||
rustup target add i686-linux-android
|
||||
rustup target add x86_64-linux-android
|
||||
|
||||
# Create .cargo directory if it doesn't exist
|
||||
mkdir -p .cargo
|
||||
|
||||
# Generate config.toml from template
|
||||
echo "⚙️ Generating .cargo/config.toml..."
|
||||
if [ ! -f ".cargo/config.toml.template" ]; then
|
||||
echo "❌ Template file .cargo/config.toml.template not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use sed to replace placeholders in template
|
||||
sed -e "s|{{NDK_PATH}}|$NDK_PATH|g" \
|
||||
-e "s|{{API_LEVEL}}|$API_LEVEL|g" \
|
||||
-e "s|{{EXE_SUFFIX}}|$EXE_SUFFIX|g" \
|
||||
-e "s|{{PLATFORM}}|$OS_PLATFORM|g" \
|
||||
-e "s|{{NDK_VERSION}}|$NDK_VERSION|g" \
|
||||
-e "s|{{ANDROID_SDK_PATH}}|$ANDROID_SDK_PATH|g" \
|
||||
.cargo/config.toml.template > .cargo/config.toml
|
||||
|
||||
echo "✅ Successfully generated .cargo/config.toml"
|
||||
|
||||
# Verify generated linkers exist
|
||||
echo "🔍 Verifying linkers..."
|
||||
LINKERS=(
|
||||
"aarch64-linux-android${API_LEVEL}-clang${EXE_SUFFIX}"
|
||||
"armv7a-linux-androideabi${API_LEVEL}-clang${EXE_SUFFIX}"
|
||||
"i686-linux-android${API_LEVEL}-clang${EXE_SUFFIX}"
|
||||
"x86_64-linux-android${API_LEVEL}-clang${EXE_SUFFIX}"
|
||||
)
|
||||
|
||||
for linker in "${LINKERS[@]}"; do
|
||||
if [ -f "$NDK_PATH/$linker" ]; then
|
||||
echo " ✅ $linker"
|
||||
else
|
||||
echo " ❌ $linker (not found)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "🎉 Rust Android setup complete!"
|
||||
echo ""
|
||||
echo "📋 Configuration summary:"
|
||||
echo " - OS Platform: $OS_PLATFORM"
|
||||
echo " - Android SDK: $ANDROID_SDK_PATH"
|
||||
echo " - NDK Version: $NDK_VERSION"
|
||||
echo " - API Level: $API_LEVEL"
|
||||
echo " - Config file: .cargo/config.toml"
|
||||
echo ""
|
||||
echo "🚀 You can now build for Android using:"
|
||||
echo " cargo build --target aarch64-linux-android"
|
||||
echo " cargo build --target armv7-linux-androideabi"
|
||||
echo " cargo build --target i686-linux-android"
|
||||
echo " cargo build --target x86_64-linux-android"
|
||||
@@ -1,5 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.amaze.filemanager.fileoperations">
|
||||
|
||||
/
|
||||
</manifest>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.amaze.filemanager.fileoperations" />
|
||||
@@ -1,13 +0,0 @@
|
||||
# For more information about using CMake with Android Studio, read the
|
||||
# documentation: https://d.android.com/studio/projects/add-native-code.html
|
||||
|
||||
cmake_minimum_required(VERSION 3.10.2)
|
||||
|
||||
project("rootoperations")
|
||||
|
||||
add_library(rootoperations SHARED rootoperations.c)
|
||||
|
||||
find_library(log-lib log)
|
||||
|
||||
target_link_libraries(rootoperations ${log-lib})
|
||||
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "-Wl,-z,max-page-size=16384")
|
||||
@@ -1,29 +0,0 @@
|
||||
#include <jni.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory(
|
||||
JNIEnv * env,
|
||||
jobject thiz,
|
||||
jstring path
|
||||
) {
|
||||
struct stat path_stat;
|
||||
const char * cPath = (*env)->GetStringUTFChars(env, path, NULL);
|
||||
int returnCode = stat(cPath, &path_stat);
|
||||
(*env)->ReleaseStringUTFChars(env, path, cPath);
|
||||
|
||||
if(returnCode == -1) {
|
||||
switch (errno) {
|
||||
case ELOOP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//This follows links
|
||||
return S_ISDIR(path_stat.st_mode);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
# Rust File Operations Module
|
||||
|
||||
This directory contains the Rust implementation of file operations for the Amaze File Manager, which
|
||||
was migrated from C/C++ to Rust for more modern, maintainable more safe code, with Rust's inherent
|
||||
type-safety and memory-safety features.
|
||||
|
||||
Please refer to [RUST_MIGRATION.md](../doc/RUST_MIGRATION.md) for detailed information
|
||||
about the migration process, key changes, and advantages of using Rust.
|
||||
|
||||
## Module Structure
|
||||
|
||||
The code is organized into separate modules for better maintainability and extensibility:
|
||||
|
||||
### `lib.rs` - Main Library File
|
||||
- **Purpose**: Organizes and re-exports functionality from various modules
|
||||
- **Content**: Module declarations and re-exports for easy access
|
||||
- **Note**: This is the entry point for the Rust library
|
||||
|
||||
### `rootoperations.rs` - Root Operations
|
||||
- **Purpose**: Contains root-specific file operations (migrated from C++)
|
||||
- **Functions**:
|
||||
- `Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory` - JNI function for directory checking
|
||||
- `check_is_directory` - Helper function to check if a path is a directory
|
||||
- `check_path_exists` - Helper function to check if a path exists
|
||||
- `check_is_file` - Helper function to check if a path is a file
|
||||
- `get_file_size` - Helper function to get file size
|
||||
|
||||
## Usage
|
||||
|
||||
### From Kotlin/Java
|
||||
The JNI functions are automatically exported and can be called from Kotlin:
|
||||
|
||||
```kotlin
|
||||
// In NativeOperations.kt
|
||||
@JvmStatic
|
||||
external fun isDirectory(path: String?): Boolean
|
||||
|
||||
// Usage
|
||||
val isDir = NativeOperations.isDirectory("/some/path")
|
||||
```
|
||||
|
||||
### From Rust (Internal)
|
||||
Functions can be called directly within the Rust codebase:
|
||||
|
||||
```rust
|
||||
use crate::rootoperations::check_is_directory;
|
||||
|
||||
let is_dir = check_is_directory("/some/path")?;
|
||||
```
|
||||
|
||||
### Through Re-exports
|
||||
Functions can be accessed through the main library:
|
||||
|
||||
```rust
|
||||
use crate::check_is_directory;
|
||||
|
||||
let is_dir = check_is_directory("/some/path")?;
|
||||
```
|
||||
|
||||
## Adding New Functionality
|
||||
|
||||
### 1. Create a New Module
|
||||
Create a new `.rs` file (e.g., `network_ops.rs`):
|
||||
|
||||
```rust
|
||||
// network_ops.rs
|
||||
pub fn download_file(url: &str, destination: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Implementation
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add Module Declaration
|
||||
In `lib.rs`, add the module declaration:
|
||||
|
||||
```rust
|
||||
pub mod network_ops;
|
||||
```
|
||||
|
||||
### 3. Re-export Functions (Optional)
|
||||
In `lib.rs`, add re-exports for easy access:
|
||||
|
||||
```rust
|
||||
pub use network_ops::{
|
||||
download_file,
|
||||
upload_file,
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Add JNI Functions (If Needed)
|
||||
If you need to call the function from Kotlin:
|
||||
|
||||
```rust
|
||||
// In network_ops.rs
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_downloadFile(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
url: JString,
|
||||
destination: JString,
|
||||
) -> jboolean {
|
||||
// Convert JString to Rust String
|
||||
let url_str: String = env.get_string(url).unwrap().into();
|
||||
let dest_str: String = env.get_string(destination).unwrap().into();
|
||||
|
||||
// Call the actual function
|
||||
match download_file(&url_str, &dest_str) {
|
||||
Ok(_) => 1, // true
|
||||
Err(_) => 0, // false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then add the corresponding method in `NativeOperations.kt`:
|
||||
|
||||
```kotlin
|
||||
@JvmStatic
|
||||
external fun downloadFile(url: String?, destination: String?): Boolean
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Each module includes its own tests. Run tests with:
|
||||
|
||||
```bash
|
||||
cd file_operations
|
||||
cargo test
|
||||
```
|
||||
|
||||
To test a specific module:
|
||||
|
||||
```bash
|
||||
cargo test rootoperations::tests
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Separation of Concerns**: Keep related functions in the same module
|
||||
2. **Error Handling**: Use `Result<T, E>` for functions that can fail
|
||||
3. **Documentation**: Add doc comments to all public functions
|
||||
4. **Testing**: Include unit tests for all functions
|
||||
5. **JNI Safety**: Always handle JNI string conversion errors
|
||||
6. **Logging**: Use the `log` crate for debugging information
|
||||
|
||||
## Example: Complete Function Implementation
|
||||
|
||||
```rust
|
||||
// In your_module.rs
|
||||
use jni::objects::{JClass, JString};
|
||||
use jni::JNIEnv;
|
||||
use jni::sys::jboolean;
|
||||
|
||||
/// Check if a file is executable
|
||||
pub fn is_executable(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let path = std::path::Path::new(path);
|
||||
let metadata = path.metadata()?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
Ok(metadata.permissions().mode() & 0o111 != 0)
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix systems, check if it's a file with executable extension
|
||||
Ok(path.extension()
|
||||
.and_then(|ext| ext.to_str())
|
||||
.map(|ext| matches!(ext.to_lowercase().as_str(), "exe" | "bat" | "cmd"))
|
||||
.unwrap_or(false))
|
||||
}
|
||||
}
|
||||
|
||||
/// JNI function to check if a file is executable
|
||||
#[no_mangle]
|
||||
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isExecutable(
|
||||
env: JNIEnv,
|
||||
_class: JClass,
|
||||
path: JString,
|
||||
) -> jboolean {
|
||||
let path_str = match env.get_string(path) {
|
||||
Ok(java_str) => java_str,
|
||||
Err(_) => {
|
||||
log::error!("Failed to get string from JString");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
let path_string: String = path_str.into();
|
||||
|
||||
match is_executable(&path_string) {
|
||||
Ok(is_exec) => if is_exec { 1 } else { 0 },
|
||||
Err(e) => {
|
||||
log::error!("Error checking if file is executable: {}", e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_executable() {
|
||||
// Test with a known executable (on Unix systems)
|
||||
#[cfg(unix)]
|
||||
assert!(is_executable("/bin/ls").unwrap_or(false));
|
||||
|
||||
// Test with a non-executable file
|
||||
assert!(!is_executable("/etc/passwd").unwrap_or(true));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This structure makes the codebase highly maintainable and easy to extend with new functionality while keeping everything organized and well-tested.
|
||||
@@ -0,0 +1,29 @@
|
||||
// Main library file for the Rust file operations module
|
||||
// This file organizes and re-exports functionality from various modules
|
||||
|
||||
// Module declarations
|
||||
pub mod rootoperations;
|
||||
|
||||
// Re-export commonly used functions for easier access
|
||||
pub use rootoperations::{
|
||||
check_is_directory,
|
||||
check_path_exists,
|
||||
check_is_file,
|
||||
get_file_size,
|
||||
};
|
||||
|
||||
// The JNI functions are automatically exported due to #[no_mangle] in their respective modules
|
||||
// No need to re-export them here as they're called directly by the JVM
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_module_integration() {
|
||||
// Test that we can access rootoperations functions through re-exports
|
||||
assert!(check_path_exists("/").unwrap_or(false));
|
||||
assert!(check_is_directory("/").unwrap_or(false));
|
||||
assert!(!check_is_file("/").unwrap_or(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
use jni::objects::{JClass, JString};
|
||||
use jni::JNIEnv;
|
||||
use jni::sys::jboolean;
|
||||
use std::path::Path;
|
||||
use std::sync::Once;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use android_logger::Config;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use log;
|
||||
|
||||
// Per Copilot review suggested, use Once::new to make Android logging init only once
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
/// Initialize Android logging (optional, but useful for debugging)
|
||||
#[cfg(target_os = "android")]
|
||||
fn init_logging() {
|
||||
INIT.call_once(|| {
|
||||
android_logger::init_once(
|
||||
Config::default()
|
||||
.with_min_level(log::Level::Debug)
|
||||
.with_tag("RustRootOperations")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Log error message (Android only)
|
||||
#[cfg(target_os = "android")]
|
||||
fn log_error(msg: &str) {
|
||||
log::error!("{}", msg);
|
||||
}
|
||||
|
||||
/// Log error message (non-Android platforms)
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn log_error(msg: &str) {
|
||||
eprintln!("ERROR: {}", msg);
|
||||
}
|
||||
|
||||
/// JNI function to check if a path is a directory
|
||||
/// This replaces the C function: Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory(
|
||||
mut env: JNIEnv,
|
||||
_class: JClass,
|
||||
path: JString,
|
||||
) -> jboolean {
|
||||
#[cfg(target_os = "android")]
|
||||
init_logging();
|
||||
|
||||
// Convert the JString to a Rust string
|
||||
let path_str = match env.get_string(&path) {
|
||||
Ok(java_str) => java_str,
|
||||
Err(_) => {
|
||||
log_error("Failed to get string from JString");
|
||||
return 0; // false
|
||||
}
|
||||
};
|
||||
|
||||
let path_string: String = path_str.into();
|
||||
|
||||
// Check if the path is a directory using Rust's standard library
|
||||
match check_is_directory(&path_string) {
|
||||
Ok(is_dir) => {
|
||||
if is_dir { 1 } else { 0 } // true : false
|
||||
}
|
||||
Err(e) => {
|
||||
log_error(&format!("Error checking if path is directory: {}", e));
|
||||
0 // false on error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to check if a path is a directory
|
||||
/// This replicates the logic from the original C code
|
||||
pub fn check_is_directory(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let path = Path::new(path);
|
||||
|
||||
match path.metadata() {
|
||||
Ok(metadata) => {
|
||||
Ok(metadata.is_dir())
|
||||
}
|
||||
Err(e) => {
|
||||
use std::io::ErrorKind;
|
||||
match e.kind() {
|
||||
ErrorKind::NotFound => Ok(false),
|
||||
ErrorKind::PermissionDenied => Ok(false),
|
||||
_ => {
|
||||
// For other errors, we'll return false similar to the C code
|
||||
// The original C code had special handling for ELOOP (too many symbolic links)
|
||||
// which would return true, but Rust's std::fs handles this differently
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to check if a path exists
|
||||
/// This can be used for future extensions
|
||||
pub fn check_path_exists(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let path = Path::new(path);
|
||||
Ok(path.exists())
|
||||
}
|
||||
|
||||
/// Helper function to check if a path is a file
|
||||
/// This can be used for future extensions
|
||||
pub fn check_is_file(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let path = Path::new(path);
|
||||
|
||||
match path.metadata() {
|
||||
Ok(metadata) => {
|
||||
Ok(metadata.is_file())
|
||||
}
|
||||
Err(e) => {
|
||||
use std::io::ErrorKind;
|
||||
match e.kind() {
|
||||
ErrorKind::NotFound => Ok(false),
|
||||
ErrorKind::PermissionDenied => Ok(false),
|
||||
_ => Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to get file size
|
||||
/// This can be used for future extensions
|
||||
pub fn get_file_size(path: &str) -> Result<u64, Box<dyn std::error::Error>> {
|
||||
let path = Path::new(path);
|
||||
|
||||
match path.metadata() {
|
||||
Ok(metadata) => {
|
||||
Ok(metadata.len())
|
||||
}
|
||||
Err(e) => {
|
||||
Err(Box::new(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_check_is_directory() {
|
||||
// Test with a known directory
|
||||
assert!(check_is_directory("/tmp").unwrap_or(false));
|
||||
|
||||
// Test with a non-existent path
|
||||
assert!(!check_is_directory("/this/path/does/not/exist").unwrap_or(true));
|
||||
|
||||
// Test with root directory
|
||||
assert!(check_is_directory("/").unwrap_or(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_path_exists() {
|
||||
// Test with root directory (should exist)
|
||||
assert!(check_path_exists("/").unwrap_or(false));
|
||||
|
||||
// Test with non-existent path
|
||||
assert!(!check_path_exists("/this/path/does/not/exist").unwrap_or(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_is_file() {
|
||||
// Test with a directory (should return false)
|
||||
assert!(!check_is_file("/").unwrap_or(true));
|
||||
|
||||
// Test with non-existent path
|
||||
assert!(!check_is_file("/this/path/does/not/exist").unwrap_or(true));
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ compileSdk = "34"
|
||||
minSdk = "21"
|
||||
targetSdk = "35"
|
||||
kotlin = "1.9.25"
|
||||
ndk = "28.2.13676358"
|
||||
ndk = "28.2.13676358" #r28c
|
||||
|
||||
jacocoAndroid = "0.2.1"
|
||||
gradle = "8.5.2"
|
||||
@@ -35,7 +35,7 @@ androidXTestRunner = "1.6.2"
|
||||
androidXTestExt = "1.2.1"
|
||||
androidXArchCoreTest = "2.2.0"
|
||||
androidXMultidex = "2.0.1"
|
||||
autoService = "1.0-rc4"
|
||||
autoService = "1.1.1"
|
||||
kotlinxCoroutineTest = "1.7.3"
|
||||
kotlinStdlibJdk8 = "1.9.20"
|
||||
uiAutomator = "2.3.0"
|
||||
@@ -73,11 +73,13 @@ mpAndroidChart = "v3.0.2"
|
||||
concurrentTrees = "2.6.1"
|
||||
aboutLibraries = "6.1.1"
|
||||
amazeTrashBin = "1.0.10"
|
||||
rustAndroidGradle = "0.9.6"
|
||||
|
||||
[libraries]
|
||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
|
||||
jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacocoAndroid" }
|
||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
rust-android-gradle = { module = "org.mozilla.rust-android-gradle.rust-android:org.mozilla.rust-android-gradle.rust-android.gradle.plugin", version.ref = "rustAndroidGradle" }
|
||||
|
||||
androidX-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidXAppCompat" }
|
||||
androidX-constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidXConstraintLayout" }
|
||||
|
||||
Reference in New Issue
Block a user