refactor: streamline download path handling and remove unused output path logic
This commit is contained in:
@@ -231,6 +231,7 @@ class DownloadEngine extends EventEmitter {
|
||||
}
|
||||
|
||||
const createdAt = Date.now()
|
||||
const settings = settingsManager.getAll()
|
||||
|
||||
const item: DownloadItem = {
|
||||
id,
|
||||
@@ -247,7 +248,7 @@ class DownloadEngine extends EventEmitter {
|
||||
title: item.title,
|
||||
status: 'pending',
|
||||
downloadedAt: createdAt,
|
||||
outputPath: options.outputPath
|
||||
downloadPath: settings.downloadPath
|
||||
})
|
||||
}
|
||||
|
||||
@@ -255,7 +256,7 @@ class DownloadEngine extends EventEmitter {
|
||||
scopedLoggers.download.info('Starting download execution for ID:', id, 'URL:', options.url)
|
||||
const ytdlp = ytdlpManager.getInstance()
|
||||
const settings = settingsManager.getAll()
|
||||
const downloadPath = options.outputPath || settings.downloadPath
|
||||
const downloadPath = settings.downloadPath
|
||||
|
||||
// Set environment variables for proper encoding on Windows
|
||||
if (process.platform === 'win32') {
|
||||
@@ -408,39 +409,8 @@ class DownloadEngine extends EventEmitter {
|
||||
}
|
||||
)
|
||||
|
||||
// Handle yt-dlp events to capture output file path and format info
|
||||
let actualOutputPath: string | null = null
|
||||
|
||||
// Handle yt-dlp events to capture format info
|
||||
ytdlpProcess.on('ytDlpEvent', (eventType: string, eventData: string) => {
|
||||
// Look for download destination messages
|
||||
scopedLoggers.download.info('ytDlpEvent:', eventType, eventData)
|
||||
if (eventType === 'download' && eventData.includes('Destination:')) {
|
||||
const match = eventData.match(/Destination:\s*(.+)/)
|
||||
if (match?.[1]) {
|
||||
actualOutputPath = match[1].trim()
|
||||
}
|
||||
}
|
||||
|
||||
// Also look for other output path patterns
|
||||
if (
|
||||
eventType === 'download' &&
|
||||
(eventData.includes('has already been downloaded') ||
|
||||
eventData.includes('has already been downloaded'))
|
||||
) {
|
||||
const match = eventData.match(/\[download\]\s*(.+?)\s+has already been downloaded/)
|
||||
if (match?.[1]) {
|
||||
actualOutputPath = match[1].trim()
|
||||
}
|
||||
}
|
||||
|
||||
// Look for final output path in download messages
|
||||
if (eventType === 'download' && eventData.includes('[download] 100%')) {
|
||||
const match = eventData.match(/\[download\]\s*100%.*?of\s*(.+?)\s+at/)
|
||||
if (match?.[1]) {
|
||||
actualOutputPath = match[1].trim()
|
||||
}
|
||||
}
|
||||
|
||||
// Look for format selection messages
|
||||
if (eventType === 'info' && eventData.includes('format')) {
|
||||
// Extract format info from yt-dlp output
|
||||
@@ -484,31 +454,15 @@ class DownloadEngine extends EventEmitter {
|
||||
this.queue.downloadCompleted(id)
|
||||
|
||||
if (code === 0) {
|
||||
// Use actual output path from yt-dlp, or fallback to simple generated path
|
||||
let finalOutputPath: string
|
||||
if (actualOutputPath) {
|
||||
finalOutputPath = actualOutputPath
|
||||
scopedLoggers.download.info(
|
||||
'Using actual output path from yt-dlp for ID:',
|
||||
id,
|
||||
'Path:',
|
||||
finalOutputPath
|
||||
)
|
||||
} else {
|
||||
// Simple fallback: generate path based on video title and format
|
||||
const title = videoInfo?.title || 'Unknown'
|
||||
const sanitizedTitle = title.replace(/[<>:"/\\|?*]/g, '_').substring(0, 50)
|
||||
const extension =
|
||||
options.type === 'audio' ? options.extractFormat || 'mp3' : actualFormat || 'mp4'
|
||||
const fileName = `${sanitizedTitle}.${extension}`
|
||||
finalOutputPath = path.join(downloadPath, fileName)
|
||||
scopedLoggers.download.warn(
|
||||
'Using fallback output path for ID:',
|
||||
id,
|
||||
'Path:',
|
||||
finalOutputPath
|
||||
)
|
||||
}
|
||||
// Generate file path using downloadPath + title + ext
|
||||
const title = videoInfo?.title || 'Unknown'
|
||||
const sanitizedTitle = title.replace(/[<>:"/\\|?*]/g, '_').substring(0, 50)
|
||||
const extension =
|
||||
options.type === 'audio' ? options.extractFormat || 'mp3' : actualFormat || 'mp4'
|
||||
const fileName = `${sanitizedTitle}.${extension}`
|
||||
const finalOutputPath = path.join(downloadPath, fileName)
|
||||
|
||||
scopedLoggers.download.info('Generated file path for ID:', id, 'Path:', finalOutputPath)
|
||||
|
||||
let fileSize: number | undefined
|
||||
try {
|
||||
@@ -529,7 +483,6 @@ class DownloadEngine extends EventEmitter {
|
||||
|
||||
this.updateDownloadInfo(id, {
|
||||
status: 'completed',
|
||||
outputPath: finalOutputPath,
|
||||
completedAt: Date.now(),
|
||||
fileSize,
|
||||
format: actualFormat || undefined,
|
||||
@@ -538,7 +491,7 @@ class DownloadEngine extends EventEmitter {
|
||||
})
|
||||
scopedLoggers.download.info('Download completed successfully for ID:', id)
|
||||
this.emit('download-completed', id)
|
||||
this.addToHistory(id, options, 'completed', undefined, finalOutputPath)
|
||||
this.addToHistory(id, options, 'completed', undefined)
|
||||
} else {
|
||||
scopedLoggers.download.error(
|
||||
'Download failed with exit code for ID:',
|
||||
@@ -617,9 +570,6 @@ class DownloadEngine extends EventEmitter {
|
||||
if (updates.duration !== undefined) {
|
||||
historyUpdates.duration = updates.duration
|
||||
}
|
||||
if (updates.outputPath !== undefined) {
|
||||
historyUpdates.outputPath = updates.outputPath
|
||||
}
|
||||
if (updates.fileSize !== undefined) {
|
||||
historyUpdates.fileSize = updates.fileSize
|
||||
}
|
||||
@@ -669,20 +619,17 @@ class DownloadEngine extends EventEmitter {
|
||||
id: string,
|
||||
options: DownloadOptions,
|
||||
status: DownloadHistoryItem['status'],
|
||||
error?: string,
|
||||
actualOutputPath?: string
|
||||
error?: string
|
||||
): void {
|
||||
// Get the download item from the queue to get additional info
|
||||
const completedDownload = this.queue.getCompletedDownload(id)
|
||||
scopedLoggers.download.info('Completed download:', completedDownload)
|
||||
const completedAt = Date.now()
|
||||
const finalOutputPath = actualOutputPath || options.outputPath
|
||||
|
||||
this.upsertHistoryEntry(id, options, {
|
||||
title: completedDownload?.item.title || `Download ${id}`,
|
||||
thumbnail: completedDownload?.item.thumbnail,
|
||||
status,
|
||||
outputPath: finalOutputPath,
|
||||
completedAt,
|
||||
error,
|
||||
duration: completedDownload?.item.duration,
|
||||
@@ -711,7 +658,7 @@ class DownloadEngine extends EventEmitter {
|
||||
thumbnail: updates.thumbnail,
|
||||
type: options.type,
|
||||
status: updates.status || 'pending',
|
||||
outputPath: updates.outputPath,
|
||||
downloadPath: updates.downloadPath,
|
||||
fileSize: updates.fileSize,
|
||||
duration: updates.duration,
|
||||
downloadedAt: updates.downloadedAt ?? Date.now(),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import type { Dirent } from 'node:fs'
|
||||
import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
@@ -61,12 +60,6 @@ class FileSystemService extends IpcService {
|
||||
}
|
||||
|
||||
if (stats?.isDirectory()) {
|
||||
const candidate = await this.findLikelyFile(normalizedPath, normalizedPath)
|
||||
if (candidate) {
|
||||
shell.showItemInFolder(candidate)
|
||||
return true
|
||||
}
|
||||
|
||||
const result = await shell.openPath(normalizedPath)
|
||||
if (result) {
|
||||
console.error('Failed to open directory:', result)
|
||||
@@ -75,43 +68,25 @@ class FileSystemService extends IpcService {
|
||||
return true
|
||||
}
|
||||
|
||||
const fallbackDirectory = path.dirname(normalizedPath)
|
||||
const fallbackCandidate = await this.findLikelyFile(fallbackDirectory, normalizedPath)
|
||||
if (fallbackCandidate) {
|
||||
shell.showItemInFolder(fallbackCandidate)
|
||||
// If the exact path doesn't exist, try to open the parent directory
|
||||
const parentDirectory = path.dirname(normalizedPath)
|
||||
const parentStats = await fs.stat(parentDirectory).catch(() => null)
|
||||
|
||||
if (parentStats?.isDirectory()) {
|
||||
const result = await shell.openPath(parentDirectory)
|
||||
if (result) {
|
||||
console.error('Failed to open parent directory:', result)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const result = await shell.openPath(fallbackDirectory)
|
||||
if (!result) {
|
||||
return true
|
||||
}
|
||||
console.error('Failed to open directory:', result)
|
||||
console.error('File or directory does not exist:', normalizedPath)
|
||||
return false
|
||||
} catch (error) {
|
||||
try {
|
||||
const directory = path.dirname(path.normalize(this.sanitizePath(filePath)))
|
||||
const dirStats = await fs.stat(directory)
|
||||
if (dirStats.isDirectory()) {
|
||||
const fallbackCandidate = await this.findLikelyFile(directory, directory)
|
||||
if (fallbackCandidate) {
|
||||
shell.showItemInFolder(fallbackCandidate)
|
||||
return true
|
||||
}
|
||||
const result = await shell.openPath(directory)
|
||||
if (result) {
|
||||
console.error('Failed to open directory:', result)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (dirError) {
|
||||
console.error('Failed to open parent directory:', dirError)
|
||||
}
|
||||
console.error('Failed to open file location:', error)
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
@@ -156,54 +131,6 @@ class FileSystemService extends IpcService {
|
||||
}
|
||||
}
|
||||
|
||||
private async findLikelyFile(directory: string, expectedPath: string): Promise<string | null> {
|
||||
try {
|
||||
const dirStats = await fs.stat(directory)
|
||||
if (!dirStats.isDirectory()) {
|
||||
return null
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(directory, { withFileTypes: true })
|
||||
const files = entries.filter((entry: Dirent) => entry.isFile())
|
||||
if (files.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const expectedBase = path.basename(expectedPath).toLowerCase()
|
||||
const expectedName = path.parse(expectedPath).name.toLowerCase()
|
||||
|
||||
const exactMatch = files.find((entry) => entry.name.toLowerCase() === expectedBase)
|
||||
if (exactMatch) {
|
||||
return path.join(directory, exactMatch.name)
|
||||
}
|
||||
|
||||
if (expectedName) {
|
||||
const partialMatch = files.find((entry) => entry.name.toLowerCase().includes(expectedName))
|
||||
if (partialMatch) {
|
||||
return path.join(directory, partialMatch.name)
|
||||
}
|
||||
}
|
||||
|
||||
let latestMatch: { filePath: string; mtimeMs: number } | null = null
|
||||
for (const entry of files) {
|
||||
const candidatePath = path.join(directory, entry.name)
|
||||
try {
|
||||
const candidateStats = await fs.stat(candidatePath)
|
||||
if (!latestMatch || candidateStats.mtimeMs > latestMatch.mtimeMs) {
|
||||
latestMatch = { filePath: candidatePath, mtimeMs: candidateStats.mtimeMs }
|
||||
}
|
||||
} catch (statError) {
|
||||
console.error('Failed to stat candidate file:', statError)
|
||||
}
|
||||
}
|
||||
|
||||
return latestMatch?.filePath ?? null
|
||||
} catch (error) {
|
||||
console.error('Failed to search for matching file:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async openExternal(_context: IpcContext, url: string): Promise<boolean> {
|
||||
try {
|
||||
@@ -288,6 +215,58 @@ class FileSystemService extends IpcService {
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async deleteFile(_context: IpcContext, filePath: string): Promise<boolean> {
|
||||
try {
|
||||
if (!filePath) {
|
||||
return false
|
||||
}
|
||||
|
||||
const sanitizedPath = this.sanitizePath(filePath)
|
||||
const normalizedPath = path.normalize(sanitizedPath)
|
||||
|
||||
const stats = await fs.stat(normalizedPath).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code === 'ENOENT') {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
})
|
||||
|
||||
if (!stats) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (stats.isFile()) {
|
||||
await fs.unlink(normalizedPath).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
const entries = await fs.readdir(normalizedPath)
|
||||
if (entries.length === 0) {
|
||||
await fs.rmdir(normalizedPath).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Failed to delete file:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { FileSystemService }
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { type IpcContext, IpcMethod, IpcService } from 'electron-ipc-decorator'
|
||||
import type { DownloadHistoryItem } from '../../../shared/types'
|
||||
import { historyManager } from '../../lib/history-manager'
|
||||
import { settingsManager } from '../../settings'
|
||||
|
||||
class HistoryService extends IpcService {
|
||||
static readonly groupName = 'history'
|
||||
@@ -24,282 +21,10 @@ class HistoryService extends IpcService {
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
async removeHistoryItem(_context: IpcContext, id: string, outputPath?: string): Promise<boolean> {
|
||||
const record = historyManager.getHistoryById(id)
|
||||
|
||||
await this.deleteOutputResource(record, outputPath)
|
||||
|
||||
removeHistoryItem(_context: IpcContext, id: string): boolean {
|
||||
return historyManager.removeHistoryItem(id)
|
||||
}
|
||||
|
||||
private async deleteOutputResource(
|
||||
record: DownloadHistoryItem | undefined,
|
||||
fallbackOutputPath?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const candidatePaths = new Set<string>()
|
||||
if (record?.outputPath) {
|
||||
candidatePaths.add(record.outputPath)
|
||||
}
|
||||
if (fallbackOutputPath) {
|
||||
candidatePaths.add(fallbackOutputPath)
|
||||
}
|
||||
|
||||
for (const candidate of candidatePaths) {
|
||||
if (await this.tryDeletePath(candidate)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const directories = this.collectCandidateDirectories(candidatePaths)
|
||||
if (directories.length === 0) {
|
||||
const defaultDownloadPath = settingsManager.get('downloadPath')
|
||||
if (defaultDownloadPath) {
|
||||
directories.push(defaultDownloadPath)
|
||||
}
|
||||
}
|
||||
|
||||
const matchKeys = this.collectMatchKeys(record, candidatePaths)
|
||||
if (matchKeys.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const extensions = this.collectExtensions(candidatePaths, record)
|
||||
for (const directory of directories) {
|
||||
const matchedPath = await this.findMatchingFile(directory, matchKeys, extensions, record)
|
||||
if (matchedPath && (await this.tryDeletePath(matchedPath))) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete output resource:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private sanitizePath(target: string): string {
|
||||
return target.trim().replace(/^['"]|['"]$/g, '')
|
||||
}
|
||||
|
||||
private normalizeMatchString(value?: string): string {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
return value
|
||||
.normalize('NFKC')
|
||||
.toLowerCase()
|
||||
.replace(/[^\p{L}\p{N}]+/gu, '')
|
||||
}
|
||||
|
||||
private collectCandidateDirectories(candidatePaths: Set<string>): string[] {
|
||||
const directories = new Set<string>()
|
||||
for (const candidate of candidatePaths) {
|
||||
const sanitized = this.sanitizePath(candidate)
|
||||
if (!sanitized) continue
|
||||
const normalized = path.normalize(sanitized)
|
||||
if (!path.isAbsolute(normalized)) continue
|
||||
const directory = path.dirname(normalized)
|
||||
if (directory) {
|
||||
directories.add(directory)
|
||||
}
|
||||
}
|
||||
return Array.from(directories)
|
||||
}
|
||||
|
||||
private collectExtensions(
|
||||
candidatePaths: Set<string>,
|
||||
record: DownloadHistoryItem | undefined
|
||||
): Set<string> {
|
||||
const extensions = new Set<string>()
|
||||
|
||||
const addExtension = (ext?: string) => {
|
||||
if (!ext) {
|
||||
return
|
||||
}
|
||||
const trimmed = ext.trim()
|
||||
if (!trimmed) {
|
||||
return
|
||||
}
|
||||
const normalized = trimmed.startsWith('.')
|
||||
? trimmed.toLowerCase()
|
||||
: `.${trimmed.toLowerCase()}`
|
||||
extensions.add(normalized)
|
||||
}
|
||||
|
||||
for (const candidate of candidatePaths) {
|
||||
const sanitized = this.sanitizePath(candidate)
|
||||
if (!sanitized) continue
|
||||
addExtension(path.extname(sanitized))
|
||||
}
|
||||
|
||||
addExtension(record?.outputPath ? path.extname(record.outputPath) : undefined)
|
||||
addExtension(record?.selectedFormat?.ext)
|
||||
addExtension(record?.selectedFormat?.video_ext)
|
||||
addExtension(record?.selectedFormat?.audio_ext)
|
||||
|
||||
if (extensions.size === 0) {
|
||||
const fallback =
|
||||
record?.type === 'audio'
|
||||
? ['.mp3', '.m4a', '.aac', '.ogg', '.opus', '.flac', '.wav']
|
||||
: ['.mp4', '.mkv', '.webm', '.mov', '.avi']
|
||||
for (const ext of fallback) {
|
||||
extensions.add(ext)
|
||||
}
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
private collectMatchKeys(
|
||||
record: DownloadHistoryItem | undefined,
|
||||
candidatePaths: Set<string>
|
||||
): string[] {
|
||||
const keys = new Set<string>()
|
||||
const fallbackKeys: string[] = []
|
||||
|
||||
const tryAddKey = (value?: string) => {
|
||||
if (!value) return
|
||||
const normalized = this.normalizeMatchString(value)
|
||||
if (!normalized) return
|
||||
if (normalized.length >= 3) {
|
||||
keys.add(normalized)
|
||||
} else {
|
||||
fallbackKeys.push(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
for (const candidate of candidatePaths) {
|
||||
const sanitized = this.sanitizePath(candidate)
|
||||
if (!sanitized) continue
|
||||
const baseName = path.parse(sanitized).name
|
||||
tryAddKey(baseName)
|
||||
}
|
||||
|
||||
tryAddKey(record?.title)
|
||||
tryAddKey(record?.id)
|
||||
|
||||
if (keys.size === 0) {
|
||||
for (const key of fallbackKeys) {
|
||||
keys.add(key)
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(keys)
|
||||
}
|
||||
|
||||
private async findMatchingFile(
|
||||
directory: string,
|
||||
matchKeys: string[],
|
||||
extensions: Set<string>,
|
||||
record: DownloadHistoryItem | undefined
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const dirStats = await fs.stat(directory).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code === 'ENOENT') {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
})
|
||||
|
||||
if (!dirStats || !dirStats.isDirectory()) {
|
||||
return null
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(directory, { withFileTypes: true })
|
||||
const matches: Array<{ path: string; diff: number }> = []
|
||||
const targetTimestamp = record?.completedAt ?? record?.downloadedAt
|
||||
const maxDiff = 10 * 60 * 1000 // 10 minutes
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue
|
||||
|
||||
const entryPath = path.join(directory, entry.name)
|
||||
const entryExt = path.extname(entry.name).toLowerCase()
|
||||
if (extensions.size > 0 && entryExt && !extensions.has(entryExt)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const normalizedName = this.normalizeMatchString(entry.name)
|
||||
if (!normalizedName && matchKeys.length > 0) continue
|
||||
|
||||
const hasMatchKeys = matchKeys.length > 0
|
||||
const isMatch = hasMatchKeys
|
||||
? matchKeys.some((key) => key && normalizedName.includes(key))
|
||||
: true
|
||||
if (!isMatch) continue
|
||||
|
||||
const stats = await fs.stat(entryPath).catch(() => null)
|
||||
if (!stats) continue
|
||||
|
||||
if (targetTimestamp) {
|
||||
const diff = Math.abs(stats.mtimeMs - targetTimestamp)
|
||||
if (diff > maxDiff) {
|
||||
continue
|
||||
}
|
||||
matches.push({ path: entryPath, diff })
|
||||
} else {
|
||||
if (!hasMatchKeys) {
|
||||
continue
|
||||
}
|
||||
matches.push({ path: entryPath, diff: Number.POSITIVE_INFINITY })
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
matches.sort((a, b) => a.diff - b.diff)
|
||||
return matches[0]?.path ?? null
|
||||
} catch (error) {
|
||||
console.error('Failed to search for matching file:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private async tryDeletePath(rawPath: string): Promise<boolean> {
|
||||
const sanitizedPath = this.sanitizePath(rawPath)
|
||||
if (!sanitizedPath) return false
|
||||
|
||||
const normalizedPath = path.normalize(sanitizedPath)
|
||||
const stats = await fs.stat(normalizedPath).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code === 'ENOENT') {
|
||||
return null
|
||||
}
|
||||
throw error
|
||||
})
|
||||
|
||||
if (!stats) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (stats.isFile()) {
|
||||
await fs.unlink(normalizedPath).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
const entries = await fs.readdir(normalizedPath)
|
||||
if (entries.length === 0) {
|
||||
await fs.rmdir(normalizedPath).catch((error) => {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code !== 'ENOENT') {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
getHistoryCount(_context: IpcContext): {
|
||||
active: number
|
||||
|
||||
Reference in New Issue
Block a user