218 lines
7.1 KiB
TypeScript
218 lines
7.1 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core'
|
|
import { BaseDirectory, appLocalDataDir } from '@tauri-apps/api/path'
|
|
import { tempDir } from '@tauri-apps/api/path'
|
|
import { message } from '@tauri-apps/plugin-dialog'
|
|
import { copyFile, exists, mkdir, remove } from '@tauri-apps/plugin-fs'
|
|
import { writeFile } from '@tauri-apps/plugin-fs'
|
|
import { fetch } from '@tauri-apps/plugin-http'
|
|
import { platform } from '@tauri-apps/plugin-os'
|
|
import { Command } from '@tauri-apps/plugin-shell'
|
|
|
|
export async function initRclone() {
|
|
const system = await isSystemRcloneInstalled()
|
|
let internal = await isInternalRcloneInstalled()
|
|
|
|
// rclone not available, let's download it
|
|
if (!system && !internal) {
|
|
await provisionRclone()
|
|
internal = true
|
|
}
|
|
|
|
return {
|
|
system: system
|
|
? async (args: string[]) => {
|
|
console.log('running system rclone')
|
|
return Command.create('rclone-system', args)
|
|
}
|
|
: null,
|
|
internal: internal
|
|
? async (args: string[]) => {
|
|
console.log('running internal rclone')
|
|
return Command.create('rclone-internal', args)
|
|
}
|
|
: null,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if rclone is installed and accessible from the system PATH
|
|
* @returns {Promise<boolean>} True if rclone is installed and working
|
|
*/
|
|
export async function isSystemRcloneInstalled() {
|
|
try {
|
|
const output = await Command.create('rclone-system').execute()
|
|
// console.log('[isSystemRcloneInstalled] output', output)
|
|
return (
|
|
output.stdout.includes('Available commands') ||
|
|
output.stderr.includes('Available commands')
|
|
)
|
|
} catch (_) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if rclone is downloaded by the application in the app's local data directory
|
|
* @returns {Promise<boolean>} True if downloaded rclone is present and working
|
|
*/
|
|
export async function isInternalRcloneInstalled() {
|
|
try {
|
|
const output = await Command.create('rclone-internal').execute()
|
|
// console.log('[isInternalRcloneInstalled] output', output)
|
|
return (
|
|
output.stdout.includes('Available commands') ||
|
|
output.stderr.includes('Available commands')
|
|
)
|
|
} catch (_) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Downloads and provisions the latest version of rclone for the current platform
|
|
* @throws {Error} If architecture detection fails or installation is unsuccessful
|
|
* @returns {Promise<void>}
|
|
*/
|
|
export async function provisionRclone() {
|
|
const currentVersionString = await fetch('https://downloads.rclone.org/version.txt').then(
|
|
(res) => res.text()
|
|
)
|
|
console.log('currentVersionString', currentVersionString)
|
|
|
|
const currentVersion = currentVersionString.split('v')?.[1]?.trim()
|
|
|
|
if (!currentVersion) {
|
|
console.error('Failed to get latest version')
|
|
return
|
|
}
|
|
console.log('currentVersion', currentVersion)
|
|
|
|
const currentPlatform = platform()
|
|
console.log('currentPlatform', currentPlatform)
|
|
|
|
const currentOs = currentPlatform === 'macos' ? 'osx' : currentPlatform
|
|
console.log('currentOs', currentOs)
|
|
|
|
let tempDirPath = await tempDir()
|
|
if (tempDirPath.endsWith('/') || tempDirPath.endsWith('\\')) {
|
|
tempDirPath = tempDirPath.slice(0, -1)
|
|
}
|
|
console.log('tempDirPath', tempDirPath)
|
|
|
|
const arch = (await invoke('get_arch')) as 'arm64' | 'amd64' | '386' | 'unknown'
|
|
console.log('arch', arch)
|
|
|
|
if (arch === 'unknown') {
|
|
throw new Error('Failed to get architecture, please try again later.')
|
|
}
|
|
|
|
const downloadUrl = `https://downloads.rclone.org/v${currentVersion}/rclone-v${currentVersion}-${currentOs}-${arch}.zip`
|
|
console.log('downloadUrl', downloadUrl)
|
|
|
|
const downloadedFile = await fetch(downloadUrl).then((res) => res.arrayBuffer())
|
|
console.log('downloadedFile')
|
|
|
|
let tempDirExists
|
|
try {
|
|
tempDirExists = await exists('rclone', {
|
|
baseDir: BaseDirectory.Temp,
|
|
})
|
|
console.log('tempDirExists', tempDirExists)
|
|
} catch (error) {
|
|
console.error('Failed to check if rclone temp dir exists', error)
|
|
}
|
|
|
|
if (tempDirExists) {
|
|
try {
|
|
await remove('rclone', {
|
|
recursive: true,
|
|
baseDir: BaseDirectory.Temp,
|
|
})
|
|
console.log('removed rclone temp dir')
|
|
} catch (error) {
|
|
console.error('Failed to remove rclone temp dir', error)
|
|
await message('Failed to provision rclone.')
|
|
return
|
|
}
|
|
}
|
|
|
|
try {
|
|
await mkdir('rclone', {
|
|
baseDir: BaseDirectory.Temp,
|
|
})
|
|
console.log('created rclone temp dir')
|
|
} catch (error) {
|
|
console.error('Failed to create rclone temp dir', error)
|
|
await message('Failed to provision rclone.')
|
|
return
|
|
}
|
|
|
|
const zipPath = `${tempDirPath}/rclone/rclone-v${currentVersion}-${currentOs}-${arch}.zip`
|
|
console.log('zipPath', zipPath)
|
|
|
|
try {
|
|
await writeFile(zipPath, new Uint8Array(downloadedFile))
|
|
console.log('wrote zip file')
|
|
} catch (error) {
|
|
console.error('Failed to write zip file', error)
|
|
await message('Failed to provision rclone.')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await invoke('unzip_file', {
|
|
zipPath,
|
|
outputFolder: `${tempDirPath}/rclone/rclone-ui`,
|
|
})
|
|
console.log('Successfully unzipped file')
|
|
} catch (error) {
|
|
console.error('Failed to unzip file', error)
|
|
await message('Failed to provision rclone.')
|
|
return
|
|
}
|
|
|
|
const unarchivedPath = `${tempDirPath}/rclone/rclone-ui/rclone-v${currentVersion}-${currentOs}-${arch}`
|
|
console.log('unarchivedPath', unarchivedPath)
|
|
|
|
const binaryName = currentPlatform === 'windows' ? 'rclone.exe' : 'rclone'
|
|
|
|
// "/" here looks to be working on windows
|
|
const rcloneBinaryPath = unarchivedPath + '/' + binaryName
|
|
console.log('rcloneBinaryPath', rcloneBinaryPath)
|
|
|
|
try {
|
|
const binaryExists = await exists(rcloneBinaryPath)
|
|
console.log('rcloneBinaryPathExists', binaryExists)
|
|
if (!binaryExists) {
|
|
throw new Error('Could not find rclone binary in zip')
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to check if rclone binary exists', error)
|
|
throw new Error('Could not find rclone binary in zip')
|
|
}
|
|
|
|
const appLocalDataDirPath = await appLocalDataDir()
|
|
console.log('appLocalDataDirPath', appLocalDataDirPath)
|
|
|
|
const appLocalDataDirPathExists = await exists(appLocalDataDirPath)
|
|
console.log('appLocalDataDirPathExists', appLocalDataDirPathExists)
|
|
|
|
if (!appLocalDataDirPathExists) {
|
|
await mkdir(appLocalDataDirPath, {
|
|
recursive: true,
|
|
})
|
|
console.log('appLocalDataDirPath created')
|
|
}
|
|
|
|
await copyFile(rcloneBinaryPath, `${appLocalDataDirPath}/${binaryName}`)
|
|
console.log('copied rclone binary')
|
|
|
|
const hasInstalled = await isInternalRcloneInstalled()
|
|
|
|
if (!hasInstalled) {
|
|
throw new Error('Failed to install rclone')
|
|
}
|
|
|
|
console.log('rclone has been installed')
|
|
}
|