Search code examples
c#uwpgoogle-drive-apitask

Cancel File Upload In Google Drive API


I have code for uploading files that work on UWP. I've looked for a way to cancel the upload. Didn't find. There is a way to cancel that? I tried to add CancellationToken but there is no place to set this in the DriveAPI properties.

try
{
    StorageFile file = StorageFile.GetFileFromPathAsync(path).AsTask().Result;
    String ID;

    var fileMetadata = new Google.Apis.Drive.v3.Data.File()
    {
        Name = Path.GetFileName(file.Path),
        MimeType = "application/octet-stream"
        
    };

    using (var stream = file.OpenStreamForReadAsync().Result)
    {
        

        var request = this.drive.Files.Create(fileMetadata, stream, "application/octet-stream");


        request.ProgressChanged += (IUploadProgress progressInfo) =>
        {
            if (progressInfo.Status == UploadStatus.Completed)
            {
                Debug.WriteLine($"done!");
                prog.Report(progressInfo);

            }
            else if (progressInfo.Status == UploadStatus.Failed)
            {
                Debug.WriteLine($"Failed To Upload Into Google Drive");
                prog.Report(progressInfo);
            }
            else
            {
                Debug.WriteLine(Convert.ToDouble(progressInfo.BytesSent/(1024*1024)) + " MB has sent");
                prog.Report(progressInfo);

            }
        };

        request.ChunkSize = 262144;
        request.Upload();
        

        var uploadedFile = request.ResponseBody;
        Debug.WriteLine($"File uploaded: {uploadedFile.Name} ({uploadedFile.Id})");

        ID = uploadedFile.Id;
        return ID;
    }

}

I tried to place if(cancelationtoken) in progressInfo.Status == UploadStatus.Uploading but It didn't stop the uploading. it only stops retorting to Progress.

Someone knows maybe how to dispose of the entire Task<>?

That's what I tried to do:

request.ProgressChanged += (IUploadProgress progressInfo) =>
        {
            if (progressInfo.Status == UploadStatus.Completed)
            {
                Debug.WriteLine($"done!");
                prog.Report(progressInfo);

            }
            else if (progressInfo.Status == UploadStatus.Failed)
            {
                Debug.WriteLine($"Failed To Upload Into Google Drive");
                prog.Report(progressInfo);
            }
            else if (progressInfo.Status == UploadStatus.Uploading)
            {
                //(if(canceltoken.iscancel)...){}
                Debug.WriteLine(Convert.ToDouble(progressInfo.BytesSent/(1024*1024)) + " MB has sent");
                prog.Report(progressInfo);

            }
        };

Solution

  • You wish to be able to cancel a file upload and this could be made easier by making your upload method async. Then you would be able to make proper use of the await operator and could put the cancellation token in the upload call in this format:

    await request.UploadAsync(cancellationToken)


    I notice a couple of places in your code where you downgrade async methods to blocking calls e.g.

    StorageFile.GetFileFromPathAsync(path).AsTask().Result

    or

    var stream = file.OpenStreamForReadAsync().Result

    This somewhat defeats the purpose of those methods being asynchronous in the first place, and you may run into some deadlocks or other threading problems doing this.


    So I would say it could be highly advantageous to reframe your method something like this:

    
    using Metadata = Google.Apis.Drive.v3.Data.File;
    
    async Task<string> Upload(string path, CancellationToken cancellationToken, IProgress<IUploadProgress> prog)
    {
        StorageFile file = await StorageFile.GetFileFromPathAsync(path);
        string ID;
    
        var fileMetadata = new Metadata()
        {
            Name = Path.GetFileName(file.Path),
            MimeType = "application/octet-stream"
        };
    
        using (var stream = await file.OpenStreamForReadAsync())
        {
            var request = this.drive.Files.Create(fileMetadata, stream, "application/octet-stream");
    
            request.ProgressChanged += (IUploadProgress progressInfo) =>
            {
                if (progressInfo.Status == UploadStatus.Completed)
                {
                    Debug.WriteLine($"done!");
                    prog.Report(progressInfo);
    
                }
                else if (progressInfo.Status == UploadStatus.Failed)
                {
                    Debug.WriteLine($"Failed To Upload Into Google Drive");
                    prog.Report(progressInfo);
                }
                else
                {
                    Debug.WriteLine(Convert.ToDouble(progressInfo.BytesSent / (1024 * 1024)) + " MB has sent");
                    prog.Report(progressInfo);
    
                }
            };
    
            request.ChunkSize = 262144;
            await request.UploadAsync(cancellationToken);
    
            var uploadedFile = request.ResponseBody;
            Debug.WriteLine($"File uploaded: {uploadedFile.Name} ({uploadedFile.Id})");
    
            ID = uploadedFile.Id;
            return ID;
        }
    }
    

    Calling your async method

    You don't necessarily have to wait for your async method to run. Here's an example of discarding the return value in a "fire and forget" call to the Upload method in a button click handler. You can also cancel a long-running upload with the second button handler.

    CancellationTokenSource? _cts;
    Progress Progress { get; } = new Progress();
    
    void ButtonUpload_Click(object sender, EventArgs e)
    {
        var path = Path.Combine(SpecialPath.GoogleOutbound.GetRelEnvPath(), "myUploadFile.txt");
        _cts = new CancellationTokenSource();
        _ = Upload(path, _cts.Token, Progress);
    }
    
    void ButtonCancelUpload_Click(object sender, EventArgs e)
    {
        _cts?.Cancel();
    }