Browse Source
- Implement folder navigation with breadcrumbs in BackupFilesList - Distinguish files and folders in UI, allow folder navigation - Add debug mode to forcibly treat all entries as files for diagnosis - Add detailed debug logging to file discovery (readdir, stat, entries) - Show warning in UI when debug mode is active - Prepare for further improvements to handle stat failures gracefully Co-authored-by: Matthew Raymercapacitor-local-save
11 changed files with 1784 additions and 75 deletions
@ -0,0 +1,597 @@ |
|||||
|
/** |
||||
|
* Backup Files List Component |
||||
|
* |
||||
|
* Displays a list of backup files saved by the app and provides options to: |
||||
|
* - View backup files by type (contacts, seed, other) |
||||
|
* - Open individual files in the device's file viewer |
||||
|
* - Access the backup directory in the device's file explorer |
||||
|
* |
||||
|
* @component |
||||
|
* @displayName BackupFilesList |
||||
|
* @example |
||||
|
* ```vue |
||||
|
* <BackupFilesList /> |
||||
|
* ``` |
||||
|
*/ |
||||
|
|
||||
|
<template> |
||||
|
<div class="backup-files-list"> |
||||
|
<div class="flex justify-between items-center mb-4"> |
||||
|
<h3 class="text-lg font-semibold">Backup Files</h3> |
||||
|
<div class="flex gap-2"> |
||||
|
<button |
||||
|
v-if="platformCapabilities.hasFileSystem" |
||||
|
@click="refreshFiles()" |
||||
|
class="text-sm bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded" |
||||
|
:disabled="isLoading" |
||||
|
> |
||||
|
<font-awesome |
||||
|
icon="refresh" |
||||
|
class="fa-fw" |
||||
|
:class="{ 'animate-spin': isLoading }" |
||||
|
/> |
||||
|
Refresh |
||||
|
</button> |
||||
|
<button |
||||
|
v-if="platformCapabilities.hasFileSystem" |
||||
|
@click="openBackupDirectory()" |
||||
|
class="text-sm bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded" |
||||
|
:disabled="isLoading" |
||||
|
> |
||||
|
<font-awesome icon="folder-open" class="fa-fw" /> |
||||
|
Open Directory |
||||
|
</button> |
||||
|
<button |
||||
|
v-if="platformCapabilities.hasFileSystem && isDevelopment" |
||||
|
@click="debugFileDiscovery()" |
||||
|
class="text-sm bg-yellow-500 hover:bg-yellow-600 text-white px-3 py-1 rounded" |
||||
|
:disabled="isLoading" |
||||
|
title="Debug file discovery (development only)" |
||||
|
> |
||||
|
<font-awesome icon="bug" class="fa-fw" /> |
||||
|
Debug |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="isLoading" class="text-center py-4"> |
||||
|
<font-awesome icon="spinner" class="animate-spin fa-2x" /> |
||||
|
<p class="mt-2">Loading backup files...</p> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="backupFiles.length === 0" class="text-center py-4 text-gray-500"> |
||||
|
<font-awesome icon="folder-open" class="fa-2x mb-2" /> |
||||
|
<p>No backup files found</p> |
||||
|
<p class="text-sm mt-1">Create backups using the export functions above</p> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="space-y-2"> |
||||
|
<!-- File Type Filter --> |
||||
|
<div class="flex gap-2 mb-3"> |
||||
|
<button |
||||
|
v-for="type in ['all', 'contacts', 'seed', 'other'] as const" |
||||
|
:key="type" |
||||
|
@click="selectedType = type" |
||||
|
:class="[ |
||||
|
'text-sm px-3 py-1 rounded border', |
||||
|
selectedType === type |
||||
|
? 'bg-blue-500 text-white border-blue-500' |
||||
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50' |
||||
|
]" |
||||
|
> |
||||
|
{{ type === 'all' ? 'All' : type.charAt(0).toUpperCase() + type.slice(1) }} |
||||
|
<span class="ml-1 text-xs"> |
||||
|
({{ getFileCountByType(type) }}) |
||||
|
</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Files List --> |
||||
|
<div class="flex items-center gap-2 mb-2"> |
||||
|
<span v-for="(crumb, idx) in breadcrumbs" :key="idx"> |
||||
|
<span |
||||
|
class="text-blue-600 cursor-pointer underline" |
||||
|
@click="goToBreadcrumb(idx)" |
||||
|
v-if="idx < breadcrumbs.length - 1" |
||||
|
> |
||||
|
{{ crumb }} |
||||
|
</span> |
||||
|
<span v-else class="font-bold">{{ crumb }}</span> |
||||
|
<span v-if="idx < breadcrumbs.length - 1"> / </span> |
||||
|
</span> |
||||
|
</div> |
||||
|
<div v-if="currentPath.length > 1" class="mb-2"> |
||||
|
<button @click="goUp" class="text-xs text-blue-500 underline">⬅ Up</button> |
||||
|
</div> |
||||
|
<div class="mb-2"> |
||||
|
<label class="inline-flex items-center"> |
||||
|
<input type="checkbox" v-model="debugShowAll" @change="loadDirectory" class="mr-2" /> |
||||
|
<span class="text-xs">Debug: Show all entries as files</span> |
||||
|
</label> |
||||
|
<span v-if="debugShowAll" class="text-xs text-red-600 ml-2">[Debug mode: forcibly treating all entries as files]</span> |
||||
|
</div> |
||||
|
<div class="space-y-2 max-h-64 overflow-y-auto"> |
||||
|
<div |
||||
|
v-for="entry in folders" |
||||
|
:key="'folder-' + entry.path" |
||||
|
class="flex items-center justify-between p-3 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer" |
||||
|
@click="openFolder(entry)" |
||||
|
> |
||||
|
<div class="flex items-center gap-2"> |
||||
|
<font-awesome icon="folder" class="fa-fw text-yellow-500" /> |
||||
|
<span class="font-medium">{{ entry.name }}</span> |
||||
|
<span class="text-xs bg-gray-200 text-gray-700 px-2 py-0.5 rounded-full ml-2">Folder</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div |
||||
|
v-for="entry in files" |
||||
|
:key="'file-' + entry.path" |
||||
|
class="flex items-center justify-between p-3 bg-white border border-gray-200 rounded-lg hover:bg-gray-50" |
||||
|
> |
||||
|
<div class="flex-1 min-w-0"> |
||||
|
<div class="flex items-center gap-2"> |
||||
|
<font-awesome icon="file-alt" class="fa-fw text-gray-500" /> |
||||
|
<span class="font-medium truncate">{{ entry.name }}</span> |
||||
|
</div> |
||||
|
<div class="text-sm text-gray-500 mt-1"> |
||||
|
<span v-if="entry.size">{{ formatFileSize(entry.size) }}</span> |
||||
|
<span v-else>Size unknown</span> |
||||
|
<span v-if="entry.path && !platformCapabilities.isIOS" class="ml-2 text-xs text-blue-600">📁 {{ entry.path }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="flex gap-2 ml-3"> |
||||
|
<button |
||||
|
@click="openFile(entry.uri, entry.name)" |
||||
|
class="text-blue-500 hover:text-blue-700 p-1" |
||||
|
title="Open file" |
||||
|
> |
||||
|
<font-awesome icon="external-link-alt" class="fa-fw" /> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Summary --> |
||||
|
<div class="text-sm text-gray-500 mt-3 pt-3 border-t"> |
||||
|
Showing {{ filteredFiles.length }} of {{ backupFiles.length }} backup files |
||||
|
</div> |
||||
|
|
||||
|
<div class="text-sm text-gray-600 mb-2"> |
||||
|
<p>📁 Backup files are saved to persistent storage that survives app installations:</p> |
||||
|
<ul class="list-disc list-inside ml-2 mt-1 text-xs"> |
||||
|
<li v-if="platformCapabilities.isIOS">iOS: Documents folder (accessible via Files app)</li> |
||||
|
<li v-if="platformCapabilities.isMobile && !platformCapabilities.isIOS">Android: Downloads/TimeSafari or external storage (accessible via file managers)</li> |
||||
|
<li v-if="!platformCapabilities.isMobile">Desktop: User's download directory</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
import { Component, Vue, Watch } from "vue-facing-decorator"; |
||||
|
import { NotificationIface } from "../constants/app"; |
||||
|
import { logger } from "../utils/logger"; |
||||
|
import { PlatformServiceFactory } from "../services/PlatformServiceFactory"; |
||||
|
import { |
||||
|
PlatformService, |
||||
|
PlatformCapabilities, |
||||
|
} from "../services/PlatformService"; |
||||
|
|
||||
|
/** |
||||
|
* @vue-component |
||||
|
* Backup Files List Component |
||||
|
* Displays and manages backup files with platform-specific functionality |
||||
|
*/ |
||||
|
@Component |
||||
|
export default class BackupFilesList extends Vue { |
||||
|
/** |
||||
|
* Notification function injected by Vue |
||||
|
* Used to show success/error messages to the user |
||||
|
*/ |
||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void; |
||||
|
|
||||
|
/** |
||||
|
* Platform service instance for platform-specific operations |
||||
|
*/ |
||||
|
private platformService: PlatformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
/** |
||||
|
* Platform capabilities for the current platform |
||||
|
*/ |
||||
|
private get platformCapabilities(): PlatformCapabilities { |
||||
|
return this.platformService.getCapabilities(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* List of backup files found on the device |
||||
|
*/ |
||||
|
backupFiles: Array<{name: string, uri: string, size?: number, type: 'contacts' | 'seed' | 'other', path?: string}> = []; |
||||
|
|
||||
|
/** |
||||
|
* Currently selected file type filter |
||||
|
*/ |
||||
|
selectedType: 'all' | 'contacts' | 'seed' | 'other' = 'all'; |
||||
|
|
||||
|
/** |
||||
|
* Loading state for file operations |
||||
|
*/ |
||||
|
isLoading = false; |
||||
|
|
||||
|
/** |
||||
|
* Interval for periodic refresh (5 minutes) |
||||
|
*/ |
||||
|
private refreshInterval: number | null = null; |
||||
|
|
||||
|
/** |
||||
|
* Current path for folder navigation (array for breadcrumbs) |
||||
|
*/ |
||||
|
currentPath: string[] = []; |
||||
|
|
||||
|
/** |
||||
|
* List of files/folders in the current directory |
||||
|
*/ |
||||
|
directoryEntries: Array<{name: string, uri: string, size?: number, path: string, type: 'file' | 'folder'}> = []; |
||||
|
|
||||
|
/** |
||||
|
* Temporary debug mode to show all entries as files |
||||
|
*/ |
||||
|
debugShowAll = false; |
||||
|
|
||||
|
/** |
||||
|
* Lifecycle hook to load backup files when component is mounted |
||||
|
*/ |
||||
|
async mounted() { |
||||
|
if (this.platformCapabilities.hasFileSystem) { |
||||
|
// Set default root path |
||||
|
if (this.platformCapabilities.isIOS) { |
||||
|
this.currentPath = ['.']; |
||||
|
} else { |
||||
|
this.currentPath = ['Download', 'TimeSafari']; |
||||
|
} |
||||
|
await this.loadDirectory(); |
||||
|
this.refreshInterval = window.setInterval(() => { |
||||
|
this.loadDirectory(); |
||||
|
}, 5 * 60 * 1000); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Lifecycle hook to clean up resources when component is unmounted |
||||
|
*/ |
||||
|
beforeUnmount() { |
||||
|
if (this.refreshInterval) { |
||||
|
clearInterval(this.refreshInterval); |
||||
|
this.refreshInterval = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Computed property for filtered files based on selected type |
||||
|
*/ |
||||
|
get filteredFiles() { |
||||
|
if (this.selectedType === 'all') { |
||||
|
return this.backupFiles; |
||||
|
} |
||||
|
return this.backupFiles.filter(file => file.type === this.selectedType); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Computed property to check if we're in development mode |
||||
|
*/ |
||||
|
get isDevelopment(): boolean { |
||||
|
return import.meta.env.DEV; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Load the current directory entries |
||||
|
*/ |
||||
|
async loadDirectory() { |
||||
|
if (!this.platformCapabilities.hasFileSystem) return; |
||||
|
this.isLoading = true; |
||||
|
try { |
||||
|
const path = this.currentPath.join('/') || (this.platformCapabilities.isIOS ? '.' : 'Download/TimeSafari'); |
||||
|
this.directoryEntries = await (this.platformService as any).listFilesInDirectory(path, this.debugShowAll); |
||||
|
logger.log('[BackupFilesList] Loaded directory:', { path, entries: this.directoryEntries }); |
||||
|
} catch (error) { |
||||
|
logger.error('[BackupFilesList] Failed to load directory:', error); |
||||
|
this.directoryEntries = []; |
||||
|
} finally { |
||||
|
this.isLoading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Navigate into a folder |
||||
|
*/ |
||||
|
async openFolder(entry: { name: string; path: string }) { |
||||
|
this.currentPath.push(entry.name); |
||||
|
await this.loadDirectory(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Navigate to a breadcrumb |
||||
|
*/ |
||||
|
async goToBreadcrumb(index: number) { |
||||
|
this.currentPath = this.currentPath.slice(0, index + 1); |
||||
|
await this.loadDirectory(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Go up one directory |
||||
|
*/ |
||||
|
async goUp() { |
||||
|
if (this.currentPath.length > 1) { |
||||
|
this.currentPath.pop(); |
||||
|
await this.loadDirectory(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Computed property for breadcrumbs |
||||
|
*/ |
||||
|
get breadcrumbs() { |
||||
|
return this.currentPath; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Computed property for showing files and folders |
||||
|
*/ |
||||
|
get folders() { |
||||
|
return this.directoryEntries.filter(e => e.type === 'folder'); |
||||
|
} |
||||
|
get files() { |
||||
|
return this.directoryEntries.filter(e => e.type === 'file'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Refreshes the list of backup files from the device |
||||
|
*/ |
||||
|
async refreshFiles() { |
||||
|
if (!this.platformCapabilities.hasFileSystem) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.isLoading = true; |
||||
|
try { |
||||
|
this.backupFiles = await this.platformService.listBackupFiles(); |
||||
|
|
||||
|
logger.log("[BackupFilesList] Refreshed backup files:", { |
||||
|
count: this.backupFiles.length, |
||||
|
files: this.backupFiles.map(f => ({ |
||||
|
name: f.name, |
||||
|
type: f.type, |
||||
|
path: f.path, |
||||
|
size: f.size |
||||
|
})), |
||||
|
platform: this.platformCapabilities.isIOS ? "iOS" : "Android", |
||||
|
timestamp: new Date().toISOString(), |
||||
|
}); |
||||
|
|
||||
|
// Debug: Log file type distribution |
||||
|
const typeCounts = { |
||||
|
contacts: this.backupFiles.filter(f => f.type === 'contacts').length, |
||||
|
seed: this.backupFiles.filter(f => f.type === 'seed').length, |
||||
|
other: this.backupFiles.filter(f => f.type === 'other').length, |
||||
|
total: this.backupFiles.length |
||||
|
}; |
||||
|
logger.log("[BackupFilesList] File type distribution:", typeCounts); |
||||
|
} catch (error) { |
||||
|
logger.error("[BackupFilesList] Failed to refresh backup files:", error); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error Loading Files", |
||||
|
text: "Failed to load backup files from your device.", |
||||
|
}, |
||||
|
5000, |
||||
|
); |
||||
|
} finally { |
||||
|
this.isLoading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Public method to refresh files from external components |
||||
|
* Used by DataExportSection to refresh after saving new files |
||||
|
*/ |
||||
|
public async refreshAfterSave() { |
||||
|
logger.log("[BackupFilesList] Refreshing files after save operation"); |
||||
|
await this.refreshFiles(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Opens a specific file in the device's file viewer |
||||
|
* @param fileUri - URI of the file to open |
||||
|
* @param fileName - Name of the file for display |
||||
|
*/ |
||||
|
async openFile(fileUri: string, fileName: string) { |
||||
|
try { |
||||
|
const result = await this.platformService.openFile(fileUri, fileName); |
||||
|
|
||||
|
if (result.success) { |
||||
|
logger.log("[BackupFilesList] File opened successfully:", { |
||||
|
fileName, |
||||
|
timestamp: new Date().toISOString(), |
||||
|
}); |
||||
|
} else { |
||||
|
throw new Error(result.error || "Failed to open file"); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
logger.error("[BackupFilesList] Failed to open file:", error); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error Opening File", |
||||
|
text: `Failed to open ${fileName}. ${error instanceof Error ? error.message : String(error)}`, |
||||
|
}, |
||||
|
5000, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Opens the backup directory in the device's file explorer |
||||
|
*/ |
||||
|
async openBackupDirectory() { |
||||
|
try { |
||||
|
const result = await this.platformService.openBackupDirectory(); |
||||
|
|
||||
|
if (result.success) { |
||||
|
logger.log("[BackupFilesList] Backup directory opened successfully:", { |
||||
|
timestamp: new Date().toISOString(), |
||||
|
}); |
||||
|
} else { |
||||
|
throw new Error(result.error || "Failed to open backup directory"); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
logger.error("[BackupFilesList] Failed to open backup directory:", error); |
||||
|
this.$notify( |
||||
|
{ |
||||
|
group: "alert", |
||||
|
type: "danger", |
||||
|
title: "Error Opening Directory", |
||||
|
text: `Failed to open backup directory. ${error instanceof Error ? error.message : String(error)}`, |
||||
|
}, |
||||
|
5000, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the count of files for a specific type |
||||
|
* @param type - File type to count |
||||
|
* @returns Number of files of the specified type |
||||
|
*/ |
||||
|
getFileCountByType(type: 'all' | 'contacts' | 'seed' | 'other'): number { |
||||
|
if (type === 'all') { |
||||
|
return this.backupFiles.length; |
||||
|
} |
||||
|
return this.backupFiles.filter(file => file.type === type).length; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the appropriate icon for a file type |
||||
|
* @param type - File type |
||||
|
* @returns FontAwesome icon name |
||||
|
*/ |
||||
|
getFileIcon(type: 'contacts' | 'seed' | 'other'): string { |
||||
|
switch (type) { |
||||
|
case 'contacts': |
||||
|
return 'address-book'; |
||||
|
case 'seed': |
||||
|
return 'key'; |
||||
|
default: |
||||
|
return 'file-alt'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the appropriate icon color for a file type |
||||
|
* @param type - File type |
||||
|
* @returns CSS color class |
||||
|
*/ |
||||
|
getFileIconColor(type: 'contacts' | 'seed' | 'other'): string { |
||||
|
switch (type) { |
||||
|
case 'contacts': |
||||
|
return 'text-blue-500'; |
||||
|
case 'seed': |
||||
|
return 'text-orange-500'; |
||||
|
default: |
||||
|
return 'text-gray-500'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets the appropriate badge color for a file type |
||||
|
* @param type - File type |
||||
|
* @returns CSS color class |
||||
|
*/ |
||||
|
getTypeBadgeColor(type: 'contacts' | 'seed' | 'other'): string { |
||||
|
switch (type) { |
||||
|
case 'contacts': |
||||
|
return 'bg-blue-100 text-blue-800'; |
||||
|
case 'seed': |
||||
|
return 'bg-orange-100 text-orange-800'; |
||||
|
default: |
||||
|
return 'bg-gray-100 text-gray-800'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Formats file size in human-readable format |
||||
|
* @param bytes - File size in bytes |
||||
|
* @returns Formatted file size string |
||||
|
*/ |
||||
|
formatFileSize(bytes: number): string { |
||||
|
if (bytes === 0) return '0 Bytes'; |
||||
|
|
||||
|
const k = 1024; |
||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
||||
|
|
||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Debug method to test file discovery |
||||
|
* Can be called from browser console for troubleshooting |
||||
|
*/ |
||||
|
public async debugFileDiscovery() { |
||||
|
try { |
||||
|
logger.log("[BackupFilesList] Starting debug file discovery..."); |
||||
|
|
||||
|
// Test the platform service's test methods |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
// Test listing all user files |
||||
|
const allFilesResult = await platformService.testListUserFiles(); |
||||
|
logger.log("[BackupFilesList] All user files test result:", allFilesResult); |
||||
|
|
||||
|
// Test listing backup files specifically |
||||
|
const backupFilesResult = await platformService.testBackupFiles(); |
||||
|
logger.log("[BackupFilesList] Backup files test result:", backupFilesResult); |
||||
|
|
||||
|
// Test listing all backup files (if available) |
||||
|
if ('testListAllBackupFiles' in platformService) { |
||||
|
const allBackupFilesResult = await (platformService as any).testListAllBackupFiles(); |
||||
|
logger.log("[BackupFilesList] All backup files test result:", allBackupFilesResult); |
||||
|
} |
||||
|
|
||||
|
// Test debug listing all files without filtering (if available) |
||||
|
if ('debugListAllFiles' in platformService) { |
||||
|
const debugAllFiles = await (platformService as any).debugListAllFiles(); |
||||
|
logger.log("[BackupFilesList] Debug all files (no filtering):", { |
||||
|
count: debugAllFiles.length, |
||||
|
files: debugAllFiles.map((f: any) => ({ name: f.name, path: f.path, size: f.size })) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Test comprehensive step-by-step debug (if available) |
||||
|
if ('debugFileDiscoveryStepByStep' in platformService) { |
||||
|
const stepByStepDebug = await (platformService as any).debugFileDiscoveryStepByStep(); |
||||
|
logger.log("[BackupFilesList] Step-by-step debug output:", stepByStepDebug); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
allFiles: allFilesResult, |
||||
|
backupFiles: backupFilesResult, |
||||
|
currentBackupFiles: this.backupFiles, |
||||
|
debugAllFiles: 'debugListAllFiles' in platformService ? await (platformService as any).debugListAllFiles() : null |
||||
|
}; |
||||
|
} catch (error) { |
||||
|
logger.error("[BackupFilesList] Debug file discovery failed:", error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Watch('platformCapabilities.hasFileSystem', { immediate: true }) |
||||
|
async onFileSystemCapabilityChanged(newVal: boolean) { |
||||
|
if (newVal) { |
||||
|
await this.refreshFiles(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
Loading…
Reference in new issue