mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-25 17:46:03 +00:00
wip
This commit is contained in:
@@ -33,8 +33,16 @@ export abstract class FileTransfer {
|
|||||||
return this.completedBytes
|
return this.completedBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatus (): string {
|
||||||
|
return this.status
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotalSize (): number {
|
||||||
|
return this.totalSize
|
||||||
|
}
|
||||||
|
|
||||||
isComplete (): boolean {
|
isComplete (): boolean {
|
||||||
return this.completedBytes >= this.getSize()
|
return this.completed
|
||||||
}
|
}
|
||||||
|
|
||||||
isCancelled (): boolean {
|
isCancelled (): boolean {
|
||||||
@@ -46,6 +54,18 @@ export abstract class FileTransfer {
|
|||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStatus (status: string): void {
|
||||||
|
this.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
setTotalSize (size: number): void {
|
||||||
|
this.totalSize = size
|
||||||
|
}
|
||||||
|
|
||||||
|
setCompleted (completed: boolean): void {
|
||||||
|
this.completed = completed
|
||||||
|
}
|
||||||
|
|
||||||
protected increaseProgress (bytes: number): void {
|
protected increaseProgress (bytes: number): void {
|
||||||
if (!bytes) {
|
if (!bytes) {
|
||||||
return
|
return
|
||||||
@@ -56,9 +76,12 @@ export abstract class FileTransfer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private completedBytes = 0
|
private completedBytes = 0
|
||||||
|
private totalSize = 0
|
||||||
private lastChunkStartTime = Date.now()
|
private lastChunkStartTime = Date.now()
|
||||||
private lastChunkSpeed = 0
|
private lastChunkSpeed = 0
|
||||||
private cancelled = false
|
private cancelled = false
|
||||||
|
private completed = false
|
||||||
|
private status = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class FileDownload extends FileTransfer {
|
export abstract class FileDownload extends FileTransfer {
|
||||||
|
@@ -5,7 +5,9 @@
|
|||||||
.icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}
|
.icon(*ngIf='isDownload(transfer)') !{require('../icons/download.svg')}
|
||||||
.icon(*ngIf='!isDownload(transfer)') !{require('../icons/upload.svg')}
|
.icon(*ngIf='!isDownload(transfer)') !{require('../icons/upload.svg')}
|
||||||
.main
|
.main
|
||||||
label.no-wrap([ngbTooltip]='transfer.getName()') {{transfer.getName()}}
|
label.no-wrap([ngbTooltip]='transfer.getName()')
|
||||||
|
| {{transfer.getName()}}
|
||||||
|
span.ms-2.text-muted(*ngIf='transfer.getStatus()') ({{transfer.getStatus()}})
|
||||||
ngb-progressbar([type]='transfer.isComplete() ? "success" : transfer.isCancelled() ? "danger" : "info"', [value]='getProgress(transfer)')
|
ngb-progressbar([type]='transfer.isComplete() ? "success" : transfer.isCancelled() ? "danger" : "info"', [value]='getProgress(transfer)')
|
||||||
.metadata
|
.metadata
|
||||||
.size {{transfer.getSize()|filesize}}
|
.size {{transfer.getSize()|filesize}}
|
||||||
|
@@ -272,7 +272,7 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async startDownloadDirectory (name: string, estimatedSize?: number): Promise<DirectoryDownload|null> {
|
async startDownloadDirectory (name: string, estimatedSize?: number): Promise<DirectoryDownload|null> {
|
||||||
const selectedFolder = await this.pickDirectory(this.translate.instant('Select destination folder for {name}', { name }))
|
const selectedFolder = await this.pickDirectory(this.translate.instant('Select destination folder for {name}', { name }), this.translate.instant('Download here'))
|
||||||
if (!selectedFolder) {
|
if (!selectedFolder) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -284,7 +284,7 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
counter++
|
counter++
|
||||||
}
|
}
|
||||||
|
|
||||||
const transfer = new ElectronDirectoryDownload(downloadPath, name, estimatedSize ?? 0, this.electron, this.zone, this)
|
const transfer = new ElectronDirectoryDownload(downloadPath, name, estimatedSize ?? 0, this.electron, this.zone)
|
||||||
await wrapPromise(this.zone, transfer.open())
|
await wrapPromise(this.zone, transfer.open())
|
||||||
this.fileTransferStarted.next(transfer)
|
this.fileTransferStarted.next(transfer)
|
||||||
return transfer
|
return transfer
|
||||||
@@ -300,11 +300,12 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async pickDirectory (title?: string): Promise<string | null> {
|
async pickDirectory (title?: string, buttonLabel?: string): Promise<string | null> {
|
||||||
const result = await this.electron.dialog.showOpenDialog(
|
const result = await this.electron.dialog.showOpenDialog(
|
||||||
this.hostWindow.getWindow(),
|
this.hostWindow.getWindow(),
|
||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
|
buttonLabel,
|
||||||
properties: ['openDirectory', 'showHiddenFiles'],
|
properties: ['openDirectory', 'showHiddenFiles'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -340,6 +341,7 @@ class ElectronFileUpload extends FileUpload {
|
|||||||
const stat = await fs.stat(this.filePath)
|
const stat = await fs.stat(this.filePath)
|
||||||
this.size = stat.size
|
this.size = stat.size
|
||||||
this.mode = stat.mode
|
this.mode = stat.mode
|
||||||
|
this.setTotalSize(this.size)
|
||||||
this.file = await fs.open(this.filePath, 'r')
|
this.file = await fs.open(this.filePath, 'r')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,6 +360,9 @@ class ElectronFileUpload extends FileUpload {
|
|||||||
async read (): Promise<Uint8Array> {
|
async read (): Promise<Uint8Array> {
|
||||||
const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
|
const result = await this.file.read(this.buffer, 0, this.buffer.length, null)
|
||||||
this.increaseProgress(result.bytesRead)
|
this.increaseProgress(result.bytesRead)
|
||||||
|
if (this.getCompletedBytes() >= this.getSize()) {
|
||||||
|
this.setCompleted(true)
|
||||||
|
}
|
||||||
return this.buffer.slice(0, result.bytesRead)
|
return this.buffer.slice(0, result.bytesRead)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,6 +384,7 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
||||||
|
this.setTotalSize(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
async open (): Promise<void> {
|
async open (): Promise<void> {
|
||||||
@@ -400,6 +406,9 @@ class ElectronFileDownload extends FileDownload {
|
|||||||
this.increaseProgress(result.bytesWritten)
|
this.increaseProgress(result.bytesWritten)
|
||||||
pos += result.bytesWritten
|
pos += result.bytesWritten
|
||||||
}
|
}
|
||||||
|
if (this.getCompletedBytes() >= this.getSize()) {
|
||||||
|
this.setCompleted(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): void {
|
close (): void {
|
||||||
@@ -414,13 +423,13 @@ class ElectronDirectoryDownload extends DirectoryDownload {
|
|||||||
constructor (
|
constructor (
|
||||||
private basePath: string,
|
private basePath: string,
|
||||||
private name: string,
|
private name: string,
|
||||||
private estimatedSize: number,
|
estimatedSize: number,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private platformService: ElectronPlatformService,
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
this.powerSaveBlocker = electron.powerSaveBlocker.start('prevent-app-suspension')
|
||||||
|
this.setTotalSize(estimatedSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
async open (): Promise<void> {
|
async open (): Promise<void> {
|
||||||
@@ -432,7 +441,7 @@ class ElectronDirectoryDownload extends DirectoryDownload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSize (): number {
|
getSize (): number {
|
||||||
return this.estimatedSize
|
return this.getTotalSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
async createDirectory (relativePath: string): Promise<void> {
|
async createDirectory (relativePath: string): Promise<void> {
|
||||||
@@ -446,7 +455,6 @@ class ElectronDirectoryDownload extends DirectoryDownload {
|
|||||||
|
|
||||||
const fileDownload = new ElectronFileDownload(fullPath, mode, size, this.electron)
|
const fileDownload = new ElectronFileDownload(fullPath, mode, size, this.electron)
|
||||||
await wrapPromise(this.zone, fileDownload.open())
|
await wrapPromise(this.zone, fileDownload.open())
|
||||||
this.platformService._registerFileTransfer(fileDownload)
|
|
||||||
return fileDownload
|
return fileDownload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -222,15 +222,19 @@ export class SFTPPanelComponent {
|
|||||||
|
|
||||||
async downloadFolder (folder: SFTPFile): Promise<void> {
|
async downloadFolder (folder: SFTPFile): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const estimatedSize = await this.calculateFolderSize(folder)
|
const transfer = await this.platform.startDownloadDirectory(folder.name, 0)
|
||||||
|
|
||||||
const transfer = await this.platform.startDownloadDirectory(folder.name, estimatedSize)
|
|
||||||
if (!transfer) {
|
if (!transfer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start background size calculation and download simultaneously
|
||||||
|
const sizeCalculationPromise = this.calculateFolderSizeAndUpdate(folder, transfer)
|
||||||
|
const downloadPromise = this.downloadFolderRecursive(folder, transfer, '')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.downloadFolderRecursive(folder, transfer, '')
|
await Promise.all([sizeCalculationPromise, downloadPromise])
|
||||||
|
transfer.setStatus('')
|
||||||
|
transfer.setCompleted(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
transfer.cancel()
|
transfer.cancel()
|
||||||
throw error
|
throw error
|
||||||
@@ -243,15 +247,16 @@ export class SFTPPanelComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async calculateFolderSize (folder: SFTPFile): Promise<number> {
|
private async calculateFolderSizeAndUpdate (folder: SFTPFile, transfer: DirectoryDownload) {
|
||||||
let totalSize = 0
|
let totalSize = 0
|
||||||
const items = await this.sftp.readdir(folder.fullPath)
|
const items = await this.sftp.readdir(folder.fullPath)
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.isDirectory) {
|
if (item.isDirectory) {
|
||||||
totalSize += await this.calculateFolderSize(item)
|
totalSize += await this.calculateFolderSizeAndUpdate(item, transfer)
|
||||||
} else {
|
} else {
|
||||||
totalSize += item.size
|
totalSize += item.size
|
||||||
}
|
}
|
||||||
|
transfer.setTotalSize(totalSize)
|
||||||
}
|
}
|
||||||
return totalSize
|
return totalSize
|
||||||
}
|
}
|
||||||
@@ -266,13 +271,12 @@ export class SFTPPanelComponent {
|
|||||||
|
|
||||||
const itemRelativePath = relativePath ? `${relativePath}/${item.name}` : item.name
|
const itemRelativePath = relativePath ? `${relativePath}/${item.name}` : item.name
|
||||||
|
|
||||||
|
transfer.setStatus(itemRelativePath)
|
||||||
if (item.isDirectory) {
|
if (item.isDirectory) {
|
||||||
await transfer.createDirectory(itemRelativePath)
|
await transfer.createDirectory(itemRelativePath)
|
||||||
await this.downloadFolderRecursive(item, transfer, itemRelativePath)
|
await this.downloadFolderRecursive(item, transfer, itemRelativePath)
|
||||||
} else {
|
} else {
|
||||||
// Create file download for this individual file
|
|
||||||
const fileDownload = await transfer.createFile(itemRelativePath, item.mode, item.size)
|
const fileDownload = await transfer.createFile(itemRelativePath, item.mode, item.size)
|
||||||
|
|
||||||
await this.sftp.download(item.fullPath, fileDownload)
|
await this.sftp.download(item.fullPath, fileDownload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -35,7 +35,7 @@ export class CommonSFTPContextMenu extends SFTPContextMenuItemProvider {
|
|||||||
if (item.isDirectory && this.hostApp.platform !== Platform.Web) {
|
if (item.isDirectory && this.hostApp.platform !== Platform.Web) {
|
||||||
items.push({
|
items.push({
|
||||||
click: () => panel.downloadFolder(item),
|
click: () => panel.downloadFolder(item),
|
||||||
label: this.translate.instant('Download folder'),
|
label: this.translate.instant('Download directory'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user