Progress bar for parallel file upload with SSH.NET and WPF MVVM

狂风中的少年 提交于 2019-12-10 11:49:20

问题


I am trying to build a WPF application that uses SSH.NET to upload multiple files on a server in parallel and display a progress bar for each of them.

In the view I have the following data grid:

                <DataGrid
                    AlternatingRowBackground="LightGray"
                    AutoGenerateColumns="False"
                    CanUserAddRows="False"
                    CanUserDeleteRows="False"
                    IsReadOnly="True"
                    ItemsSource="{Binding Files, Mode=TwoWay}"
                    SelectedItem="{Binding SelectedFile, Mode=OneWayToSource}">
                    <DataGrid.Columns>
                        <DataGridTextColumn Binding="{Binding Path=DisplayName}" Header="File" />
                        <DataGridTextColumn Binding="{Binding Path=FileSize}" Header="Size" />
                        <DataGridTextColumn Binding="{Binding Path=IsUploaded, Mode=OneWay}" Header="Uploaded" />
                        <DataGridTextColumn Binding="{Binding Path=IsUploading, Mode=OneWay}" Header="Uploading" />
                        <DataGridTemplateColumn Header="Progress">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Grid>
                                        <ProgressBar
                                            Width="300"
                                            Height="10"
                                            Maximum="{Binding FileSize}"
                                            Value="{Binding Path=SizeUploaded, Mode=OneWay}" />
                                        <TextBlock
                                            Margin="0,5,0,0"
                                            HorizontalAlignment="Center"
                                            Text="{Binding Path=PercentageCompleted, StringFormat={}{0}%}" />
                                    </Grid>
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTemplateColumn Header="Start">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button cal:Message.Attach="UploadFile" Content="Start" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTemplateColumn Header="Stop">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button cal:Message.Attach="StopTransfer" Content="Stop" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTemplateColumn Header="Remove">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Button cal:Message.Attach="RemoveFile" Content="Remove" />
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>

                </DataGrid>

The ViewModel has public properties to bind DataGrid's items and SelectedItem:

    public BindingList<UploadedFile> Files { get; set; }

    public UploadedFile SelectedFile
    {
        get { return _selectedFile; }
        set
        {
            _selectedFile = value;
            NotifyOfPropertyChange(() => SelectedFile);
        }
    }

To get a file from hard-drive I am using MvvmDialgos library and call this method to create a new UploadedFile and add it to Files list:

        public void SelectFile()
    {
        var success = OpenFileDialog(this);

        if (success)
        {
            var file = new UploadedFile
            {
                FullName = OpenFileDialogSettings.FileName
            };

            file.DisplayName = Path.GetFileName(file.FullName);

            var fileInfo = new FileInfo(file.FullName);
            file.FileSize = (int)fileInfo.Length;

            if (Files == null)
                Files = new BindingList<UploadedFile>();

            Files.Add(file);

            NotifyOfPropertyChange(() => Files);
        }

    }

To upload the file on the server, with SSH.NET I am doing to following:

    private async Task UploadFileToServer(ConnectionModel connection, string fileName, string destinationPath)
    {
        if (!string.IsNullOrWhiteSpace(destinationPath))
        {
            await Task.Run(() =>
            {
                var currentUploadedFile = Files.FirstOrDefault(x => x.FullName == fileName);

                currentUploadedFile.IsUploading = true;
                try
                {
                    _fileManager.UploadFile(connection, fileName, destinationPath);

                    currentUploadedFile.IsUploaded = true;
                }

                catch (Exception ex)
                {
                    BroadCastErrorMessage(ex.Message);
                    IsConnectionTabExpanded = true;
                }
                finally
                {
                    currentUploadedFile.IsUploading = false;
                }
            });
        }

        else
        {
            BroadCastErrorMessage("ERROR: You must enter a destination folder!");
            IsConnectionTabExpanded = true;
        }
    }

FileManager call's the actually ssh.net library with:

                        using (var fs = new FileStream(fileName, FileMode.Open))
                        {
                            fullFileName = fileName;
                            sftp.BufferSize = 4 * 1024;
                            sftp.UploadFile(fs, Path.GetFileName(fileName), true, uploadCallback: UpdateProgresBar);
                            OnFileUploadedSuccess($@"Successfully uploaded {fileName} in {path}.");
                            status = FileRenamedStatus.Uploaded;
                        }

and raises an event with the uploaded size:

    private void UpdateProgresBar(ulong uploaded)
    {
        OnFileUploading(uploaded);
    }

   protected void OnFileUploading(ulong uploaded)
    {
        FileUploading?.Invoke(this, new UploadingEventArgs(new UploadedFile
        {
            SizeUploaded = (int)uploaded,
            FullName = fullFileName
        }));
    }

In the ViewModel I am listening for the event and update the progress bar:

    private void OnUploading(object sender, UploadingEventArgs e)
    {
        foreach (var item in Files.ToList())
        {
            if (item.FullName == e.UploadedFile.FullName)
            {
                item.SizeUploaded = e.UploadedFile.SizeUploaded;

                var percentage = (double)e.UploadedFile.SizeUploaded / item.FileSize;

                item.PercentageCompleted = Math.Round(percentage * 100, 2);
            }
        }
    }

My problem is when I am starting a second file transfer the progress bar for the first one stops and the one of the second transfer is going nuts, displaying random values, increasing and decreasing. My guess is somehow adding in the progress of the first file too.

Somehow I am unable to separate the progress for each progress bar.

What am I doing wrong?


回答1:


I am not sure if this is the correct fix but it seems that getting a new instance of _fileManager from IoC (using Caliburn.Micro) every time I am calling UploadFileToServer() solved the progressbar issue:

var manager = IoC.Get<IFileTransferManager>(); manager.OnUploading += OnUploading;



来源:https://stackoverflow.com/questions/57902034/progress-bar-for-parallel-file-upload-with-ssh-net-and-wpf-mvvm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!